- 将子项目打包 UMD 模块,教程
- 在主项目中加载子项目的入口文件
- 使用 vue-router 的
router.addRoutes
将子项目的路由动态注册到主项目中 - 使用 Vuex 的
store.registerModule
将子项目的store module
动态注册到主项目中
app-entry/vue.config.js
const webpack = require('webpack');
const APP_NAME = require('./package.json').name;
const PORT = require('./package.json').devPort; // 开发模式下项目的启动端口
const PROXY = require('./config/proxy'); // 开发模式下的 proxy 配置
module.exports = {
baseUrl: './',
configureWebpack: {
// 提取公共依赖
externals: {
vue: 'Vue',
'element-ui': 'ELEMENT',
},
plugins: [
// 定义全局变量
new webpack.DefinePlugin({
'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME),
}),
],
},
devServer: {
port: PORT,
proxy: PROXY,
},
};
app-entry/public/index.html
<head>
<link href="path/to/element-ui/index.css" rel="stylesheet" />
</head>
<body>
<script src="path/to/vue.min.js"></script>
<script src="path/to//element-ui/index.js"></script>
</body>
app-entry/config/proxy.js
module.exports = {
'/app-typescript/': {
target: 'http://localhost:10241/', // 指向 app-typescript 开发服务
},
'app-javascript/': {
target: 'http://localhost:10242/', // 指向 app-javascript 开发服务
},
};
app-entry/src/main.js
import router from './router'; // router 实例
import store from './store'; // store 实例
// 挂载主项目的 store 和 router 实例
Reflect.defineProperty(Vue, '__share_pool__', {
value: {
store,
router,
},
});
app-entry/src/router.js
import { loadModule } from './load-helper';
import { modules } from './modules';
router.beforeEach(async (to, from, next) => {
const [, module] = to.path.split('/');
if (Reflect.has(modules, module)) {
loadModule(modules[module]);
Reflect.deleteProperty(modules, module);
}
next();
});
app-entry/src/modules.js
export const modules = {
'app-typescript': './app-typescript/main.js',
'app-javascript': './app-javascript/main.js',
};
app-entry/src/load-helper.js
export function loadModule(url) {
return new Promise((resolve) => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.onload = ({ type }) => resolve({ status: type, url });
script.onerror = ({ type }) => resolve({ status: type, url });
script.src = url;
document.body.appendChild(script);
});
}
以 app-javascript 为例
yarn add mfv-cli-service -D
app-javascript/package.json
{
"scripts": {
"build": "mfv-cli-service build --report --target lib --formats umd-min ./src/main.js"
}
}
这样可以将 app-javascript 构建成一个 UMD 文件,然后在 app-entry 中引用,参考
app-javascript/vue.config.js
const webpack = require('webpack');
const APP_NAME = require('./package.json').name;
const PORT = require('./package.json').devPort; // 开发模式下项目的启动端口
module.exports = {
publicPath: `/${APP_NAME}/`, // 必须为绝对路径;配合 app-entry 中的 proxy 配置,配合生产环境下的 Nginx 配置
configureWebpack: {
// 提取公共依赖
externals: {
vue: 'Vue',
'element-ui': 'ELEMENT',
},
entry: './src/main.js',
output: {
libraryExport: 'default', // https://cli.vuejs.org/zh/guide/build-targets.html#应用
jsonpFunction: `webpackJsonp-${APP_NAME}`, // 解决默认情况下子项目 chunkname 冲突的问题
},
plugins: [
// 定义全局变量,项目中会使用
new webpack.DefinePlugin({
'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME),
}),
],
},
devServer: {
port: PORT,
},
};
app-javascript/src/main.js
import Vue from 'vue';
import routes from './routes';
// 动态添加子项目的 route-list
Vue.__share_pool__.router.addRoutes(routes);
app-javascript/src/routes.js
/* routes-list */
const APP_NAME = process.env.VUE_APP_NAME;
const App = () => import('./App.vue');
const Home = () => import('./views/Home.vue');
const About = () => import('./views/About.vue');
export default [
{
path: `/${APP_NAME}`,
name: APP_NAME,
redirect: { name: `${APP_NAME}.home` },
component: App,
children: [
{
path: 'home',
name: `${APP_NAME}.home`,
component: Home,
},
{
path: 'about',
name: `${APP_NAME}.about`,
component: About,
},
],
},
];
app-javascript/src/base.js
import Vue from 'vue';
import store from './store';
// 子项目异步注册 store module
Vue.__share_pool__.store.registerModule(process.env.VUE_APP_NAME, store);
export default null;
app-javascript/src/store.js
/* store module */
export default {
namespaced: true, // namespaced must be true in module app.
state: {
name: process.env.VUE_APP_NAME,
},
mutations: {},
actions: {},
};
- 方式一:通过 CI 合并主项目和子项目的 dist 目录,然后部署
- 方式二:将子项目当成单独服务对待,独立部署,然后通过 Nginx 反向代理合并主项目和子项目
由于子项目的路由是异步加载 / 注册的,导致子项目无法使用 meta
判断路由是否需要鉴权
优化方向: 在主项目实例化之前加载子项目的路由,参考:预加载子项目入口文件
推荐在服务端为子项目入口文件添加协商缓存。