(注:本文以vue-cli 2.x为例)
需求的产生
起初,我们的项目还只是简单的一个脚手架创建的简单工程,直到有一天,公司决定在现有基础上再出一个 版本,现有内容不变,两个版本并存,后期有可能还会出现更多版本,作为前端,我们怎么做最好呢,
大佬0:开个新的分支做吧,
大佬1:但是现在这个工程,已经做了大量的打包配置,并且有很多可复用的组件和封装好的类,如果有一天这些内容要做更改呢,我总不能没一个分支都去修改吧,
大佬2:我们可以传到公司的git上,公共内容改变时只要重新pull一下就行了,
大佬n:...
小王子:我们可以像分布式那样垂直拆分一下我们的项目吗?提取公共部分,然后分模块进行编译,
众人:...
emmmmm,说的怪好,怎么实现呢,
webpack运行过程
首先看一下默认的文件结构
一个工程,默认有一个打包入口,build文件夹下存放着webpack的打包配置,包括开发环境和生产环境。 当我们执行npm run dev(或build)时,webpack会读取build文件夹下对应的配置文件, 找到打包的入口文件(main.js)和模板文件(index.html),然后通过我们配置的loader对不同类型文件进行解析或编译,最后生成不同浏览器都可识别的js和index.html。文件结构变化
既然我们要用多入口打包,当然就不能再用一套入口文件(main.js)和模板文件(index.html)了,我们在不同的模块下分别创建他们。当我们对模块A打包, 他就自动去访问模块A的main.js和index.html; 对模块B打包,他就自动去访问模块B的main.js和index.html。 这时候我们的文件结构就要变成这样子,
注意这里的数据仓库,尽管我们现在有了多个的模块,但是他们仍然使用一套vuex即可,所以,这里我们可以把store放在最外面,然后在每一个模块下,让他们按需引入就好了。实现
1.yargs:接收命令行参数
安装
npm install yargs --D
在命令后面添加参数
node build/build.js --env module1
接收参数
const yargs = require('yargs')
const module_fileName = yargs.argv.env
执行一下发现读取到对应的参数了,我们再进行下一步, 我们可以把接收参数的方法提取到公共部分, 因为生产环境和开发环境对应的打包文件都要使用他,我们直接在使用的地方调用方法取值即可了,
在build/utils.js文件中添加以下代码
+ const yargs = require('yargs')
+ const fileName = yargs.argv.env
+ // 自定义入口main.js
+ exports.entry = function () {
+ return {
+ app: './src/modules/' + fileName + '/main.js'
+ }
+ }
+ // 自定义入口html文件
+ exports.template = function () {
+ return './src/modules/' + fileName + '/index.html'
+ }
2.动态读取main.js和index.html
2.1.main.js
作为入口文件,生产环境和开发环境都要从这里进入,属于基础配置,所以我们找到webpack.base.conf.js
在build/webpack.base.conf.js文件中修改entry
entry: utils.entry(),
2.2.index.html
模板文件的使用就相对复杂点了,同样生产环境和开发环境都需要他,而且我们的热更新也要用他
-
首先修改开发环境
在build/webpack.dev.conf.js修改devServer.historyApiFallback.rewrites devServer: { historyApiFallback: { rewrites: [ {from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, utils.template())}, ], } ... } 在build/webpack.dev.conf.js修改plugins plugins: [ new HtmlWebpackPlugin({ filename: utils.template(), template: utils.template(), inject: true }), ... ]
-
接着修改生产环境
在build/webpack.dev.conf.js修改plugins plugins: [ new HtmlWebpackPlugin({ template: utils.template(), ... }) ]
-
最后测试一下,我们分别执行两个命令
npm run dev:module1 npm run dev:module2 等到编译通过后,发现webpack启动了两个服务器, 分别是module1和module2的项目, 这就表示我们的修改生效了
3.模块化 数据仓库
3.1 官网提供的方案
关于数据仓库的模块化,vuex本身提供了方案
Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割 vuex.vuejs.org/zh/guide/mo…
官方推荐了modules,同时我们配合registerModule,可以实现数据仓库的按需引入,是个很好的优化方案。
但是我们的项目在搭建的时候没有考虑到现在这种情况。采取该方案,意味着我们要修改每一个使用了vuex的组件,工程量比较大,如果很不幸,你的情况像我们一样,或许可以考虑下面这种方案。
3.2 文件合并方案(require + Object.assign)
文章开头我们已经展示的文件的结构,数据仓库放在项目根目录, module1和module2只要按需引入数据仓库的模块即可 这里我们这样做,
首先看下根目录下的数据仓库
这是每个模块的内容我们在module1中按需引入
src/module1/store/config.js
export [
'common',
'alarm',
'params_3dCity',
'params_sky',
'queue_message',
'user_city'
]
这里字符串的内容就是文件名,有了他,我们可以动态地将文件require进来,然后使用Object.assign方法将对象合并,最后export出一个实例化的Vuex.Store对象,main.js直接使用即可。
代码如下
src/module1/store/config.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import config from './config'
const state = {};
const mutations = {};
const actions = {};
for(let fileName of config) {
const module = require('@/store/' + fileName);
Object.assign(state, state, module.default.state);
Object.assign(actions, actions, module.default.actions);
Object.assign(mutations, mutations, module.default.mutations);
}
export default new Vuex.Store({
state,
mutations,
actions,
})
4.配置命令,按需编译
在package.json中添加如下命令
+ "dev:module1": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --env module1",
+ "dev:module2": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --env module2",
+ "build:module1": "node build/build.js --env module1",
+ "build:module2": "node build/build.js --env module2",
总结
- 多入口打包对生产环境与开发环境都有要求,所以入口(entry)的内容要根据参数改变
- 尽可能提取公共部分,包括分装的公用类,util函数,公共组件等等
- 减少公共部分与每个模块的耦合度,尽量不要在公共部分写业务,如有需要,只需在模块中extends公共组件,然后在模块中编写独有的内容即可
- 数据仓库根据需求决定,官方推荐的方案可以配合registerModule实现按需加载,本文的方案只是演示了一种解决方案,如果工作量不是很大的情况下,还是推荐使用官方的方案。