草庐IT

微前端解决方案-qiankun实战及部署

e只咸鱼 2023-09-28 原文

先来张图片压压惊
image.png

在线demo:wzs.bengdada.com/

单独访问在线子应用:

一.导读

1.什么是微前端
  • 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
  • 微前端架构具备以下几个核心价值:
    技术栈无关 : 主框架不限制接入应用的技术栈,微应用具备完全自主权
    独立开发、独立部署 : 微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
    增量升级:在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
    独立运行时 : 每个微应用之间状态隔离,运行时状态不共享
  • 微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
2.qiankun是什么
  • qiankun 是一个基于 single-spa微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
    官网: https://qiankun.umijs.org/zh
  • qiankun特性
    基于 single-spa 封装,提供了更加开箱即用的 API。
    技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
    HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
    样式隔离,确保微应用之间样式互相不干扰。
    JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
    ⚡️ 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
    umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
  • 了解完理论基础,让我们动手实践一下···

二.建立项目

image.png

如图: 我建立了一个主应用和三个子应用
主应用 main vue3搭建 "vue": "^3.0.0",
子应用 micro-react react18搭建 "react": "^18.1.0",
子应用 micro-vue2 vue2搭建 "vue": "^2.6.11",
子应用 micro-vue3 vue3搭建 "vue": "^3.0.0",

注意 : vue3技术选型我使用的是vue3 + webpack ,vite目前对于qiankun还不是太友好 ,硬要搞vite代价会很大,后续等官网优化后我们在去使用vite

由于搭建项目太简单我就不说明了 ~ ovo

三.主应用

注意: qiankun 需要一个主应用 来注入所有的子应用
先安装乾坤的依赖包

 yarn add qiankun # 或者 npm i qiankun -S

目前乾坤是2.0版本 安装后package.json 是2.72版本

image.png

在安装 element-plus 把项目的布局简单做一下

npm install element-plus --save

注意: vue3 安装element-plus, vue2安装element-ui

src下新建micro-app.js 用于存放所有子应用
const microApps = [
  {
    name: 'micro-react', //应用名 项目名最好也是这个
    entry: '//localhost:20000', //默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)内部用的fetch
    activeRule: '/react', // 激活的路径
    container: '#micro-react', // 容器名
    props: {}, //父子应用通信
  },
  {
    name: 'micro-vue2',
    entry: '//localhost:30000',
    activeRule: '/vue2',
    container: '#micro-vue2',
    props: {},
  },
  {
    name: 'micro-vue3',
    entry: '//localhost:40000',
    activeRule: '/vue3',
    container: '#micro-vue3',
    props: {},
  },
];

export default microApps;
新建vue.config.js
module.exports = {
  devServer: {
    port: 8000,
    headers: {
      // 重点1: 允许跨域访问子应用页面
      'Access-Control-Allow-Origin': '*',
    },
  },
};
Main页面
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// createApp(App).use(store).use(router).mount('#app')

//-----------------------上面是原先的,下面是新增的-----------------------------

import ElementPlus from 'element-plus'; //element-plus
import 'element-plus/dist/index.css'; //element-plus
import { registerMicroApps, start } from 'qiankun';
import microApps from './micro-app';

let app = createApp(App);
app.use(store);
app.use(router);
app.use(ElementPlus);
app.mount('#app');

registerMicroApps(microApps, {
  //还有一些生命周期 如果需要可以根据官网文档按需加入
  beforeMount(app) {
    console.log('挂载前', app);
  },
  afterMount(app) {
    console.log('卸载后', app);
  },
});

