为什么要拆模块,因为本地启动,ci跑构建已经太慢了。
针对上图我们已经实现了一个业务组件库(想知道怎么做看这里呀),进行组件复用降低了一定的代码量,为了进一步提升速度所以我们需要做模块分离。
技术选择
问题根本:选定方案之前我们需要确定问题在哪,通过webpack的时间分析我们得出是*.vue 文件过于多处理时间过长,所以我们需要进行项目拆分,独立开发独立部署。
技术分析: 微前端?因为受限于技术栈(vue2.x)以及项目模块之间的互相调用我们无法很好的转向比较火的qiankun。这样子我们为数不多的方法似乎只剩下模块代码拆分,独立部署非独立运行。而webpack的Module Federation也就进入我们的考虑之中。
技术栈:emp + vue全家桶
开始实践
为了解决使用同一个vue实例所以需要在主项目中以script标签引入维持同一份。主项目和模块项目打包时 external掉即可。
主项目index.html
<body>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.22/vue<%= process.env.NODE_ENV === 'development' ? '' : '.min' %>.js"></script>
<div id="emp-root"></div>
</body>
主项目emp-config.js
const withVue2 = require('@efox/emp-vue2')
const { VUE_APP_ROUTES, VUE_APP_REDIRECT, VUE_APP_REDIRECT_NAME } = process.env;
module.exports = withVue2(({ config, env, empEnv,webpack }) => {
...
config.externals({
vue: 'Vue'
})
...
模块项目emp-config.js
module.exports = ({ config, env, empEnv }) => {
...
config.externals({
vue: 'Vue'
})
...
打出来的包引入时需要解决的问题 (vuex 和 vue-router)
因为上面已经解决了用同一个vue的问题所以这里我们只需要引入文件挂载上去全局vue属性上即可。
模块项目暴露文件 emp-config.js
config.plugin('mf').tap(args => {
args[0] = {
...args[0],
...{
// 项目名称
name: projectName,
// 暴露项目的全局变量名
library: { type: 'var', name: projectName },
// 被远程引入的文件名
filename: 'emp.js',
remotes: {
// 远程项目别名:远程引入的项目名
// vue2Components: 'vue2Components',
},
// 需要暴露的东西
exposes: {
'./main': 'src/main', // 模块的routes挂载文件
'./store': 'src/store', //该模块的vuex
},
// // 需要共享的依赖
shared: [],
},
}
return args
})
src/main
import Vue from 'vue';
const APP_NAME = 'test';
const App = () => import('./views/App.vue');
const Home = () => import('./views/Home.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,
},
],
},
];
const sharePool = (Vue.__share_pool__ = Vue.__share_pool__ || {});
const routesPool = (sharePool.routes = sharePool.routes || {}); // 挂载到全局主项目就可以读到来用了
// 挂载子项目的 route-list
routesPool['test'] = routes;
export default routesPool
src/store
import Vue from 'vue'
const store = {
namespaced: true,
state: {
name: 'momommmomomoomom',
},
mutations: {},
actions: {},
}
Vue.__share_pool__.store.registerModule('test', store); // 这里就反过来了需要在主项目已有store后再注册
到这一步我们已经把工作完成的七七八八了,只需要在主项目引入暴露的文件注意文件加载顺序即可。
主项目 emp-config.js
module.exports = withVue2(({ config, env, empEnv,webpack }) => {
...
config.plugin('mf').tap(args => {
args[0] = {
...args[0],
...{
// 项目名称
name: projectName,
// 被远程引入的文件名
filename: 'emp.js',
remotes: {
// 远程项目别名:远程引入的项目名
thirdParty: 'thirdParty@http://localhost:8006/emp.js'
},
// 需要暴露的东西
exposes: {
},
// 需要共享的依赖
},
}
return args
})
})
....
主项目 main.js
(Vue.__share_pool__ = Vue.__share_pool__ || {}).store = store; 需要主项目先初始化vuex实例
require("thirdParty/store") 模块项目注册进来
主项目 routes.js
import VueRouter from 'vue-router';
import routesPool from "thirdParty/main" 先引入模块项目的路由再进行初始化
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes: routesPool['test']
});
总结
优点:
- 基于module federation封装的emp-cli,容易使用替换已有的vue-cli,配置页面基本都是webpack配置项没有学习负担。
- 模块独立部署,原有代码拆分简单干净,且能用上splitchunks异步加载(webpack能力大部分使用上)。
- 拆分成本低,可以降低到一部分路由为模块拆分。
缺点:
- 模块独立部署,不能独立运行。
- 不能多技术栈并存,非微前端。
如果你也有拆分项目的需求且都是vue的话似乎这也是个不错的解决方案,有更好的不妨下方留言,或者觉得笔者有啥不对的地方也可以提出。