高级篇主要是进行webpack
优化,使代码在编译或者运行时性能更加优异,主要从以下角度进行优化。
SourceMap
(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案。
它会生成一个xxx.map
文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过xxx.map
文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。
实际开发时我们只需要关注两种情况即可。
cheap-module-source-map
devtool: "cheap-module-source-map"
。// webpack.dev.js
module.exports = {
// 其他省略
mode: "development",
devtool: "cheap-module-source-map",
};
source-map
devtool: "source-map"
。// webpack.prod.js
module.exports = {
// 其他省略
mode: "production",
devtool: "source-map",
};
开发环境下如果修改了其中一个模块代码,Webpack
默认会将所有模块全部重新打包编译,如果文件过多,会导致速度很慢。所以需要做到修改某个模块代码,仅需重新打包编译该模块,其他模块不变,提升打包速度。
HotModuleReplacement
(HMR
/热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。
配置方式如下,设置devServer
中的hot
属性为true
,即可。(默认条件下该属性时开启状态)
module.exports = {
// 其他省略
devServer: {
host: "localhost", // 服务器域名
port: "3000", // 端口号
open: true, // 是否自动打开浏览器
hot: true, // 开启HMR功能(只能用于开发环境)
},
};
但仅开启hot
属性对js
文件是无效的,修改js
文件依旧是会重新打包整个文件,所以需要对js
文件做手动判断,使用module.hot.accept('文件路径'[,callback])
方法,callback
方法可省略,修改js
文件会触发该方法。
// main.js
import count from "./js/count";
import sum from './js/sum';
// 需要引入才能被打包
import './css/index.css';
import './less/index.less'
console.log(count(1,2));
console.log(sum(1,2,3,4,5));
// 判断是否开启热模块替换
if(module.hot){
// 有回调函数
module.hot.accept('./js/count.js',function(){
const result1 = count(2, 1);
console.log(result1);
});
// 无回调函数
module.hot.accept('./js/sum.js');
}
实际开发中,各前端框架的loader
基本都已封装该方式,无需手动配置,比如vue-loader
和react-hot-loader
等。
打包时每个文件都会经过所有loader
处理,虽然因为test
正则原因实际没有处理上,但是都要过一遍。比较慢。使用oneOf
属性可以实现只要匹配上一个loader
, 剩余的就不再匹配。配置方法如下:
...
module.exports = {
...,
// 模块
module: {
rules: [
{
oneOf: [
{
test:/\.css$/i,
use: ['style-loader', 'css-loader']
},
...
]
}
]
},
// 插件
plugins:[...],
// 开发服务器不会输出文件,只存在内存中
devServer: {...},
// 模式
mode: 'development',
devtool: 'cheap-module-source-map'
};
开发项目时需要使用第三方的库或插件,所有文件都下载到node_modules
中了。而这些文件是不需要编译可以直接使用的。所以在对js
文件处理时,要排除node_modules
下面的文件。
在loader
或者plugins
中可以使用include
或者exclude
对文件进行筛选,顾名思义include
表示仅处理的文件,exclude
表示需要处理的文件,两者是互斥的,即同一个loader
或者plugins
中只能使用其中的一个属性。
如上述babel-loader
中使用的exclude
。一般plugins
插件中会默认开启这个功能,排除node_module
中的文件。对于css-loader
一般是自定义的文件,即使引用了第三方的样式包,同样也需要引入开发文件中所以一般不需要设置include
或者exclude
属性。
每次打包时js
文件都要经过Eslint
检查 和Babel
编译,速度比较慢。所以缓存之前的Eslint
检查 和Babel
编译结果,在第二次或者多次打包时速度就会更快(第一次打包除外,第一次不会有缓存)。
对对Eslint
检查 和Babel
编译结果进行缓存的配置方法如下:
// webpack.dev.js/webpack.prod.js
...
function getCommonStyleSets(pre) {...}
module.exports = {
...,
// 模块
module: {
rules: [
{
oneOf: [
...,
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启babel编译缓存
cacheCompression: false, // 缓存文件不要压缩
},
}
}
]
}
]
},
// 插件
plugins:[
new ESLintWebpackPlugin({
// 指定检查文件的根目录
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", // 默认值
cache: true, // 开启缓存
// 缓存目录及文件名称
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
}),
...
],
// 模式
mode: 'production',
devtool: 'source-map'
};
当项目越来越庞大时,打包速度越来越慢,要继续提升打包速度,本质就是要提升js
的打包速度(其他文件相对都比较少)。而对js
文件处理主要就是eslint
、babel
、Terser
(生产模式下自动激活,无需配置该工具) 三个工具,所以需要提升它们的运行速度。可以开启多进程同时处理js
文件,更快提升打包速度。
多进程打包就是开启电脑的多个进程同时干一件事,速度更快。
由于每个电脑的cpu
核数都不一样,所以首先需要获取cpu
的核数。使用nodejs
的os
模块获取,然后借助thread-loader
进行处理,使用方法如下。
# 安装thread-loader
npm i thread-loader -D
修改webpack配置文件:
// webpack.dev.js/webpack.prod.js
const os = require('os');
...
// terser插件
const TerserPlugin = require("terser-webpack-plugin");
const threads = os.cpus().length-1;
function getCommonStyleSets(pre) {...}
module.exports = {
...,
// 模块
module: {
rules: [
{
oneOf: [
...,
{
...,
use: [
{
loader: "thread-loader", // 开启多进程
options: {
workers: threads, // 数量
},
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启babel编译缓存
cacheCompression: false, // 缓存文件不要压缩
}
},
]
}
]
}
]
},
// 插件
plugins:[
...,
// 加压缩文件可以统一放置到optimization中
// new CssMinimizerPlugin(),
// new TerserPlugin({
// parallel: threads // 开启多进程
// }),
],
optimization: {
// 告知 webpack 使用 TerserPlugin 或其它在 optimization.minimizer定义的插件压缩 bundle。
minimize: true,
// 允许通过提供一个或多个定制过的 TerserPlugin 实例,覆盖默认压缩工具(minimizer)。
minimizer: [
// css压缩也可以写到optimization.minimizer里面,效果一样的
new CssMinimizerPlugin(),
// 当生产模式会默认开启TerserPlugin,但需要进行其他配置,就要重新写
new TerserPlugin({
parallel: threads // 开启多进程
})
],
},
// 模式
mode: 'production',
devtool: 'source-map'
};
注意:每个worker
都是一个独立的node.js
进程,其开销大约为600ms
左右。同时会限制跨进程的数据交换。请仅在耗时的操作中使用此loader
!
开发时如果定义了一些工具函数库,或者引用第三方工具函数库或组件库。如果没有特殊处理,打包时会引入整个库,但是实际上可能只用上极小部分的功能。这样将整个库都打包进来,打包的文件体积就变大了。
Tree Shaking
是一个术语,通常用于描述移除JavaScript
中的没有使用上的代码,它依赖ES Module
(对commonJS
无效)。
Webpack
已经默认开启了这个功能,无需其他配置。该属性的名称为:usedExports
。可以在optimization
属性中开启或关闭。
...
module.exports = {
...,
mode: 'production',
optimization: {
// 默认开启,可省略
usedExports: true,
},
};
举例,在我们的项目中添加一个新的通用模块文件src/math.js
,并导出两个函数,但在main.js
中仅引用了其中一个:
// src/js/math.js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
// main中应用math中的cube方法
...
import { cube } from './js/math';
...
...
console.log(cube(3));
...
打包的文件中不会包含square
方法。
在一个纯粹的ESM
模块世界中,很容易识别出哪些文件有副作用。然而,我们的项目无法达到这种纯度,所以,此时有必要提示 webpack
编译器哪些代码是“纯粹部分”。通过package.json
的 “sideEffects
” 属性,来实现这种方式。
“side effect
(副作用)” 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个export
或多个export
。举例说明,例如 polyfill
,它影响全局作用域,并且通常不提供export
。具体配置方式请参考webpack
官网。