start({
  prefetch: false, //取消预加载
  sandbox: { experimentalStyleIsolation: true }, //沙盒模式
}); 
进入App页面简单调下布局
<template>
  <el-menu :router="true" mode="horizontal">
    <!-- 子应用的跳转路径 -->
    <el-menu-item index="/">主应用 main</el-menu-item>
    <el-menu-item index="/react">子应用 react18</el-menu-item>
    <el-menu-item index="/vue2">子应用 vue2</el-menu-item>
    <el-menu-item index="/vue3">子应用 vue3</el-menu-item>
    <el-menu-item index="/about">{{ $store.state.GlobalData }}</el-menu-item>
  </el-menu>
  <router-view />
  <!-- 子应用的容器 -->

  <div id="micro-react"></div>
  <div id="micro-vue2"></div>
  <div id="micro-vue3"></div>
</template>

<script>
export default {
  name: 'App',
  setup() {
    return {};
  },
};
</script>

<style lang="less">
html,
body,
#app {
  width: 100%;
  height: 100%;
  margin: 0;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}
</style>

需要注意: app里的容器名和跳转路径都不是随便起的 需要和micro-app.js 定义好的子应用一一对应

image.png

到此主应用搭建完毕~~~ovo

四.子应用

1.react

安装npm install react-app-rewired 重写默认的react配置文件
npm install react-app-rewired --save

修改package.json,原本的react-script 改为react-app-rewired

"scripts": {
   "start": "react-app-rewired start",
   "build": "react-app-rewired build",
   "test": "react-app-rewired test",
   "eject": "react-app-rewired eject"
 },
安装npm i react-router-dom 我安装的是最新版本 "react-router-dom": "^6.3.0"
npm i react-router-dom --save
根目录下新建.env文件
PORT=20000
#  防止热更新出错
WDS_SOCKET_PORT=20000 
src下新建public-path.js (用于修改运行时的 publicPath)
//判断是否是qiankun加载
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
src下新建 config-overrides.js
const { name } = require('./package');

module.exports = {
  webpack: config => {
    config.output.library = `${name}-[name]`; 
    config.output.libraryTarget = 'umd';
    config.output.globalObject = 'window';
    return config;
  },
  devServer: _ => {
    const config = _;

    config.headers = {
      'Access-Control-Allow-Origin': '*',
    };
    config.historyApiFallback = true;
    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;

    return config;
  },
};

进入src下index.js
// import logo from './logo.svg';
// import './App.css';

// function App() {
//   return (
//     <div className="App">
//       <header className="App-header">
//         <img src={logo} className="App-logo" alt="logo" />
//         <p>
//           Edit <code>src/App.js</code> and save to reload.
//         </p>
//         <a
//           className="App-link"
//           href="https://reactjs.org"
//           target="_blank"
//           rel="noopener noreferrer"
//         >
//           Learn React
//         </a>
//       </header>
//     </div>
//   );
// }

// export default App;

// ------------------------上面原先的,下面最新的------------------------------------

import logo from './logo.svg';
import './App.css';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

function App() {
  return (
    <>
      {/* basename 判断如果是qiankun加载 basename为react 相当于加个标识*/}
      <Router basename={window.__POWERED_BY_QIANKUN__ ? '/react' : '/'}>
        {/*  */}
        <Link to="/">首页</Link>
        <Link to="/about">关于页面</Link>
        <Routes>
          <Route path="/" element={<Home />}></Route>
          <Route path="/about" element={<About />}></Route>
        </Routes>
      </Router>
    </>
  );
}

function About() {
  return <div>about</div>;
}

function Home() {
  return (
    <div>
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
          Learn React
        </a>
      </header>
    </div>
  );
}
export default App;

2.vue2

src下新建public-path.js 用于修改运行时的 publicPath
// eslint-disable-next-line no-undef
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
在main页面 引入public-path.js文件
// import Vue from 'vue';
// import App from './App.vue';
// import router from './router';

// Vue.config.productionTip = false

// new Vue({
//   router,
//   render: h => h(App)
// }).$mount('#app')

// ·················上面原先的 下面新增的·····················
import './public-path';
import Vue from 'vue';
import App from './App.vue';
import router from './router';

// Vue.config.productionTip = false

