github地址
https://github.com/shuidian/v...
为了充分发扬拿来主义的原则,先放出github地址,clone下来即可测试运行效果。如果觉得还可以的话,请点star,为更多人提供方便。
背景
在实际的开发过程中,单页应用并不能满足所有的场景。传统单页应用所生成的成果物,在单个系统功能拆分和多个系统灵活组装时并不方便。举个例子,我们在A系统中开发了一个实时视频预览模块和一个gis模块,传统方式打包A系统,我们会生成一个静态资源包,这个包,包含了实时视频预览模块和gis模块在内的所有模块。那么,现在有一个新的系统要做,碰巧需要原来A系统中的gis模块,那么我们只能把A系统打包,然后拿过来,通过引入url的方式,只使用其中的一个模块。这样做的问题很明显,我只需要A系统中的一个模块,但是我要引入整个A系统的资源包,这显然不合理。那么我们的需求就是从这里产生的,如果我们在开发系统A时,能够按模块划分生成多份静态资源包,最终的成果物中,会有多个子目录,每个子目录可独立运行,完成一个业务功能。这样的话,我们有任何系统需要我们开发过的任何模块,都可以直接打包指定的模块,灵活组装。
场景分析
首先,这种方案不是完全适合任何场景的,在使用时,还需要注意鉴别是否适用于当前业务场景。下面分析一下这种方式的优缺点:
优点:
1、可与其他系统灵活组装
2、各个模块相互不受影响,所以不受框架和开发模式的制约
3、不同模块可以分开部署
4、后期维护风险小,可以持续的、稳定的进行维护(万一哪天vue/react/angular被淘汰了,不会受太大影响,每个模块分别迭代就好)
缺点:
1、各个模块有相互独立的资源包,那么如果有相同的资源引用,不能复用
2、模块的组装要依赖iframe,所以要对浏览器安全设置、cookie共享等问题进行单独处理
3、用iframe来包裹组件,组件所能控制到的范围就是其所在的iframe,当涉及到全屏的应用场景时,会比较麻烦
4、不同组件之间的通信比较麻烦
以上只是分析应用场景,下面重点讲解一下如何实现多模块独立打包。
我们的目标
vue-cli默认打包方式的成果物与我们修改后生成的成果物结构对比如下:
上图为默认配置下,打包成果物的目录结构,下图为我们修改配置后,打包成果物的目录结构
思路分析
我们最终输出的成果物是多个独立的目录,那么我们就应该区分这些模块,最好的方式就是每个模块的代码放在不同的目录中,所以我们需要在src中创建每个模块的目录。暂时我们以a,b,c三个模块为例,由于我们现在的项目是多模块的,每个模块都应该有独立的入口,所以我们修改src目录结构如下:
其他目录你怎么命名,以及要不要都无所谓,主要是modules目录,我们会在下面创建若干个模块。原来的src下的main.js、index.html和app.vue已经没用了,可以删掉了。然后模块内的目录结构如下图所示:
聪明的同学已经看出来了,这里其实就是跟原来的src下的main.js、index.html和app.vue一样的,只不过我们把main.js改成了index.js而已。那么如果模块内要使用路由、状态管理都可以根据自己的需求去配置了,如何配置就不在这里讨论了。
那么如何从这些模块开始,把项目最终编译成三个独立的静态资源包呢?简单来说,其实就是循环跑三次打包脚本,每次打包一个模块,然后修改一下文件输出路径,把编译好的文件输出到dist目录下的a,b,c目录中。这样最基本的模块分开打包功能就完成了,但是还有以下一些问题需要处理。
1、这样打出的包,各个模块彼此独立。如果有这些模块是在一个系统中使用的,那么应该把多个模块重复的东西抽取出来复用。
2、如果只需要系统中的部分模块,那么应该只打包需要的模块,并且把需要打包的模块之间的重复代码抽取出来复用。
问题解决方案
针对第一个问题:实质上只要把webpack配置成多入口的方式即可,这样在编译时webpack可以把模块之间的重复代码抽取出来,最终的成果物就是一个静态资源包加多个html文件。这种方式的成果物目录结构如下:
针对第二个问题:其实跟第一个问题一样,只不过把webpack的入口配置成可变的就可以了,需要打包哪些模块,就把入口设置为哪些模块即可。这种方式的成果物目录如下(假设要打包a,c两个模块):
修改webpack配置的详细步骤
第一步:增加build/module-conf.js用来处理获取模块目录等问题
var chalk = require('chalk')
var glob = require('glob')
// 获取所有的moduleList
var moduleList = []
var moduleSrcArray = glob.sync('./src/modules/*')
for(var x in moduleSrcArray){
moduleList.push(moduleSrcArray[x].split('/')[3])
}
// 检测是否在输入的参数是否在允许的list中
var checkModule = function () {
var module = process.env.MODULE_ENV
// 检查moduleList是否有重复
var hash = {}
var repeatList = []
for(var l = 0;l < moduleList.length; l++){
if(hash[moduleList[l]]){
repeatList.push(moduleList[l])
}
hash[moduleList[l]] = true
}
if(repeatList.length > 0){
console.log(chalk.red('moduleList 有重复:'))
console.log(chalk.red(repeatList.toString()))
return false
}
let result = true
let illegalParam = ''
for (let moduleToBuild of module.split(',')) {
if (moduleList.indexOf(moduleToBuild) === -1) {
result = false
illegalParam = moduleToBuild
break
}
}
if(result === false){
console.log(chalk.red('参数错误,允许的参数为:'))
console.log(chalk.green(moduleList.toString()))
console.log(chalk.yellow(`非法参数:${illegalParam}`))
}
return result
}
// 获取当前要打包的模块列表
function getModuleToBuild () {
let moduleToBuild = []
if (process.env.NODE_ENV === 'production') {
/* 部署态,构建要打包的模块列表,如果指定了要打包的模块,那么按照指定的模块配置入口
* 这里有个特性