webpack-merge
使用 webpack-merge
插件可以让不同环境的 webpack 配置分别写在不同的文件里。在配置 webpack 时可以将开发环境和生产环节相同的配置项提取出来,写在一个单独的文件中,这样做可以更好的管理项目配置。
// 提取出 webpack-base文件
module.exports = {
// ....
}
// webpack-config-dev.js 文件
const { merge } = require('webpack-merge');
const webpackBase = require('./webpack-config-base');
// 合并:
module.exports = merge(webpackBase,{
// 开发环境配置
// 如果配置相同项时
// base 中的配置项会被覆盖
});
// webpack-config-prod.js 文件
const merge = require('webpack-merge');
const webpackBase = require('./webpack-config-base');
// 合并:
module.exports = merge(webpackBase,{
// 开发环境配置
// 如果配置相同项时
// base 中的配置项会被覆盖
});
devtool
devtool
可以让打包的代码生成 source map 文件,source map 就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。
有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这给开发者带来了很大方便。
module.exports = {
devtool: 'source-map',
}
source-map
会输出详细的异常信息,除此之外 devtool
的其他几个配置值:
eval-source-map
,这个表示不会产生单独的文件(集成在打包后的文件中),但是可以显示行和列(代码有异常时);cheap-module-source-map
不会产生列,但是会产生一个 source-map;cheap-module-eval-source-map
配置不会生成 source-map 文件,集成在打包后的文件中,不会产生列。hidden-source-map
会产生出完整的 map 文件,只不过不会在 bundle 文件中添加对于 map 文件的应用。
要产生 CSS map 文件,可以对 optimize-css-assets-webpack-plugin 插件进行配置。
new OptimizeCssAssetsPlugin({
cssProcessorOptions: {
map: {
inline: false, // 产生 map 文件
annotation: true, // 有注释
}
}
})
如果 inline
设置成 true,则表示不生成 map 文件,会与 bundle 放在一起。
source map 不仅可以帮助开发者调试源码,当线上有问题产生时也有助于查看调用栈信息,是线上查错的重要线索。同时,有了 source map 也意味着任何人通过浏览器的开发者工具都可以看到工程源码,对于安全性来说是有隐患的。可以将 devtool
配置为 nosources-source-map
,打包部署之后,我们可以在浏览器开发者工具的 Sources 选项卡看到源码的目录结构,但是文件的具体内容会被隐藏起来,对于错误来说,我们仍然可以在 console 控制台中查看源码的错误栈,或者 console 日志的准确行数。
IgnorePlugin 与 noParse
当引入一个模块时,webpack 会检测这个模块有没有依赖别的模块,如果没有依赖别的模块,可以使用 noParse
配置项让 webpack 不再做多余的解析。noParse: /jquery/ 表示不再解析 jquery 模块。
module.exports = {
//...
module: {
noParse: /jquery|lodash/,
}
};
IgnorePlugin
是 webpack 的一个内置插件,该插件可以忽略掉指定的文件。比如当在项目中使用 moment 模块时,moment 模块中会引入别的模块,比如:locale
目录下所有的模块,这些模块都是语言模块(包含了许多语言来格式化本地时间),但有许多是用不到的。因此可以使用 ignorePlugin 插件忽略掉 locale 模块:
{
plugin: [
// 从 moment 中引入了 locale 时,就会把 locale 忽略掉
new webpack.IgnorePlugin(/\.\/locale/,/moment/),
]
}
这样所有的语言包都会被忽略掉。但我们需要时就需要手动引入:
import 'moment/locale/zh-cn';
webpack 自身的优化
tree-shaking
tree shaking 可以在打包过程中帮助我们检测工程中没有被引用过的模块,webpack 回对这部分代码进行标记,并在资源压缩时(生产环境)将它们从最终的 bundle 中去掉。
有些我们自己写的模块或者第三方模块中使用 export
导出了,但是我们在实际开发中并没有使用 import
引入这些模块来使用,这些模块也被称为“死代码”,在打包时我们也没必要把它们打包进去。
tree-shaking 有时可以让 bundle 体积显著减小。但要启用 tree shaking 必须使用 ES6 的模块系统,commonjs 模块不具备 tree shaking 的能力。
ES6 Module 是静态引入,编译时引入,无条件的引入,不能在某个条件语句中使用;commonjs 是动态引入,可以在条件语句中引入,执行时才被引入。只有 ES6 Module 才能静态分析,实现 Tree-Shaking,webpack 在打包时代码还没有执行,而 commonjs 模块在执行时才被引入,因此无法实现 Tree-Shaking。
在 babel-loader 中应禁止它的模块依赖解析,因为如果由 babel-loader 来做依赖解析,webpack 接收到的就都是转化过的 commonjs 形式的模块,无法进行 tree shaking。
// .babelrc 文件
{
"presets": [
["@babel/preset-env", { "modules": false }]
]
}
scope-hosting
Webpack 在打包时会为每一个文件包装一层函数作用域来避免全局污染,项目比较大时函数作用域会越来越多,对 js 代码的执行和内存的使用是很不友好的。而 webpack 在开发模式下会默认开启 scope-hosting,它会把多个函数合并为一个函数,减少函数作用域,减少代码体积。
多线程打包与多进程压缩
hppypack
Happypack 可以开启多个线程,对不同模块进行转译,这样就可以充分利用本地的计算资源来提升打包速度。对于一些转译任务比较重的工程,比如 babel-loader、ts-loader,我们可以使用 happypack 进行提速,使用前需要先下载。
yarn add happypack -D
使用时需要改造一下 loader 中的配置。
{
test: /\.jsx?$/,
// 把对 .js 文件的处理转交给 id 为 js 的 HappyPack 实例
loader: 'happypack/loader?id=js',
exclude: /node_modules/
},{
test: /\.tsx?$/,
// 把对 .ts 文件的处理转交给 id 为 ts 的 HappyPack 实例
loader: 'happypack/loader?id=ts',
}
// ....
plugins: [
new HappyPack({
id: 'js',
loaders: [{
loader: 'babel-loader',
options: {
// 启用缓存机制,在重复打包未改变过的模块时防止二次编译
cacheDirectory: true
}
}]
}),
new HappyPack({
id: 'ts',
loaders: [{
loader: 'ts-loader'
}]
}),
]
thread-loader
thread-loader 和 happypack 的作用一样。它在配置时只需要将其放在其他 loader 之前即可。
{
test: /\.jsx?$/,
use: ['thread-loader', 'babel-loader']
}
生产环境与开发环境
mode
配置项为 development
时,表示开发环境;mode 配置项为 production
表示生产环境。
production
模式的效果:
- 自动开启代码压缩;
- Vue、React 等会自动删掉调试代码(如开发环境的 warning);
- 自动启动 Tree-Shaking(只打包项目中使用的模块,即 import 引入的模块),必须使用 ES6 的模块才能生效(commonjs不会生效);
- 启动 Scope Hosting。过去,webpack 对每个模块都将包裹在单独的函数闭包中,而 Scope Hosting 可以将多个作用域合并到一个函数作用域下,减少代码体积,创建的函数作用域会更少。
生产环境调优
- 优化 babel-loader,例如设置 exclude 和 include,去除掉不用 babel 解析的文件;
- 使用 IgnorePlugin 忽略一些不必要的模块;
- 使用 noParse 忽略一些文件的解析;
- 使用动态链接库:DllPlugin;
- happyPack 多进程打包;
- ParallelUglifyPlugin 多进程打包 JavaScript 代码。
开发环境调优
- 自动刷新,配置 webpack 的
watch
项; - 使用 devServer;
- 热更新(HMR);
- DllPlugin 使用动态链接库;
优化产出代码
- 小图片使用 base64 编码(url-loader 中设置 limit);
- bundle 加 hash(当内容改变后,同时引起资源文件名的更改,从而使用户在下一次请求资源文件时会立即下载新的版本而不会使用本地缓存);
- 懒加载(减少首次页面请求的代码体积)
- 提取公共代码(如splitChunks);
- IgnorePlugin;
- 使用 production;
- 使用 CDN 加速;
常用的 plugin
cleanWebpackPlugin
该插件功能是每次新的打包完成后,旧的打包目录会自动被删除。该插件需要传入一个参数,你要删除的路径,要删除多个目录可以传入一个数组。
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin({
// 要删除的路径
cleanOnceBeforeBuildPatterns: ['../build'],
// 允许删除 webpack 配置目录之外的文件
dangerouslyAllowCleanPatternsOutsideProject: true,
})
]
}
copyWebpackPlugin
这个插件的功能是将没有指定为入口的目录中的文件拷贝到打包后的目录中。
格式:
import CopyWebpackPlugin from 'copy-webpack-plugin';
module.exports = {
plugins: [
new CopyWebpackPlugin([
patterns: [
{ from: 'source', to: 'dest' },
{ from: 'other', to: 'public' },
],
]);
]
}
sizePlugin
size-plugin 这个插件可以帮助我们监控资源体积的变化,尽早地发现问题。
speedMeasurePlugin
SMP 可以分析出 Webpack 整个打包过程中在各个 loader 和 plugin 上耗费的时间,这将会有助于找出构建过程中的性能瓶颈。使用如下:
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
mode: process.env.NODE_ENV,
entry: {
index: path.join(__dirname, '../src/index.js')
},
// ....
});
webpackBundleAnalyzer
这个模块可以生成可视化图表文档,让我们查看 webpack 输出文件的大小。
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
loader 与 plugin 的区别
loader
是模块转换器,如 less --> css,loader 能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中;plugin
用于扩展 webpack 的功能,它可以解决 loader 无法实现的其他任务。plugin 可以处理各种各样的任务,比如压缩代码,重新定义环境变量,在webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。plugin 针对是 loader 结束后,webpack 打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些节点,执行广泛的任务。
前端使用打包和构建的原因
- 打包会让代码体积更小(Tree-Sharking、压缩、合并),加载更快;
- 编译高级语法(ts、ES6+、模块化、sass等);
- 兼容性和错误性检查(polyfill、postcss、eslint);
- 统一、高效的开发环境;
- 统一的构建流程和产出标准;
- 集成公司构建规范(提交、测试、上线等)。