let instance = null;
function render(props = {}) {
  const { container } = props;
  instance = new Vue({
    router,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// 如何独立运行微应用?
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap(props) {
  // 启动
}
export async function mount(props) {
  // 挂载  onGlobalStateChange 可通过这个属性来进行父子应用通信 发布订阅机制
  render(props);
}
export async function unmount(props) {
  // 卸载
  instance.$destroy();
}

新增vue.config.js文件
const { name } = require('./package');

module.exports = {
  devServer: {
    port: 30000,
    headers: {
      'Access-Control-Allow-Origin': '*', //开发时增加跨域 表示所有人都可以访问我的服务器
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把子应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};
router.js文件
const router = new VueRouter({
  mode: 'history',
  // base: process.env.BASE_URL,
  base: '/vue2',
  routes,
});

3.vue3

src下新建public-path.js 用于修改运行时的 publicPath
// eslint-disable-next-line no-undef
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
在main页面 引入public-path.js文件
import './public-path'; // 注意需要引入public-path
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';

let instance = null;

function render({ container } = {}) {
  instance = createApp(App);
  instance.use(router);
  instance.use(store);
  instance.mount(container ? container.querySelector('#app') : '#app');
}


// 如何独立运行微应用?
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap(props) {
  // 启动
}
export async function mount(props) {
  // 挂载
  render(props);
}
export async function unmount(props) {
  // 卸载
  instance.unmount();
  instance = null;
}
新增vue.config.js文件
const { name } = require('./package');

module.exports = {
  devServer: {
    port: 40000,
    headers: {
      'Access-Control-Allow-Origin': '*', //开发时增加跨域 表示所有人都可以访问我的服务器
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把子应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

到这里项目搭建完毕,基础跳转没有问题 ,可以在主应用和子应用跳转
bug :主应用和子应用使用不同版本的vue后路由切换报错 ?
bug :主应用样式与子应用样式冲突 ?
需求 :父子组件传参如何实现 ?
需求 :如何部署 ?
别担心 下面我一一解答

5.bug

[Bug]主应用和子应用使用不同版本的vue后路由切换报错

问题的原因 : vue-router 3.x与vue-router 4.x设置的history.state的数据结构不同
低版本的 vue-router 在 pushState 的时候,会覆盖丢失主路由的 history.state,导致主路由跳转异常
解决办法 : 主应用监听router.beforEach 手动修改history.state数据结构

import _ from "lodash"

router.beforeEach((to, from, next) => {
  if (_.isEmpty(history.state.current)) {
    _.assign(history.state, { current: from.fullPath });
  }
  next();
});
[Bug]主应用样式与子应用样式冲突

可以通过给css样式名加前缀来实现隔离
https://blog.csdn.net/zjscy666/article/details/107864891
https://blog.csdn.net/m0_54854484/article/details/123442168

6.需求

[需求] 父子组件传参如何实现

qiankun通过initGlobalState, onGlobalStateChange, setGlobalState实现主应用的全局状态管理,然后默认会通过props将通信方法传递给子应用。先看下官方的示例用法:

主应用

// main/src/main.js
import { initGlobalState } from 'qiankun';
// 初始化 state
const initialState = {
  user: {} // 用户信息
};
const actions = initGlobalState(initialState);
actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();

子应用

// 从生命周期 mount 中获取通信方法,props默认会有onGlobalStateChange和setGlobalState两个api
export function mount(props) {
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);
  });
  props.setGlobalState(state);
}

这两段代码不难理解,父子应用通过onGlobalStateChange这个方法进行通信,这其实是一个发布-订阅的设计模式。
ok,官方的示例用法很简单也完全够用,纯JavaScript的语法,不涉及任何的vue或react的东西,开发者可自由定制。

如果我们直接使用官方的这个示例,那么数据会比较松散且调用复杂,所有子应用都得声明onGlobalStateChange对状态进行监听,再通过setGlobalState进行更新数据。

因此,我们很有必要对数据状态做进一步的封装设计

主应用src下新建actions.js
//src/actions.js
// 父子应用通信
import { initGlobalState } from 'qiankun';
import store from './store';

const state = {
  //这里写初始化数据
  name: 'wang',
  age: 123,
  count: 0,
};

const actions = initGlobalState(state);

actions.onGlobalStateChange((state, prev) => {
  console.log('主应用变更前:', state);
  console.log('主应用变更后:', prev);
  store.commit('setGlobalData', state);
});

store.commit('setGlobalData', state);

export default actions;

将初始化的数据存到vuex中 如果数据变更了 在将变更后的数据存到vuex

主应用main store文件夹下index.js中
//store/index.js 
import { createStore } from 'vuex';

export default createStore({
  state: {
    GlobalData: {},
  },
  mutations: {
    setGlobalData(state, value) {
      state.GlobalData = value;
    },
  },
  actions: {},
  modules: {},
});

最后在main.js 中导入

//main.js
import './actions.js'
子应用 (vue3)

核心:通过将主应用的onGlobalStateChange,setGlobalState方法挂载到全局就可以使用了

import './public-path'; // 注意需要引入public-path
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';

let instance = null;

//核心
function render(props) {
  const { container, onGlobalStateChange, setGlobalState } = props;
  console.log(props);
  instance = createApp(App);
  instance.config.globalProperties.$onGlobalStateChange = onGlobalStateChange;
  instance.config.globalProperties.$setGlobalState = setGlobalState;
  instance.use(router);
  instance.use(store);
  instance.mount(container ? container.querySelector('#app') : '#app');
}

// 如何独立运行微应用?
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap(props) {
  // 启动
}
export async function mount(props) {
  // 挂载
  render(props);
}
export async function unmount(props) {
  // 卸载
  instance.unmount();
  instance = null;
}
使用

主应用

<template>
  <div>
    <h1>主应用/vue3子应用 的全局数据</h1>
    <div>姓名 : {{ $store.state.GlobalData.name }}</div>
    <div>年龄 : {{ $store.state.GlobalData.age }}</div>
    <div>数量 : {{ $store.state.GlobalData.count }}</div>

    <el-button type="primary" @click="revampData">修改全局数据</el-button>
  </div>
</template>

<script setup>
import actions from '../actions';
const revampData = () => {
  actions.setGlobalState({ name: '主应用' });
};
</script>

子应用(vue3)

<template>
    <div>我是vue3项目</div>
    <button @click="revampData">修改全局数据</button>
</template>

<script>
import { getCurrentInstance } from 'vue';
export default {
  name: 'Home',
  components: {
    HelloWorld,
  },
  setup() {
    const { proxy } = getCurrentInstance();
    const revampData = () => {
      proxy.$setGlobalState({ name: 'vue3子应用应用' });
    };

    return {
      revampData,
    };
  },
};
</script>

[需求] 如何部署

qiankun部署的帖子网上根本找不到, 可能是感觉简单就都不想说了吧,笔者这里也是部署了很多遍才跑通,这里说下我的思路。

考虑到主应用和子应用共用域名时可能会存在路由冲突的问题,子应用可能会源源不断地添加进来,因此我们将子应用都放在xx.com/subapp/这个二级目录下,根路径/留给主应用。

步骤如下:
1.主应用main和所有子应用都打包出一份html,css,js,static,分目录上传到服务器,子应用统一放到subapp目录下,最终如:

├── main
│   └── index.html
└── subapp
    ├── sub-react
    │   └── index.html
    └── sub-vue
        └── index.html

2.配置nginx,预期是xx.com根路径指向主应用,xx.com/subapp指向子应用,子应用的配置只需写一份,以后新增子应用也不需要改nginx配置,以下应该是微应用部署的最简洁的一份nginx配置了。

    server{
        listen 80;                                            #侦听端口
        server_name http://wzs.bengdada.com/;                 #定义使用www.xx.com访问
        charset utf-8;

        location / {
            root /data/wzs/main;  # 主应用所在的目录
            try_files $uri $uri/ /index.html;
        }
        location /subapp {
            alias /data/wzs/subapp;  # 主应用所在的目录
            try_files $uri $uri/ /index.html;
        }
    }
nginx -s reload后就可以了。

本文特地做了线上demo展示:
整站(主应用):wzs.bengdada.com/
单独访问子应用:

最后

本人从开始弄微前端反复查阅大量资料和视频,踩过很多坑,忍不住感叹 : 真是学无止境.....
最后的最后,喜欢本文的同学还请能顺手给个赞鼓励一下,非常感谢看到这里。

有关微前端解决方案-qiankun实战及部署的更多相关文章

  1. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  2. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

  3. ruby-on-rails - Ruby on Rails 可以部署在 Azure 网站上吗? - 2

    我可以在Azure网站上部署RubyonRails吗? 最佳答案 还没有。目前仅支持.NET和PHP。 关于ruby-on-rails-RubyonRails可以部署在Azure网站上吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/12964010/

  4. 屏幕录制为什么没声音?检查这2项,轻松解决 - 2

    相信很多人在录制视频的时候都会遇到各种各样的问题,比如录制的视频没有声音。屏幕录制为什么没声音?今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音,你可以试试这个方法。 一、检查是否打开电脑系统声音相信很多小伙伴在录制视频后会发现录制的视频没有声音,屏幕录制为什么没声音?如果当时没有打开音频录制,则录制好的视频是没有声音的。因此,建议在录制前进行检查。屏幕上没有声音,很可能是因为你的电脑系统的声音被禁止了。您只需打开电脑系统的声音,即可录制音频和图画同步视频。操作方法:步骤1:点击电脑屏幕右下侧的“小喇叭”图案,在上方的选项中,选择“声音”。 步骤2:在“声

  5. 【高数】用拉格朗日中值定理解决极限问题 - 2

    首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有,  也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加

  6. jenkins部署1--jenkins+gitee持续集成 - 2

    前置步骤我们都操作完了,这篇开始介绍jenkins的集成。话不多说,看操作1、登录进入jenkins后会让你选择安装插件,选择第一个默认的就行。安装完成后设置账号密码,重新登录。2、配置JDK和Git都需要执行路径,所以需要先把执行路径找到,先进入服务器的docker容器,2.1JDK的路径root@69eef9ee86cf:/usr/bin#echo$JAVA_HOME/usr/local/openjdk-82.2Git的路径root@69eef9ee86cf:/#whichgit/usr/bin/git3、先配置JDK和Git。点击:ManageJenkins>>GlobalToolCon

  7. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  8. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  9. Ruby,使用包含 TK GUI 的 ocra 部署一个 exe - 2

    Ocra无法处理需要“tk”的应用程序require'tk'puts'nope'用奥克拉http://github.com/larsch/ocra不起作用(如链接中的一个问题所述)问题:https://github.com/larsch/ocra/issues/29(Ocra是1.9的"new"rubyscript2exe,本质上它用于将rb脚本部署为可执行文件)唯一的问题似乎是缺少tcl的DLL文件我不认为这是一个问题据我所知,问题是缺少tk的DLL文件如果它们是已知的,则可以在执行ocra时将它们包括在内有没有办法知道tk工作所需的DLL依赖项? 最佳答

  10. Ruby 守护进程和 JRuby - 备选方案 - 2

    我有一个应用程序正在从Ruby迁移到JRuby(由于需要通过Java提供更好的Web服务安全支持)。我使用的gem之一是daemons创建后台作业。问题在于它使用fork+exec来创建后台进程,但这对JRuby来说是禁忌。那么-是否有用于创建后台作业的替代gem/wrapper?我目前的想法是只从shell脚本调用rake并让rake任务永远运行......提前致谢,克里斯。更新我们目前正在使用几个与Java线程相关的包装器,即https://github.com/jmettraux/rufus-scheduler和https://github.com/philostler/acts

随机推荐