一、 webpack 进阶用法的提取页面公共资源、treeShaking、scopeHoisting、代码分割、webpack与 ESLint 结合和webpack 打包组件与基础库
- 基础库分离,思路是将
react、react-dom
基础包通过 cdn
引入,不打入 bundle
中,方法是使用 html-webpack-externals-plugin
。 - 利用
SplitChunksPlugin
进行公共脚本分离,Webpack4
内置的,替代 CommonsChunkPlugin
插件。chunks
参数说明,async
异步引入的库进行分离(默认),initial
同步引入的库进行分离,all
所有引入的库进行分离(推荐),代码如下:
module.exports = {
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests:3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
}
}
}
};
- 利用
SplitChunksPlugin
分离基础包,test
是匹配出需要分离的包,代码如下:
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /(react|react-dom)/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
- 利用
SplitChunksPlugin
分离页面公共文件,minChunks
设置最小引用次数为 2 次,minSize
分离的包体积的大小,代码如下:
module.exports = {
optimization: {
splitChunks: {
minSize: 0,
cacheGroups: {
commons: {
name: 'commons',
chunks: 'all',
minChunks: 2
}
}
}
}
};
tree shaking
摇树优化,1 个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到 bundle
里面去,tree shaking
就是只把用到的方法打入 bundle
,没用到的方法会在 uglify
阶段被擦除掉。对于使用,webpack
默认支持,在 .babelrc
里设置 modules
: false
即可,production mode
的情况下默认开启。对于要求,必须是 ES6
的语法,CJS
的方式不支持。DCE
,如下所示:
- 代码不会被执行,不可到达
- 代码执行的结果不会被用到
- 代码只会影响死变量,只写不读
Tree shaking
原理,如下所示:
- 利用
ES6
模块的特点,只能作为模块顶层的语句出现,important
的模块名只能是字符串变量,important binding
是 immutable
的 - 代码擦除,
uglify
阶段删除无用代码
- 对于现象,构建后的代码存在大量闭包代码,以此导致的问题,如下所示:
- 大量函数闭包包裹代码,导致代码体积增大,模块越多越明显
- 运行代码时创建的函数作用域变多,内存开销变大
- 对于
webpack
模块转换分析,被 webpack
转换后的模块会带上一层包裹,important
会被转换成 _webpack_require
,webpack
的模块机制,如下所示:
- 打包出来的是一个
IFE
匿名闭包 modules
是一个数组,每一项是一个模块初始化函数_webpack_require
用来加载模块,返回 module.exports
- 通过
WEBPACK_REQUIRE_METHOD(0)
启动程序
scope hoisting
原理,将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突。通过对比,使用 scope hoisting
可以减少函数声明代码和内存开销。scope hoisting
使用,webpack mode
为 production
默认开启,必须是 ES6
语法,CJS
不支持,代码如下:
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name][chunkhash:8].js',
path: _dirname + '/dist'
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
};
- 代码分割的意义,对于大的
Web
应用来讲,将所有的代码都放在一个文件中显然是不够有效的,特别是当你的某些代码块是在某些特殊的时候才会被使用到。webpack
有一个功能就是将你的代码库分割成 chunks
语块,当代码运行到需要它们的时候再进行加载,使用的场景,如下所示:
- 抽离相同代码到一个共享块
- 脚本懒加载,使得初始下载的代码更小
- 懒加载
JS
脚本的方式,如下所示:
CommonJS
:require.ensure
ES6
:动态 import
,目前还没有原生支持,需要 babel
转换
- 如何使用动态
import
,如下所示:
- 安装
babel
插件,通过 npm install @babel/plugin-syntax-dynamic-important --save-dev
命令 ES6
动态 import
,目前还没有原生支持,需要 babel
转换,{ "plugins": ["@babel/plugin-syntax-dynamic-important"], }
- 制定团队的
ESLint
规范,如下所示:
- 不重复造轮子,基于
eslint:recommend
配置并改进 - 能够帮助发现代码错误的规则,全部开启
- 帮助保持团队的代码风格统一,而不是限制开发体验
ESLint
如何执行落地,如下所示:
- 对于方案一,
webpack
与 CI/CD
集成,如下所示:
- 增加
lint pipline
- 本地开发阶段增加
precommit
钩子,安装 husky
,通过 npm install husky --save-dev
命令 - 增加
npm script
,通过 lint-staged
增量检查修改的文件,代码如下:
"scripts": {
"precommit": "lint-staged"
},
"lint-staged": {
"linters": {
"*.{js,scss}": ["eslint --fix", "git add"]
}
},
- 对于方案二,
webpack
与 ESLint
集成,使用 eslint-loader
,构建时检查 JS
规范,如下所示:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
"babel-loader",
"eslint-loader"
]
}
]
}
};
webpack
打包库和组件,webpack
除了可以用来打包应用,也可以用来打包 js
库,实现一个大整数加法库的打包,如下所示:
- 需要打包压缩版和非压缩版本
- 支持
ADM/CJS/ESM
模块引入
- 库的目录结构和打包要求,打包输出的库名称,未压缩版
large-number.js
,压缩版 large-number.min.js
。对于支持的使用方式,支持 ES module
、支持 CJS
和支持 AMD
。如何将库暴露出去,library
指定库的全局变量,libraryTarget
支持库引入的方式,代码如下:
module.exports = {
mode: "production",
entry: {
"large-number": "./src/index.js",
"large-number.min": "./src/index.js"
},
output: {
filename: "[name].js",
library: "largeNumber",
libraryExport: "default",
libraryTarget: "umd"
}
};
- 如何只对
.min
压缩,通过 include
设置只压缩 min.js
结尾的文件,代码如下:
module.exports = {
mode: "none",
entry: {
"large-number": "./src/index.js",
"large-number.min": "./src/index.js"
},
output: {
filename: "[name].js",
library: "largeNumber",
libraryTarget: "umd"
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
include: /\.min\.js$/,
})
]
}
};
- 对于设置入口文件,
package.json
的 main
字段为 index.js
,代码如下:
if (process.env.NODE_ENV === 'production') {
module.exports = require("./dist/large-number.min.js");
} else {
module.exports = require("./dist/large-number.js");
}