虽然当前前端项目多以单页面为主,但多页面也并不是一无可取,在一些状况下也是有用武之地的,好比:javascript
这里就第4点作一些解释,也对多页面的应用场景作一个我认为有价值的思路,在组内的一个项目中,由于项目日益膨胀,拆分系统有必定困难,项目页面达到200+个以上, 所以构建速度十分缓慢,部署时间也很长,常常由于文案的更改及一些简单的bug修复就要进行从新构建,若是采用单页面一方面构建部署时间会随着体量增大,另外一方面在工程上很差进行拆分。这时候多页面就存在一种优点,咱们能够在前端作一个空框架只包含菜单,内容区域采用多页面结构,当咱们部署上线时能够只针对单个页面进行上线,速度大幅度提高(单页面内部能够集成前端路由),这样业务模块间也可平滑解耦。css
vue + typescript + webpack4 vue项目,并无使用vue-cli,缘由是对于开发人员来讲,了解构建的详细流程很重要,vue-cli这类工具的目的是快速实现项目的搭建,让开发人员快速接手,快速进入 业务代码编写,所以隐含的为咱们作了不少事,不少构建及本地开发的优化等等,但对于开发人员来讲了解每一个步骤,每一个细节是作什么的对自身成长颇有帮助(尤为是组里的不少程序员都不爱使用高度封装的东西)。html
对于多页面来讲,与单页面对比无非就是如下几个问题:前端
- entry入口文件为多个,须要考虑页面多须要自动生成,少的话提早预置几个就能够。
- htmlWebpackPlugin使用时也须要相应的添加多个。
- 公共静态资源提取的问题,splitchunkplugin是否须要使用的问题。
- 最后就是支持项目的部分构建的功能实现
为达到咱们的终极目标,也就是可以部分代码进行构建,咱们将一个项目从业务角度进行一个划分,两个层级,模块和页面,模块表明一个具体业务场景,页面表明这个业务场景的各个页面,咱们将支持进行单/多模块和单/多页面的打包。vue
首先先看一下咱们的项目目录结构java
├── build_tasks // 构建脚本webpack
├── config // 配置文件git
├── src // 源码路径程序员
└── static //build后文件路径github
src目录:
src
├── global.js // 项目全局工具
├── modules // 模块 │
├── Layout.vue │
├── moduleA // 具体模块名 │
│ └── pageA // 具体页面名称 │
├── xx.js │
├── index.vue
因为咱们的页面很是之多,所以咱们确定是须要自动生成entry文件的,而且这一步是须要在进入webpack构建流程以前就要作好的。咱们建立一个build_entries.ts的文件,用于编写建立entry流程,这里放一些核心代码
const getTemplate = pagePath => { return ( ` import App from '${pagePath}'; import Vue from 'vue'; new Vue({ render: function (h) { return h(App); } }).$mount('#app');`); } const scriptReg = /<script([\s\S]*?)>/; /** * 判断文件应该采用的后缀 */ const getSuffix = (source: string): string => { const matchArr = source.match(scriptReg) || []; if(matchArr[1].includes('ts')){ return '.ts' } return '.js'; }; const generateEntries = () => { const entries = {}; /***一些前置代码拿到pages*/ if (!pages.length) return entries; // 清除entries rimraf.sync(entryPath+'/*.*'); pages.forEach(page => { const relativePage = path.relative(vueRoot, page); const source = fs.readFileSync(page, 'utf8'); const suffix = getSuffix(source); const pageEntry = path.resolve(entryPath, relativePage.replace(/\/index\.vue$/, '').replace(/\//g, '.')) + suffix; const entryName = path.basename(pageEntry, suffix); entries[entryName] = pageEntry if (fs.existsSync(pageEntry)) return; const pagePath = path.resolve(vueRoot, relativePage); const template = getTemplate(pagePath); fs.writeFileSync(pageEntry, template, 'utf-8'); }); return entries } export const getEntriesInfos = ()=>{ return generateEntries(); }
大概解释下思路,咱们规定项目目录结构为modules/xxmodle/xxpage,咱们以命名为index.vue的页面为入口页面,为每一个index.vue建立入口的js模版(getTemplete方法),生成的entry名称为"模块名.页面名.js"。由于项目内须要支持ts,所以咱们还须要判断vue内的script标签的语言,以便建立ts格式的entry仍是js格式的entry。 咱们的webpack配置:
const entries = getEntriesInfos(); const common = { entry: entries, output: { filename: `[name]-[hash].bundle.js`, path: path.resolve(rootPath, 'static'), publicPath, },
由于咱们是多页面,每一个页面都须要加载核心的包(如vue,element-ui,lodash等等)而这类包咱们是不常变化的,所以咱们须要使用webpack的dllplugin来剥离他们出来,不参与构建,咱们的项目中也可能会有咱们本身的全局工具包,这部分代码不适合提取,只须要在entry中再加入一个common的entry便可。对于单页面内是否须要使用splitchunk,在个人实践中是没有使用的,可是这个看状况,若是页面引用的包确实比较大(毕竟vue这类框架包已经被提取出去了,这个几率不大)那么可使用splitchunk来分离,我目前的实践是合并到一个页面的js内,单页面js在gzip后在200k之内均可忍受。 下面放一下dll的配置 webpack.dll.config.ts
const commonLibs = ['vue','element-ui','moment', 'lodash'] export default { mode: 'production', entry: { commonLibs }, output: { path: path.join(__dirname, 'dll_libs'), filename: 'dll.[name].[hash:8].min.js', library: '[name]', // publicPath: '/static/' }, plugins: [ new CleanWebpackPlugin(), new webpack.DllPlugin({ context: __dirname, path: path.join(__dirname, 'dll_libs/', '[name]-manifest.json'), name: '[name]' }), new assetsWebpackPlugin({ filename: 'dll_assets.json', path: path.join(__dirname,'assets/') }) ] } as webpack.Configuration;
如代码所示咱们将'vue','element-ui','moment', 'lodash'这几个组件提取打成一个公共包命名为commonLib,这里使用了assetsWebpackPlugin用于生成一个json文件,记录每次dll构建的文件名(由于每次构建hash是不同的),为的是在使用webpackhtmlplugin的时候拿到这个结果注入到模版页面中去。 生成的json记录相似:
{"commonLibs":{"js":"dll.commonLibs.51be3e86.min.js"}}
这样咱们就能够在webpack配置文件中取到这个名字:
const dllJson = require('./assets/dll_assets.json'); for(let entryKey in entries){ if(entryKey!== 'global'){ common.plugins.push( new HtmlWebpackPlugin({ title: allConfiguration[entryKey].title, isDebug: process.env.DEBUG, filename: `${entryKey}.html`, template: 'index.html', chunks:['global', entryKey, ], chunksSortMode: 'manual', dll_common_assets: process.env.NODE_ENV !== 'production'?'./dll_libs/' + dllJson.commonLibs.js : publicPath + 'dll_libs/' + dllJson.commonLibs.js, }), ) } }
由于是多页面,所以咱们webpackhtml使用时也是要添加多个的,这里根据生成的json拿到dll的文件名注入到模版页面中。
接下来咱们要支持进行按需构建打包,支持单/多模块以及单/多页面的打包,这里怎么作呢,能够在构建时传入环境变量,而后在build_entry中判断环境变量进行局部打包,由于打包的入口是entry的数量决定的。 命令能够这样构成:
MODULES=xxx,xxx PAGES=sss,sss npm run build
build_entry相关代码,在generateEntries方法中
const entries = {}; const buildModules = process.env.MODULES || '*'; const buildPages = process.env.PAGES || '*'; const filePaths = `${!buildModules.includes(',') ? buildModules : '{'+buildModules+'}'}/${!buildPages.includes(',') ? buildPages : '{'+buildPages+'}'}/*.vue` const pages = glob.sync(path.resolve(vueRoot, filePaths)).filter(file =>{ return /index\.vue$/.test(file) || []; }) if (!pages.length) return entries;
上面的方法根据传入的环境变量拼对应的页面及模块路径,经过glob的支持生成对应的entyr进行构建。
多页面构建完成以后就是发布流程,发布流程其实也会变的简单,若是是单页面每次构建完成都要总体替换静态文件(js,css),多页面模式下,咱们只须要替换对应页面的文件便可,通常的思路是页面文件能够上传到部署的服务器,而后静态js,css等文件直接扔到CDN上便可,发布不会影响到其余页面,即使出错也不会影响项目,并且效率极高,这部分代码就不展现了,只是提供思路,毕竟每一个项目发布流程都不太同样。
以上是我对多页面应用场景的一个思路,它是有必定的适用场景的,比较适合大而全并且模块划分清晰的系统。
原文连接:https://zhangzippo.github.io/posts/2019/05/12/_20xx-05-10mutilpage.html