内容简介:由于公司旧版的脚手架是基于这次升级有几个地方需要注意和改进:
Webpack
构建的基于 zepto
的多页应用脚手架,本文聊聊本次项目中 Webpack
构建多页应用的一些心得体会。
1.前言
由于公司旧版的脚手架是基于 Gulp
构建的 zepto
多页应用(有兴趣可以看看 web-mobile-cli
),有着不少的痛点。例如:
-
需要兼容低版本浏览器,只能采用
promise
,不能使用await
、generator
等。(因为babel-runtime
需要模块化); - 浏览器缓存不友好(只能全缓存而不是使用资源文件的后缀哈希值来达到局部缓存的效果);
- 项目的结构不友好(可以更好的结构化);
- 开发环境下的构建速度(内存);
-
Gulp
插件相对Webpack
少且久远,维护成本高等等。
这次升级有几个地方需要注意和改进:
- 项目旧代码尽量做到无缝转移;
- 资源文件的缓存;
- 组件式的组织目录结构。
Github仓库:
2.多页
Webpack
的多页应用通过多入口 entry
和多实例 html-webpack-plugin
配合来构建, html-webpack-plugin
的 chunk
属性传入对应 entry
的 key
就可以做到关联,例如:
module.exports = { entry: { pageOne: './src/pageOne/index.js', pageTwo: './src/pageTwo/index.js', pageThree: './src/pageThree/index.js' }, plugins: [ new HtmlWebpackPlugin({ filename: `pageOne.html`, template: `./src/pageOne.html`, chunks: ['pageOne'] }), new HtmlWebpackPlugin({ filename: `pageTwo.html`, template: `./src/pageTwo.html`, chunks: ['pageTwo'] }), new HtmlWebpackPlugin({ filename: `pageTwo.html`, template: `./src/pageTwo.html`, chunks: ['pageTwo'] }) ] } 复制代码
那么问题来了,开发新的页面每次都得添加岂不是很麻烦。这里推荐神器 glob 根据正则规则匹配。
const glob = require('glob') module.exports = { entry: glob.sync('./src/js/*.js').reduce((pre, filepath) => { const tempList = filepath.split('src/')[1].split(/js\//) const filename = `${tempList[0]}${tempList[1].replace(/\.js/g, '')}` return Object.assign(pre, {[filename]: filepath}) }, {}), plugins: [ ...glob.sync('./src/html/*.ejs').map((filepath, i) => { const tempList = filepath.split('src/')[1].split(/html\//) const fileName = tempList[1].split('.')[0].split(/[\/|\/\/|\\|\\\\]/g).pop() const fileChunk = `${tempList[0]}${fileName}` return new HtmlWebpackPlugin({ filename: `${fileChunk}.html`, template: filepath, chunks: [fileChunk] }) }) ] } 复制代码
3.模板
项目没有直接使用 html
,而是使用了 ejs
作为模板,这里有至少两个好处:
- 把公共的代码抽离出来;
- 传入公共的变量。
// header.ejs <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title><%= title %></title> </head> // index.ejs <!DOCTYPE html> <html lang="en"> <% include ./header.ejs %> <body> <!-- page --> </body> <script src="<%= publicPath %>lib/zepto.js"></script> </html> 复制代码
<% include ./header.ejs %>
就是引用了 header.ejs
文件, <%= title %>
和 <%= publicPath %>
是我在配置文件定义的两个变量, publicPath
是为了统一 cdn
缓存服务器的域名,非常有用。
4.垫片
项目中使用了 zepto
,所以需要垫片,所谓垫片就是shim 预置依赖,即全局依赖。
webpack compiler 能够识别遵循 ES2015 模块语法、CommonJS 或 AMD 规范编写的模块。然而,一些 third party(第三方库) 可能会引用一些全局依赖(例如 jQuery 中的 $)。因此这些 library 也可能会创建一些需要导出的全局变量。这些 "broken modules(不符合规范的模块)" 就是 shim(预置依赖) 发挥作用的地方。
垫片有两种方式:
-
传统方式的 垫片
就是在
html
文件中,所有引用的js
文件的最前面引用的文件(例如zepto
); -
Webpack
配置shim预置依赖
。
最终我选择了 Webpack
配置 shim预置依赖
这种方式,因为:
-
传统的方式需要每个页面都手动引入(虽说搭配
ejs
可以抽离出来成为公共模块,但还是需要每个页面手动引入公共模块); - 传统的方式需要多发一次请求去请求垫片;
-
Webpack
可以把所有第三方插件的代码都拆分打包成为一个独立的chunk
,只需一个请求。
module.exports = { entry: {...}, module: { rules: [ { test: require.resolve('zepto'), use: 'imports-loader?this=>window' } ] }, plugins: [ new webpack.ProvidePlugin({$: 'zepto'}) ] } 复制代码
5.拆分
一般来讲 Webpack
的配置 entry
中每个 key
就对应输出一个 chunk
,那么该项目中会提取这几类 chunk
:
-
页面入口(
entry
)对应的chunk
; -
common
:多次引用的公共文件; -
vender
:第三方依赖; -
manifest
:Webpack
运行时(runtime
)代码,它存储着Webpack
对module
和chunk
的信息。
module.exports = { entry: {...}, module: {...}, plugins: [], optimization: { runtimeChunk: { name: 'manifest' }, splitChunks: { cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, chunks: 'all', name: 'vendors', filename: 'js/vendors.[contenthash:8].js', priority: 2, reuseExistingChunk: true }, common: { test: /\.m?js$/, chunks: 'all', name: 'common', filename: 'js/common.[contenthash:8].js', minSize: 0, minChunks: 2, priority: 1, reuseExistingChunk: true } } } } } 复制代码
这里注意的有两点:
-
优先顺序:第三方插件的
priority
比common
代码的priority
大; -
提取
common
代码:minChunks
为引用次数,我设置为引用2次即提取为公共代码。minSize
为最小字节,设置为0。
6.缓存
缓存的目的是为了提高加载速度, Webpack
在缓存方面已经是老生常谈的了,每个文件赋予唯一的hash值,只有更新过的文件,hash值才改变,以达到整体项目最少文件改动。
6.1 hash值
Webpack
中有三种 hash
值:
-
hash
:全部文件同一hash
,一旦某个文件改变,全部文件的hash都将改变(同一hash
不满足需求); -
chunkhash
:根据不同的入口文件(Entry
)进行依赖文件解析、构建对应的chunk,生成对应的哈希值(问题是css
作为模块import
到JavaScript
文件中的,它们的chunkhash
是一致的,一旦改变js
文件,即使import
的css
文件内容没有改变,其chunkhash
值也会一同改变,不满足需求); -
contexthash
:只有模块的内容变了,那么hash值才改变(采用)。
module.exports = { entry: { pageOne: './src/pageOne/index.js', pageTwo: './src/pageTwo/index.js', pageThree: './src/pageThree/index.js' }, output: { path: 'src', chunkFilename: 'j[name].[contenthash:8].js', filename: '[name].[contenthash:8].js' }, plugins: [ new HtmlWebpackPlugin({ filename: `pageOne.html`, template: `./src/pageOne.html`, chunks: ['pageOne'] }), new HtmlWebpackPlugin({ filename: `pageTwo.html`, template: `./src/pageTwo.html`, chunks: ['pageTwo'] }), new HtmlWebpackPlugin({ filename: `pageTwo.html`, template: `./src/pageTwo.html`, chunks: ['pageTwo'] }) ] } 复制代码
6.2 module id
仅仅使用 contexthash
还不足够,每当 import
的资源文件顺序改变时, chunk
依然会改变,目的没有达成。要解决这个问题首先要理解 module
和 chunk
分别是什么,简单理解:
-
module
:一个import
对应一个module
(例如:import zepto from 'zepto'
中的zepto
就是一个module
); -
chunk
:根据配置文件打包出来的包,就是chunk
。(例如多页应用中每个entry
的key
值对应的文件)。
因为 Webpack
内部维护了一个自增的 id
,依照顺序赋予给每个 module
,每当新增或者删减导致 module
的顺序改变时,受影响的 chunk
的 hash
值也会改变。解决办法就是使用唯一的 hash
值替代自增的 id
。
module.exports = { entry: {...}, module: {...}, plugins: [], optimization: { moduleIds: 'hashed' } } 复制代码
7.优化
优化的目的是提高执行和打包的速度。
7.1 查找路径
告诉 Webpack
解析模块时应该搜索的目录,缩小编译范围,减少不必要的编译工作。
const {resolve} = require('path') module.exports = { entry: {...}, module: {...}, plugins: [], optimization: {...}, resolve: { alias: { '@': resolve(__dirname, '../src'), }, modules: [ resolve('src'), resolve('node_modules'), ] } } 复制代码
7.2 指定目录
指定 loader
的 include
目录,作用是缩小编译范围。
const {resolve} = require('path') module.exports = { entry: {...}, module: { rules: [ { test: /\.css$/, include: [ resolve("src"), ], use: ['style-loader', 'css-loader'] } ] }, plugins: [], optimization: {...}, resolve: {...} } 复制代码
7.3 babel
缓存目录
babel-loader
开始缓存目录 cacheDirectory
。
const {resolve} = require('path') module.exports = { entry: {...}, module: { rules: [ { test: /\.m?js$/, exclude: /(node_modules|bower_components)/, include: [ resolve("src"), ], use: { loader: 'babel-loader', options: { cacheDirectory: true, presets: ['@babel/preset-env'], plugins: ['@babel/plugin-transform-runtime'] } } } ] }, plugins: [], optimization: {...}, resolve: {...} } 复制代码
7.4 插件 TerserJSPlugin
TerserJSPlugin
插件的作用是压缩 JavaScript
,优化的地方是开启缓存目录和开启多线程。
const {resolve} = require('path') module.exports = { entry: {...}, module: {...}, plugins: [], optimization: { minimizer: [ new TerserJSPlugin({ parallel: true, cache: true, }) ] }, resolve: {...} } 复制代码
8.总结
通过这次学习 Webpack
到升级脚手架,对前端工程化有了进一步的了解,也感受到了 Webpack4
带来的开箱即用,挺方便的。
以上所述就是小编给大家介绍的《Webpack构建多页应用心得体会》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 入行产品这一年,我的心得体会
- vueSSR: 从0到1构建vueSSR项目 --- 路由的构建
- 在 Android Studio 里使用构建分析器提升构建性能
- [译] 使用 React 和 ImmutableJS 构建一个拖放布局构建器
- 为 Envoy 构建控制面指南第4部分:构建的可扩展性
- 自动化构建工具 Gradle 4.5 RC1 发布,改进构建缓存
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。