目录
一. 需求分析
二. 原方案分析
三. 多页面改造 3 步走
四. 小结
示例代码托管在: http://www.github.com/dashnowords/blogs
博客园地址:《大史住在大前端》原创博文目录
华为云社区地址:[你要的前端打怪升级指南]
一. 需求分析
为另一个项目提供可嵌入的功能单页, 大部分页面使用时都是独立功能页, 个别页面带有左侧边栏 (相当于 3-4 个页面的整合形态), 由于资源定位地址的限定, 每个页面打包为单页后, 入口 html 文件需要定制命名, 且脚本和样式文件需要放在指定的路径下, 公共资源地址也必须替换成特殊字符以适配母系统的调用逻辑 (比如下面结构中应用 jQuery.min.JS 的路径可能是 {{publicRoot}}/{{publicLib}}/jQuery.minjs). 假设原工程中拥有 AB 这 2 个旧页面, 现在需要开发 CDE 这 3 个页面, 目录结构要求如下:
蓝色部分为旧资源, 绿色部分为新开发需求.
二. 原方案分析
原方案采用 vue+ElementUI 进行开发, 构建过程基本是零配置的, 开发效率非常高, 页面风格也统一, 但零配置的构建过程只能生成 SPA 模式的应用, 所以原方案的做法是:
将构建过程中需要定制的量提取到 config.JS 文件中进行统一管理, 大致形式如下:
- //config.JS
- module.exports = {
- A:{
- publicPath:'{{publicRoot}}/{{publicLib}}'
- prodFileName:'A.html',
- entryKey:'public/A',
- entryPath:'public/A/A.js'
- },
- B:{
- //...
- }
- //...
- }
开发过程中使用统一的路由文件 router.JS, 打包过程中在 main.JS 中引用对应页面的 XX.router.spa.JS 作为路由, 而将其他页面注释掉, 打包时传入命令行参数 --key=XXX,key 值在打包脚本中被解析后从 config.JS 中取出打包需要的设置参数, 然后将目标页面打包为独立页面, 其他页面虽然也在工程中, 但并不参与打包.
- // 入口文件 src/main.JS
- import router from './pages/C/router.spa';
- //import router from './pages/D/router.spa';
- //import router from './pages/E/router.spa';
上述打包过程在使用中出现了很多问题:
公共依赖没有剥离, vue 和 ElementUI 会被打包进每一个单页面, 使得每个打包出的 index.JS 几乎有 1.2MB 大小, 这种空间浪费是没必要的.
公共样式没有形成独立文件, 这使得每当有样式细节发生变更, 就需要手动将每个页面逐一进行重新出包.
页面增多后在 main.JS 中会有很多独立路由, 如果开发中进行了跨页面修改, 很可能在 main.JS 中激活的路由为 C 页面路由时, 打包时 --key 参数的值却传成了 D, 这种情况并不会引起报错, 但事实上构建结果确实错误的.
由于入口文件保持 main.JS 没有变化, 所以在不同页面打包时, 结果都输出在 dist 目录下, 需要手动与母工程中的地址去匹配, 操作繁琐.
三. 多页面改造 3 步走
上面的问题实际上都是因为原方案将一个多页面开发需求按照单页面应用来实现而造成的, 需要对自动化构建工程进行一些定制.
1. 分离 webpack 配置
本例中开发环境和最终打包的主要差异在于路由上, 开发中由于可能需要进行跨页面开发, 可以使用单入口和独立路由, 而进行生产环境构建时则需要输出多页面应用, 所以首先要做的就是将原本的 webpack.config.JS 文件拆分为 webpack.base.JS,webpack.dev.JS,webpack.prod.JS 三个文件, webpack.base.JS 为环境无差别的配置, 然后依据构建模式的不同, 使用 https://github.com/survivejs/webpack-merge 插件将环境相关的配置与基本配置进行合并:
- /*webpack.base.JS 示例 */
- const argv = require('yargs-parser')(process.argv.slice(2));
- const env_short = argv.env.all ? 'all' : argv.p ? 'prod':'dev';
- const webpackConfig = require(`./config/webpack.${env_short}`);// 根据 - p 属性加载 webpack 的 dev 配置或 prod 配置
- const merge = require('webpack-merge');
- // 基本配置
- const baseConfig = {
- //....
- }
- // 输出合并后的配置
- module.exports = merge(baseConfig, webpackConfig);
webpack.dev.JS 保持原本的 SPA 开发的设置即可满足需求.
2. 抽离外部引用
本例中较大的外部应用是 vue 和 ElementUI, 很多开发者一直使用自动化脚手架工具, 并没有意识到这两个库作为外部依赖该如何引入工程. 公共库的抽离需要在 webpack 配置中将其填写在 external 配置项中:
- module.exports = {
- //...
- externals:{
- vue:'Vue',
- 'element-ui':'ELEMENT'
- },
- //...
- }
key 为引用的模块名, value 为这个模块引入后对应的全局命名, external 配置项的含义是: 请不要将这个模块注入编译后的 JS 文件里, 对于源代码里出现的任何 import/require 这个模块的语句, 请将它保留并根据模块化标准进行依赖方式适配 .
Tips:
Vue 做为外部依赖时有很多构建包, 本例中因为使用 webpack 进行了构建, 没有在线编译模板的需求, 所以不需要引入完整的 Vue, 而只需要引入压缩后的只包含运行时的版本 vue.runtime.min.JS 即可.
外部引入库时需要注意命名, 比如上例中的 ELEMENT, 开发者通常会填写为自己在代码中使用的 ElementUI 而引起报错, 当不确定名称时, 有个简单的办法就是找一个 CDN 的资源看一下, 通常代码最开始都是 UMD 规范的固定结构, 很容易看到关键词 (如下图所示).
然后将资源的 CDN 地址或是本地公共库地址加入到 index.HTML 中, 你可以使用模板语法, 然后从 HTML-webpack-plugin 插件实例化时传入定制参数:
- <!--html 文件模板 -->
- <body>
- <div id="app"></div>
- <script src="<%= htmlWebpackPlugin.options.vue_path %>"></script>
- <script src="<%= htmlWebpackPlugin.options.elementUI_path %>"></script>
- <script src="<%= htmlWebpackPlugin.options.tpl_entryPath %>/index.js"></script>
- </body>
- //webpack.prod.JS
- module.exports = {
- //...
- plugins: [
- new HtmlWebpackPlugin({
- template: 'src/index.html',// 生成 index.HTML 时依据的模板
- filename: '.....',
- inject:false,
- tpl_entryPath:'....',
- vue_path:'.....',
- elementUI_path:'.....',
- }),
- //new BundleAnalyzerPlugin()
- ],
- }
最终打包后生成的 index.HTML 文件大致如下:
- <body>
- <div id="app"></div>
- <script src="{{publicRoot}}/{{publicLib}}/vue.min.js"></script>
- <script src="{{publicRoot}}/{{publicLib}}/element-ui.js"></script>
- <script src="public/A/A.js"></script>
- </body>
如果第三方库从本地加载, 则需要将 / node_modules/element-ui/lib/index.JS 和 / node_modules/vue/dist/vue.runtime.min.JS 两个依赖文件拷贝到 lib 文件夹中的对应地址, 这样访问 index.HTML 时就可以以外部依赖的形式将其加载进来. 样式文件的剥离直接使用插件完成即可, webpack4 以前的版本使用 extract-text-webpack-plugin, 从 4.0 版本后统一使用 mini-CSS-extract-plugin.
3. 为 webpack 定制多入口
多入口的配置是多页面应用打包的关键, 由于打包结果存在嵌套目录, 所以需要对 entry 对象的键值进行一些定制, 打包后的路径信息是直接通过 key 值来定制的, 同时需要实例化多个 HtmlWebpackPlugin 来为每一个入口文件生成一个对应的 index.HTML 访问入口, 定制参数可以在实例化时传入:
- //webpack.prod.JS
- module.exports = {
- entry:{
- 'C/index':'./src/pages/C/C.entry.js',
- 'DESK/D/index':'./src/pages/D/D.entry.js',
- 'DESK/E/index':'./src/pages/E/E.entry.js'
- }
- //...
- plugins:[
- new HtmlWebpackPlugin({...paramsC}),
- new HtmlWebpackPlugin({...paramsD}),
- new HtmlWebpackPlugin({...paramsE}),
- ]
- }
当然你可以将 entry 或 plugins 数组的组装过程剥离到其他文件中, 然后直接引用:
当然, 每个页面的入口文件 X.entry.JS 相当于旧方案中 main.JS 文件中移除被注释掉的未启用路由信息后剩余的部分, 它足以支撑每个单页独立被访问.
四. 小结
经上述改造后, 在 dist 目录中输出的结构和需求中 public 目录下的结构就保持一致了, 而且每个页面的 index.JS 文件也缩小到了 100K 左右. 当然你也可以使用 node.JS 去编写一些自动化脚本, 将后续的替换过程也自动化, 或者继续对 webpack 的打包过程进行优化, 本文就不再赘述了.
来源: https://www.cnblogs.com/dashnowords/p/11415211.html