Vue Cli 构建多页应用
背景
大多数的应用场景为单页面,但是到目前我遇到两种需要构建多页面应用的场景:
- 业务需要打包出不同模块,模块之间各自独立,支持独立部署;
- 项目过大,为提升性能分模块构建
两种场景的过程基本一致,下面以一个demo进行演示,其中会涉及到一些webpack配置相关和脚手架知识。
构建目标
确定大方向后需要细致化目标,拆解需求:
- [ ] 区分模块,配置多入口打包
- [ ] 可定制化哪些模块需要被打包
- [ ] 抽离公共资源
- [ ] 排查性能问题
构建过程
创建项目
创建vue-mpa项目并创建模块目录:
vue create vue-mpa* vue-mpa/
* src/
* modules/
* module1
* app.js
* app.vue
* module2
* app.js
* app.vue
* app.vue
* main.js
* README.md
* package.json创建了两个子模块module1和module2,模块中的app.js挂载同目录下的app.vue文件。
js
import Vue from 'vue'
import App from './app.vue'
new Vue({
el: '#app',
render: h => h(App)
})app.vue的内容可以自定义。
确定打包结构
- dist
- js 公共 公共内容,公共组件,公共方法(包括公共指令等)
- css 公共 公共css
- index.html 作为外层框架入口,主入口
- moduleA
- index.html
- moduleB
- index.html
修改打包
- 获取多模块的入口 首先
vue-cli官方当已经提供了在 multi-page 模式下构建应用的配置选项pages:
给出的示例如下:
js
module.exports = {
pages: {
index: {
// page 的入口
entry: 'src/index/main.js',
// 模板来源
template: 'public/index.html',
// 在 dist/index.html 的输出
filename: 'index.html',
// 当使用 title 选项时,
// template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: 'Index Page',
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
// 当使用只有入口的字符串格式时,
// 模板会被推导为 `public/subpage.html`
// 并且如果找不到的话,就回退到 `public/index.html`。
// 输出文件名会被推导为 `subpage.html`。
subpage: 'src/subpage/main.js'
}
}结合示例和我们自定义的模块目录,抽取一下获取多模块入口的逻辑到/src/build/pageFiles中:
js
const path = require('path');
// glob 是 webpack 安装时依赖的一个第三方模块,该模块允许你使用 * 等符号,
// 例如 lib/*.js 就是获取 lib 文件夹下的所有 js 后缀名的文件
const glob = require('glob');
// 取得相应的页面路径,因为之前的配置,所以是 src 文件夹下的 modules 文件夹
const PAGE_PATH = path.resolve(__dirname, '../src/modules');
/*
* 多入口配置
* 通过 glob 模块读取 pages 文件夹下的所有对应文件夹下的 js * 后缀文件,如果该文件存在
* 那么就作为入口处理
*/
exports.getPages = () => {
const pagesConfig = {}
glob.sync(PAGE_PATH + '/**/app.js').forEach((filePath) => {
let chunk = filePath.split('/src/modules/')[1].split('/app.js')[0]
pagesConfig[chunk] = {
entry: filePath,
template: 'public/index.html',
filename: `${chunk}/index.html`,
title: 'module',
chunks: ['chunk-vendors', 'chunk-common', chunk]
}
})
return pagesConfig
}vue.config.js文件中执行getPages获取入口配置:
js
const pageFiles = require('./build/pageFiles')
module.exports = defineConfig({
publicPath: process.env.NODE_ENV === 'production' ? '../' : '/',
pages: pageFiles.getPages(),
// ...
})执行npm run serve本地启动后,生成的多入口配置为:
本地访问主入口页面:
访问子模块页面:
执行npm run build最后的打包文件目录为: 
扩展
上面的示例根据给定的目录直接获取了所有的子模块入口,但一般需求是根据权限列表去生成对应的模块资源,即可构建需要的模块,最简单的方式是命令行直接获取参数,比如我们可以在npm run build 后追加模块参数,但这种方式不够直观,因此使用inquirer实现一个交互式命令,供用户选择本次需要启动的模块列表。
交互式命令选择模块
npm i inquirer@8.2.6 --save-dev这里下载的是v8版本,兼容cjs,v9不兼容。
新建generate-pages文件,用来生成用户手动选择的模块:
js
const inquirer = require('inquirer')
const pageFiles = require('./pageFiles')
exports.promptModule = async () => {
const allModules = pageFiles.getPages()
const answers = await inquirer.prompt([{
type: 'checkbox',
name: 'modules',
message: '请选择启动的模块, 点击上下键选择, 按空格键确认(可以多选), 回车运行。',
pageSize: 15,
choices: Object.keys(allModules).map((item) => {
return {
name: item,
value: item,
}
})
}])
let selectedPages = {}
answers.modules.forEach(item => {
selectedPages[item] = allModules[item]
});
return selectedPages
}修改vue.config.js文件,异步生成页面配置:
js
const generatePages = require('./build/generate-pages')
module.exports = async () => {
const dynamicPages = await generatePages.promptModule();
return {
// ...
pages: dynamicPages,
// ...
}
}执行打包:
只选择module2后,dist只包含module2资源。 