系列文章
1、提升开发体验
1、sourceMap
1、为什么?
为什么要有sourceMap
?
因为我们在开发环境用webpack
编译生成的代码通常情况下是这个样子的,如果在开发环境下面报错了,我们在定位问题的时候是非常困难的,所以我们就需要一个文件来映射源代码与编译后代码
之间的关系。
2、是什么?
sourceMap
顾名思义,就是对源代码的一个映射,里面包含了源代码和构建后代码的每一行、每一列的映射关系。当构建后的代码运行报错的时候,就可以通过.map
文件,从构建后代码出错的位置找到映射后源代码出错的位置,这样就可以让浏览器提示出源代码出错的位置,帮助我们更快的找到错误的位置。
3、怎么用?
通过查看Webpack DevTool 文档open in new window可知,SourceMap 的值有很多种情况.
但实际开发时我们只需要关注两种情况即可:
-
开发模式:
cheap-module-source-map
- 优点:打包编译速度快,只包含行映射
- 缺点:没有列映射
js
module.exports = {
mode: "development",
devtool: "cheap-module-source-map",
};
-
生产模式:
source-map
- 优点:包含行/列映射
- 缺点:打包编译速度更慢
生产模式也有可能不开启sourceMap
,会部生成专门的sourceMap
文件
js
module.exports = {
// 其他省略
mode: "production",
devtool: "source-map",
};
2、提升打包构建速度
1、HotModuleReplacement
1、为什么?
在没有使用热模块替换
功能的时候,当我们修改了项目中一个模块的代码,webpack
就会将所有模块重新编译打包,这样速度回很,还会刷新整个页面。
所以我们就需要一个机制,就是当我们修改了某一模块,只需要重新打包编译当前模块,其他模块不变,这样就能大大提升开发环境编译的速度。
2、是什么?
HotModuleReplacement(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而不需要重新加载整个页面。
3、怎么用?
css 已经经过 style-loader
处理过了,已经有HMR
的功能了,此时 js
还不行。
基本配置
js
module.exports = {
// 其他省略
devServer: {
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true, // 是否自动打开浏览器
hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
},
};
js 配置
js
// 判断是否支持 HMR 功能
if (module.hot) {
module.hot.accept("./js/count.js", function (count) {
const result1 = count(2, 1);
console.log(result1);
});
module.hot.accept("./js/sum.js", function (sum) {
const result2 = sum(1, 2, 3, 4);
console.log(result2);
});
}
2、oneOf
1、为什么?
webpack
打包时,每个文件都会经过所有的loader
处理,就算有test
进行匹配,没有进行实际上的处理,但是都需要过一遍,比较慢。
2、是什么?
就是只能匹配上一个loader
,剩下的就不进行匹配了。
3、怎么用?
js
// 加载器
module: {
rules: [
{
oneOf: [
... 原有的 rule 规则
],
},
],
},
生产环境也这样配置
3、include/exclude
1、为什么?
开发的时候我们安装的第三方库或者插件,都下载到了node_modules
中了,这些依赖包在内部已经生成好了编译后的代码了,所以不在需要webpack
对其进行编译。所以我们在对 js 文件进行处理的时候,就需要排除这些node_modules
下面的依赖包。
2、是什么?
- include 包含,只处理 包含的文件
- exclude 排除,处理 排除文件之外的文件
3、怎么用?
开发环境配置
js
{
test: /\.js$/,
// exclude: /node_modules/, // 排除 node_modules 下面的代码不进行编译
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
},
plugins: [
new ESlintWebpackPlugin({
// 指定检查文件的根目录
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", // 默认值
})
],
生产环境也是同样的配置
3、Cache
1、为什么?
每次打包的时候 js
文件都要经过 eslint
检查和 babel
编译,比较慢。
我们可以缓存之前 eslint
检查和 babel
编译的结果,这样在第二次构建的时候速度就会更快了。
2、是什么?
对 eslint
检查和 babel
编译结果进行缓存。
3、怎么用?
js
可以观察一下第一次耗时与开启后的耗时
第一次
第二次
还有更多配置参见 babel-loader 文档
4、Thread
1、为什么?
当我们的项目越来越大的时候,打包构建的速度越来越慢的时候,这个时候我们就要想办法提高打包的效率,对于我们来说其实就是提高 js
打包的效率,因为其他的文件数量相对较少。
而处理 js
文件的主要就是 eslint
babel
Terser
这三个工具,因此我们要提高他们的运行速度。
我们可以采取开启多进程同时处理 js
文件,这样的速度就会比之前的单进程打包更快。
2、是什么?
多进程打包:开启电脑的多个进程同时干一件事情,速度更快。
注意:仅适合在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右的开销。
3、怎么用?
启动的进程数就是我们电脑 cpu
的核心数
js
const os = require("os");
// 获取 cpu 核数
const threads = os.cpus().length;
安装 thread-loader
shell
yarn add thread-loader
webpack 配置
js
const TerserPlugin = require("terser-webpack-plugin");
// babel-loader
{
test: /\.js$/,
exclude: /node_modules/, // 排除 node_modules 下面的代码不进行编译
use: [
{
loader: "thread-loader",
options: {
workers: threads,
},
},
{
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
},
},
],
},
// eslint
new ESlintWebpackPlugin({
...
threads,
}),
// 压缩
optimization: {
minimize: true,
minimizer: [
// css压缩也可以写到optimization.minimizer里面,效果一样的
new CssMinimizerPlugin(),
// 当生产模式会默认开启TerserPlugin,如果我们有其他的配置就要在这里进行重写
new TerserPlugin({
parallel: threads, // 开启多进程
}),
],
},
更多信息参见 优化(Optimization)
因为当前我们的代码内容很少,又因为开启了进程的原因,所以实际上我们的打包时间会变长。所以大项目才使用多进程打包。
开启多进程以后的
可以与之前的进行对比
3、减少代码体积
1、tree shaking
1、为什么?
开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。
如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。
这样将整个库都打包进来,体积就太大了。
2、是什么?
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 import
和 export
。这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。
注意:它依赖
Es Module
3、怎么用?
我们新建src/js/math.js
js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
在 main.js 中使用
js
import { cube } from "./js/math";
console.log(cube(5));
然后我们先给webpack.prod.js
配置文件中的 mode
改成 development
然后进行打包
再改回 production
再看打包结果,是不是很清晰,只会把用到的代码打包进结果里面。
更多用法请参见 Tree Shaking 文档
webpack production
模式已经默认开启了这个功能。
2、babel
1、为什么?
Babel 在每个文件都插入了辅助代码,使代码体积过大!
Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend
。默认情况下会被添加到每一个需要它的文件中。
你可以引入 Babel runtime 作为一个独立模块,来避免重复引入。
注意 :你必须执行 npm install -D @babel/plugin-transform-runtime
来把它包含到你的项目中,然后使用 npm install @babel/runtime
把 @babel/runtime
安装为一个依赖。
2、是什么?
@babel/plugin-transform-runtime
配置禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime
并且使所有辅助代码从这里引用。
更多信息请查看 文档。
3、怎么用?
下载包
js
yarn add @babel/plugin-transform-runtime -D
配置
js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 排除 node_modules 下面的代码不进行编译
use: [
...
{
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
presets: ["@babel/preset-env"],
plugins: ["@babel/plugin-transform-runtime"],
},
},
],
},
];
}
更多信息请参见文档 babel-loader
这里说的 babel
产生的辅助代码,目前没有测试出来,什么情况下会产生辅助代码,有了解的小伙伴可以评论一波哦!!!
4、优化代码运行性能
1、code split
1、为什么?
打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。
所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。
2、是什么?
代码分割(Code Split)主要做了两件事:
- 分割文件:将打包生成的文件进行分割,生成多个 js 文件。
- 按需加载:需要哪个文件就加载哪个文件
3、怎么用?
代码分割实现方式有不同的方式,为了更加方便体现它们之间的差异,我们会分别创建新的文件来演示
- 多入口配置
文件目录
arduino
├── public
├── src
| ├── app.js
| └── main.js
├── package.json
└── webpack.config.js
app.js
js
console.log("this is app");
main.js
js
console.log("this is main");
运行打包命令
shell
yarn build
- 提取重复代码
如果多入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复,体积更大。
我们需要提取多入口的重复代码,只打包生成一个 js 文件,其他文件引用它就好。
代码如下,两个入口文件都引用了 math.js
新手可以跟着 视频课程 学习,有基础的直接跟着文章敲一遍感受一下。