webpack 的基础内容

1 views

webpack 的基础内容
Loading...

一、webpack的能力

  1. 编译代码能力: 提高效率,解决浏览器兼容问题。(ES6+ => ES5)

编译

  1. 模块整合能力:提高性能,可维护性,解决浏览器频繁请求文件。

模块整合

  1. 万物皆可模块化:项目维护性增强,支持不同种类的模块类型,统一的模块化方案,所有的资源文件都可通过代码控制。

模块

二、webpack热更新实现及原理

HRM 全程 Hot Module Replacement,可以理解为模块热替换,指在应用程序运行中,替换、添加、删除模块,而无需重新刷新整个应用。

HMR 的实现原理主要依靠以下两个工具

  • webpack-dev-server: 它是一个开发服务器,可以提供实时编译和热更新功能。
  • HotModuleReplacementPlugin: 它是一个 webpack 插件,用于在模块更新时触发热更新。

以下是 HMR 的工作流程

  1. 启动 webpack-dev-server: 服务器会监听文件更改,并启动 webpack 进行编译。
  2. 浏览器端: 浏览器端会加载一个包含 HMR 客户端的 JavaScript 文件。
  3. 模块更新: 当文件发生更改时,webpack-dev-server 会重新编译代码,并生成新的模块。
  4. HMR 客户端: HMR 客户端会检测到模块更新,并向服务器发送请求。
  5. 服务器端: 服务器会将更新后的模块发送给浏览器端。
  6. 浏览器端: 浏览器端会应用更新后的模块,并刷新页面部分内容。

HMR 的优势:

  • 提高开发效率: 可以节省重新加载页面和重新执行 JavaScript 代码的时间。
  • 减少错误: 可以快速查看代码更改的效果,并及时发现错误。
  • 更好的开发体验: 可以让开发人员专注于代码本身,而不必担心页面刷新带来的影响。

HMR 的局限性:

  • 仅适用于开发环境: HMR 仅适用于开发环境,在生产环境中通常不使用。
  • 对代码结构有要求: HMR 需要代码结构合理,才能正确识别模块更新。
  • 可能存在兼容性问题: HMR 可能与某些库或框架存在兼容性问题。

三、webpack构建流程

3.1 运行流程

webpack 的运行流程是一个串行的过程,他的工作流程就是将各个loader和plugin串联起来。

在运行过程中广播事件,插件只需要监听它所关心的事件,就能加入到这条webpack机制中,去改变 webpack 的运作,使得这个那个系统扩展性良好。

从启动到结束会一次执行一下三个大步骤:

  • 初始化流程:从配置文件和 Shell 语句中 读取与合并参数,并初始化使用的插件和配置插件等执行环境所需要的参数。
  • 编译流程构建:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到 Module 依赖的 Module ,递归地进行编译处理
  • 输出流程:对编译后的 Module 组合成 Chunk , 把 Chunk 转换成文件,输出到文件系统。

运行流程

3.1.1 初始化流程

从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。

配置文件默认下为 webpack.config.js , 或者也可以通过命令的形式指定配置文件,主要作用是用于激活 webpack 的加载项和插件。

初始化流程

webpack 将 webpack.config.js 中的配置项拷贝到 options 对象中,并加载用户配置的 plugins

完成上述步骤之后,则开始初始化 Compiler 编译对象,该对象掌控者 webpack 生命周期,不执行具体的任务,只是进行一些调度工作。

调度工作

Comiler 对象继承自 Tapable, 初始化时定义了很多钩子函数。

3.1.2 编译构建流程

根据配置中的 entry 找出所有的入口文件

module.exports = {
entry: './src/index.js'
};

初始化完成后会调用 Compiler 的 run 来真正启动 webpack 编译构建流程,主要流程如下:

  • compile 开始编译
  • make 从入口点分析模块及其依赖的模块,创建这些模块对象
  • build-module 构建模块
  • seal 封装构建结果
  • emit 把各个 chunk 输出到结果文件
3.1.2.1 compile 编译

执行了 run 方法后,会触发 compile , 主要是构建一个 Compilation 对象。

该对象是编译阶段的主要执行者,主要会依次执行下述流程:执行模块创建、依赖收集、分块、打包等主要任务的对象。

3.1.2.2 make 编译模块

当完成了上述的 compilation 对象后, 就开始从 Entry 入口文件开始读取,主要执行 addModuleChain() 函数, 如下:

make 编译模块

addModuleChain 中接收参数 dependency 传入的入口依赖,使用对应的工厂函数 NormalModuleFactory.create 方法生成一个空的 module 对象。

回调中会把此 module 存入 comilation.modules 对象和 dependencies.module 对象汇总,由于是入口文件,也会存入 compilation.entries 中,随后执行 buildModule 进入真正的构建模块 Module 内容的过程。

3.1.2.3 build module 完成模块编译

这里主要调用配置的 loaders ,将我们的模块转成标准的 JS 模块。

在用 Loader 对一个模块转换完成后,使用 acorn 解析转换后的内容,输出对应的抽象语法数(AST),以方便 Webpack 后面对代码的分析。

从配置的入口模块开始,分析其 AST,当遇到 require 等导入其它模块语句时,便将其加入到依赖模块的列表中,同时对新找出的依赖··················模块递归分析,最终搞清楚所有的模块依赖关系。

3.1.3 输出流程

3.1.3.1 seal 输出资源

seal 方法主要是生成 chunks , 对chunks 进行一系列的优化操作,并生成输出的代码。

webpack 中的 chunk,可以理解为配置在 entry 中的模块,或者是动态引入的模块

根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk, 再把每个 Chunk 转换成一个单独的文件加入到输出列表。

3.1.3.2 emit 输出完成

在确定好输出内容后,根据配置确定输出的路径和文件名

output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js'
}

在 Compiler 开始生成文件前,钩子 emit 会被执行,这是我们修改最终文件的最后一次机会,从而 webpack 整个打包过程就结束了。

整个打包过程

四、webpack proxy 工作原理?为什么能解决跨域?

4.1 是什么

webpack proxy 即 webpack 提供的代理服务

基本行为就是接收客户端发送的请求后转发给其他服务器。

其目的是为了便于开发者在开发环境下解决跨域为(浏览器安全策略限制) 想要实现代理首先需要一个中间服务器, webpack 中提供服务器的工具为 webpack-dev-server

4.1.1 webpack-dev-server

webpack-dev-server 是 webpack 官方推出的一款开发工具,将自动编译和热更新等一系列对开发者友好的功能全部集成在了一起,目的是为了提高开发者日常的开发效率。 只适用于开发阶段

关于配置方面,在 webpack 配置对象属性中通过 devServer 属性提供,如下:

webpack-dev-server配置

devServer 里面的 proxy 则是关于代理的配置,该属性为对象的形式,对象中每一个属性就是一个代理的规则匹配

属性的名称是需要被代理的请求路径前缀,一般是为了辨别都会设置前缀为 /api ,值对应的代理匹配规则,对应如下:

  • target: 表示的是代理到的目标位置
  • pathRewite: 默认情况下,我们的 /api-hy 也会被写入到 url 中,如果希望删除,可以使用pathRewrite
  • secure:默认情况下不接收转发到 https 的服务器上,如果希望支持,可以设置成false
  • changeOrigin: 表示时候更新代理后请求的 headers 中 host 地址。

4.2 工作原理

proxy 工作原理实质上是利用 http-proxy-middleware 这个 http 代理中间件,实现请求转发给其他服务器。

举个例子:

在开发阶段,本地地址为 ,该浏览器发送一个前缀带有 /api 表示的请求到服务器获取数据,但响应这个请求的服务器只是将请求转发到另一台服务器中。

proxy配置

4.3 跨域

在开发阶段,webpack-dev-server 会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost 的一个端口上,而服务端又是在另外一个地址上,所有在开发阶段中,由于浏览器同源策略的原因,当本地访问服务端就会出现跨域请求的问题。

通过设置 webpack proxy 实现代理请求后,相当于浏览器与服务端中添加一个代理者。当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器目标服务器响应后数据再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地

跨域原理

在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候本地浏览器就能正常接收数据。

注意**:服务器和服务器之间请求数据不会存在跨域行为,跨域行为是浏览器安全策略限制。**

五、webpack 中常见的Loader?解决了什么问题?

5.1 是什么

loader 用于对模块的 ”源代码“ 进行转换,在 import 或 “加载” 模块时预处理文件。

webpack 做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中。如下图:

中常见的Loader?解决了什么问题

在 webpack 内部中, 任何文件都是模块,不仅仅只是 js 文件

默认情况下,在遇到 import 或者 require 加载模块的时候, webpack 只支持对 js 和 json 文件打包。像 css 、sass 、png 等这些文件类型的时候, webpack 则无能为力,这时候就需要配置对应的 loader 进行文件内容的解析。

在加载模块的时候,执行顺序如下:

执行顺序

当 webpack 碰到无法识别的模块的时候, webpack 会在配置中查找该文件解析规则,关于配置 loader 的方式有三:

  • 配置方式(推荐):在 webpack.config.js 文件中指定 相关loader
  • 内联方式:在每个 import 语句中显示指定 loader
  • CLI 方式:在shell 命令中指定它们

5.1.1 配置方式

关于 loader 的配置,我们是写在 module.rules 属性中,属性介绍如下:

  • rules 是一个数组的形式,因此我们可以配置很多个 laoder
  • 每一个 loader 对应一个 对象的形式,对象属性 test 为匹配的规则,一般情况下是正则表达式
  • 属性 use 针对匹配到文件类型,调用对应的 loader 进行处理

代码编写,如下形式:

配置方式

5.2 特性

从上述的代码可以看到,在处理 css 模块的时候, use 属性中配置了三个 loader 分别处理 css 文件。因为 loader 支持链式调用,链中的每个 loader 会处理之前已经处理过的资源,最终变为 js 代码。顺序为相反的顺序执行,即上述的执行方式为 sass-loader -> css-loader => style-loader 除此之外, loader 的特性还有如下:

  • loader 可以是同步的,也可以是异步的
  • loader 运行在 Node.js 中,并且能够执行任何操作
  • 除了常见的通过 package.json 的main 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块
  • 插件(plugin)可以为 loader 带来更多特性
  • loader 能够产生额外的任意文件

可以通过 loader 的预处理函数,为 Javascript 生态提供更多能力,用户现在可以更加灵活地引入细粒度逻辑,列入:压缩、打包、语言翻译和更多其他特性。

六、说说webpack中常见的Plugin?解决了什么问题

6.1 是什么

Plugin 是一种计算机应用程序,它和主应用程序互相交互,以提供特定的功能。是一种遵循一定规范的应用程序接口编写出来的程序,只运行在程序规定的系统下,应为其需要调用原纯净系统同的函数库或者数据。

webpack 中的 plugin 也是如此,plugin 赋予其各种灵活地功能,例如打包优化、资源管理、环境变量注入等,他们会运行在 webpack 的不同阶段(钩子、生命周期),贯穿了 webpack 整个编译周期。

Plugin

目的是解决 loader 无法实现的其他事。

6.1.1 配置方式

一般情况下,通过配置文件导出的对象中 plugins 属性传入 new 实例对象。如下所示:

const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack');
module.exports = {
...
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({ template: './src/index.html' })
]
}

6.2 特性

其本质是一个具有 apply 方法 Javascript 对象

apply 方法会被 webpack compiler 调用,并且在整个编译周期都可以访问 compiler 对象。

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, (compilation) => {
console.log('webpack 构建过程开始');
})
}
}
module.exports = ConsoleLogOnBuildWebpackPlugin;

compiler hook 的 tap 方法的第一个参数,应是驼峰命名的插件名称,关于整个编译生命周期钩子,有如下:

  • entry-option: 初始化option
  • run
  • compile:真正开始的编译,在创建 compilation 对象之前
  • compilation:生成好了 compilation 对象
  • make:从 entry 开始递归分析依赖,准备对每个模块进行 build
  • after-compile: 编译 build 过程结束
  • emit: 在将内存中 assets 内容写到磁盘文件夹之前
  • after-emit:在将内存中 asets 内容写到磁盘文件夹之后
  • done:完成所有的编译过程
  • failed:编译失败的时候

6.3 常见的Plugin

常见的Plugin

七、说说 Loader 和 Plugin的区别?编写 Loader 和 Plugin 的思路?

7.1 区别

五、六分别介绍了 Loader 与 Plugin 对应的概念。

  • loader:是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中。
  • plugin: 赋予了 webpack 各种灵活地功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事。

从整个运行时间上来看,如下图所示:

整个运行流程

可以看到,两者在运行时机上的区别:

  • loader: 在运行打包文件之前
  • plugins:在整个编译周期都起作用

在 webpack 运行的生命周期中会广播出很多事件,Plugin 可以监听到这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果

对于 loader ,实质上是一个转换器,将 A 文件进行编译形成 B文件,操作的是文件,比如 A.scss 装变成 B.css ,单纯的文件转换过程。

示例:

  • Loader: 使用 babel-loader 将 TypeScript 转换为 JavaScript。
  • Plugin: 使用 UglifyJsPlugin 压缩代码。

总结:

Loader 和 Plugin 是 webpack 中两个重要的概念。它们都用于扩展 webpack 的功能,但它们的工作方式有所不同。选择使用 Loader 还是 Plugin 取决于您的具体需求。

以下表格总结了 Loader 和 Plugin 的区别:

特性LoaderPlugins
工作方式在模块编译阶段工作在webpack 整个生命周期工作
功能转换模块扩展webpack 功能
粒度通常针对单个模块通常针对整个项目
使用场景转换模块格式,或在模块编译阶段执行自定义操作扩展 webpack 的功能,或在 webpack 构建的不同阶段执行自定义操作

7.2 编写loader

loader的本质为函数,函数中的 this 作为上下文会被 webpack 填充,因此我们不能将 loader 设为 一个箭头函数。

函数接受一个参数,为 webpack 传递给 loader 的文件源内容

函数中 this 是由 webpack 提供的推向,能够获取当前 loader 所需要的各种信息。

函数中有异步或者同步操作,异步操作通过 this.callback 返回,返回值要求为 string 或者 Buffer

代码如下所示:

// 导出一个函数,source 为 webpack 传递给loader 的文件源内容
module.exports = function (source) {
const content = doSomeThingToJsString(source);
// 如果loader 配置了 options对象,那么 this.query 将指向 options
const options = this.query;
// 可以用作解析其他模块路径的上下文
console.log('this.context');
/*
* this.callback 参数:
* error: Error | null,当 loader 出错是向外抛出一个error
* content: String | Buffer, 经过loader 编译后需要导出的内容
* ast: 本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,从而省去重复生成 AST 的过程。
*/
this.callback(null, content); // 异步
return content; // 同步
};

一般在编写 loader 的过程中,保持功能单一,避免做多种功能。

如 less 文件 转换成 css 文件也不是一步到位,而是 less-loader、css-loader、style-loader 几个 loader 的链式调用才能完成转换。

7.3 编写Plugins

由于 webpack 基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过这些事件,就可以在特定的阶段执行自己的插件任务

在之前了解过,webpack 编译会创建两个核心对象

  • compiler:包含了webpack 环境的所有配置信息,包括 options、loader 和 plugin,和 webpack 整个生命周期相关的钩子。
  • compilation: 作为 plugin 内置事件回调函数的参数,包含了当前的模块资源、编译生成资源、变化的文件及被跟踪依赖的状态信息。当检测到一个文件变化,一次新的 Compilation 将被创建。

编写Plugin需要遵循一定的规范

  • 插件必须是一个函数或者包含apply 方法的对象,这样才能访问 compiler 实例。
  • 传给每个插件的 compiler 和 compilation 对象都是同一个引用,因此不建议修改
  • 异步的事件需要再插件处理完成任务是调用回调函数通知 webpack 进行下一个流程,不然会卡主

实现 plugin 的模版如下:

class MyPlugin {
// webpack 会调用 MyPlugin 实例的 apply 方法给插件实例传入 complier 对象
apply(compiler) {
// 找到合适的事件钩子,实现自己的插件功能
compiler.hooks.emit.tap('MyPlugin', (compilation) => {
// compilation: 当前打包构建流程的上下文
console.log(compilation);
// do somethins...
});
}
}

在 emit 事件发生时,代表源文件的转换和组装已经完成,可以读取到最终将输出的资源、代码块。模块及其依赖,并且可以修改输出资源的内容。

八、如何提高webpack 构建速度?

常见的提升构建速度的手段有如下

  • 优化loader 配置
  • 合理使用 resolve.extensions
  • 优化 resolve.modules
  • 优化 resolve.alias
  • 优化 DLLPlugin 插件
  • 使用 cache-loader
  • teser 启动多线程
  • 合理使用 sourceMap

8.1 优化 loader 配置

在使用 loader 时,可以通过配置 include、exclude、test属性来匹配文件, include、exclude 规定哪些匹配应用 loader。

如采用 ES6 的项目为例,在配置 babel-loader 时,可以这样:

module.exports = {
module: {
rules: [
{
// 如果项目中只有js 文件就不要写成 /\.jsx?$/,提升表达式性能
test: \/.js$/,
// babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
use: ['babel-laoder?cacheDirectory']l
// 只针对根目录下的 src 目录中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
}
]
}
}

8.2 合理使用 resolve.extensions

在开发过程中我们会有各种各样的模块依赖,这些模块可能来源于自己编写的代码,也可能来自第三方库,resolve 可以帮助 webpack 从每个 require/import 语句中,找到需要引入到合适的模块代码,通过.resolve.extensions 是解析到文件时自动添加扩展名,默认情况如下:

module.exports = {
...
extensions: [".warm", 'mjs', 'js', 'json']
}

当我们引入文件的时候,如果没有文件后缀名,则会根据数组内的值一次查找

当我们配置的时候,则不要随便把所有后缀名都写在里面,这会调用多次文件的查找,这样就会减慢打包速度。

8.3 优化 resolve.modules

resolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块。默认值为 [‘node_modules’], 所以会默认从 node_modules 中查找文件

当安装的第三方模块都放在根目录下的 ./node_moduels 目录时,可以指明存放第三方模块的绝对路径,减少寻找过程。配置如下:

module.exports = {
resolve: {
// 使用绝对路径指明第三方模块存放的位置,减少搜索步骤
modules: [path.resolve(__dirname, 'node_modules')]
}
};

8.4 优化 resolve.alias

alias 给一些常用的路径起一个别名,特别当我么的项目目录结构比较深的时候,一个文件的路径可能是 ./../../ 的形式

通过配置 alias 以减少查找过程

module.exports = {
...
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
}

8.5 使用 DLLPlugin 插件

DLL 全程是 动态链接库,是为软件在 windows 实现共享函数库的一种实现方式,而 webpack 也内置了 DLL 的功能,为的就是可以共享不经常改变的代码,抽成一个共享的库,这个库在编译过程中,会被引入到其他项目的代码中。

使用步骤分为两部分

  • 打包一个DLL 库
  • 引入DLL库

8.5.1 打包一个 DLL 库

webpack 内置了一个 DLLPlugin 可以帮助我们打包一个DLL库文件

module.exports = {
...
pugins: [
new webpack.DLLPlugin({
name: 'dll_[name]',
path: path.resolve(__dirname, './dll/[name].mainfest.json')
})
]
}

8.5.2 引入DLL库

使用 webpack 自带的 DLLRefererncePlugin 插件对 mainfest.json 映射文件进行分析,获取要使用的 DLL 库

然后通过 AddAssetHtmlPlugin 插件,将我们打包的 DLL 库 引入到 Html 模板中。

引入DLL库

8.6 使用 cache-loader

在一些性能开销较大的 laoder 之前添加 cache-loader,将结果缓存到磁盘里,显著提升二次构建速度

保存和读取这些缓存文件会有一些时间开销,所以只对性能大的loader使用

cache-loader

8.7 terser 启动多线程

使用多线程并运行来提高构建速度, 主要体现在压缩代码上。

和Tree shaking的区别:

特性TerserPluginTree shaking
工作方式在代码压缩阶段工作在代码打包阶段工作
功能压缩 JavaScript 代码移除未使用的代码
效果提高代码执行效率减少代码体积
使用场景生产环境开发环境和生产环境

terser 启动多线程

8.8 合理使用 sourceMap

打包生成 souceMap 的时候,如果信息越详细,打包速度越慢。对应属性取值如下所示:

sourceMap

九、如何借助webpack来优化前端性能?

9.1 背景

随着前端的项目逐渐扩大,必然会带来的一个问题就是性能,尤其是在大型复杂的项目中,前端业务可能因为一个小小的数据依赖,导致整个页面卡顿甚至崩溃,一般项目在完成后,会通过webpack进行打包,利用 webpack 对前端项目性能优化是一个十分重要的环节。

9.2 如何优化

通过 webpack 优化前端的手段有:

  • JS代码代码压缩
  • CSS 代码压缩
  • HTML 文件代码压缩
  • 文件大小压缩
  • 图片压缩
  • Tree Shaking
  • 代码分离
  • 内联 chunk

9.2.1 JS代码压缩

terser 是一个 JavaScript 的解释、绞肉机、压缩机的工具集,可以帮助我们压缩、简化我们的代码,让 bundle 更小

在 production 模式下,webpack 默认就是使用 TerserPlugin 来处理我们的代码。如果想要自定义配置他,配置方法如下:

JS代码压缩

属性介绍如下:

  • extractComments: 默认值为true,表示会将注释抽取到一个单独的文件中,开发阶段,我们可以设置为false,不保留注释。
  • parallel: 使用多进程并发运行提高构建的速度,默认值是true,并发运行的默认数量是 os.cpus().length - 1
  • terserOptions: 设置我们的terser相关的配置
  • compress: 设置压缩相关的选项
  • mangle:设置筹划相关的选项,可以直接设置为true
  • toplevel: 底层变量是否进行转换
  • keep_classnames: 保留类的名称
  • keep_frames: 保留函数的名称

9.2.2 CSS代码压缩

CSS 压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等 CSS 压缩我们班可以使用另外一个插件: css-minimizer-webpack-plugin

CSS代码压缩

9.2.3 Html 文件代码压缩

使用 HtmlWebpackPlugin 插件来生成 HTML 模板的时候,通常配置属性 minify 进行 html 优化

Html 文件代码压缩

设置了 minify,实际会使用另一个插件,html-minifier-terser

9.2.4 文件大小压缩

对文件大小进行压缩,减少http传输过程中带宽的损耗

文件大小压缩

9.2.5 图片压缩

一般来说在打包之后,一些图片文件的大小是远远比 js 或者 css 文件大的多,所以图片压缩较为重要

配置方法如下:

图片压缩

9.2.6 Tree Shaking

Tree Shaking 是一个术语,在计算机中表示消除死代码,依赖于 ES Module 的静态语法分析(不执行任何代码,可以明确知道模块的依赖关系)

在 webpack 实现 Tree Shaking 有两种不同的方案:

  • useExports: 通过标记某些函数是否被使用,之后通过Terser来进行优化
  • sideEffets: 跳过整个模块/ 文件,直接查看该文件是否有副作用

两种不同的配置方案,有不同的效果

9.2.6.1 usedExports

配置方法也很简单,只需要将 usedExports 设为 true

usedExports

使用之后,没被用上的代码在 webpack 打包中会加入 unused harmony export null 注释。用来告知 Terser 在优化时,可以删除掉这段代码。

9.2.6.2 sideEffects

sideEffects 用于告知 webpack compiler 哪些模块是有副作用,配置方法在 package.json 中设置 sideEffects 属性

如果 sideEffects 设置为 false,就是告知 webpack 可以安全的删除未使用到的 exports。如果有哪些文件需要保留,可以设置为数组的形式

sideEffects

上述都是关于 JavaScript 的 tree shaking, css 同样也能事项 tree shaking

9.2.6.3 css tree shaking

css 进行 tree shaking 优化可以安装 PurgeCss 插件

css tree shaking

  • paths: 表示要检测哪些目录下的内容需要被分析,配合使用 glob
  • 默认情况下,Purgecss 会将我们的html 标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属性

9.2.7 代码分离

将代码分离到不同的 bundle 中,之后我们可以按需加载, 或者并行加载这些文件

默认情况下,所有的 JavaScript 代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度。

代码分离可以分出更小的 bundle ,以及控制资源加载的优先级,提供代码的加载性能

这里通过 splitChunksPlugin 来实现,该插件 webpack 已经默认安装和集成,只需要配置即可

默认配置中,chunks 仅仅针对于 异步(async) 请求,我们可以设置为 initial 或者 all

代码分离

splitChunks 主要属性有如:

  • Chunks: 对代码进行处理
  • minSize:拆包的大小,至少为minSize,如果包的大小不超过minSize,这个包不会被拆分
  • maxSize:将大于maxSize的包拆分成不小于minSize的包
  • minChunks: 被引入的次数,默认是1

9.2.8 内联chunk

可以通过 InlineChunkHtmlPlugin 插件将一些 chunk 的模块内联到 html,如runtime 的代码(对模块进行解析、加载、模块信息相关的代码),代码量不是很大,但是必须加载

内联chunk

9.3 总结

关于 webpack 对前端性能的优化,可以通过体积大小入手,其次还可以通过分包的形式、减少http请求次数等方向,实现对前端性能的优化。

十、与webpack类似的工具有哪些?区别?

10.1 模块化工具

模块化是一种处理复杂系统分解为更好的可管理模块的方式

可以用来分割,组织和打包应用。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体(bundle)

在前端领域中,并非只有 webpack 这一款优秀的模块打包工具,还有其他类似的工具,例如 Rollup 、Parcel、Snowpack、Vite

通过这些模块打包工具,能够提高我们的开发效率,减少开发成功

这里没有提及 gulp、grunt 是因为他们只是定义为构建工具,不能类比

10.1.1 Rollup

Rollup 是一款 ES Module 打包器,从作用上来看,Rollup 与 webpack 非常类似,不过相比于 Webpack, Rollup 要小巧的多

现在很多我们熟悉的库都是使用他进行打包,比如:Vue、React等

Rollup

打包结果

打包结果

可以看到,代码非常简洁,完全不像 webpack 那样存在大量引导代码和模块函数

并且 error 方法由于没有被使用,输出的结果并无 error 方法,可以看到,Rollup 默认开启 Tree-Shaking 优化输出结果

因此,可以看到 Rollup的优点

  • 代码更简洁、效率更高
  • 默认支持 Tree-Shaking

缺点也十分明显,加载其他的资源文件或者支持导入 CommonJS 模块,又或是编译 ES 新特性,这些额外的需求 Rollup 需要使用插件区完成

综合来看,Rollup 并不适合开发应用使用,因为需要使用第三方模块,而目前第三方模块大多数使用 CommnJS 方式导出成员,并且 Rollup 不支持 HMR,开发效率降低

但是在用于打包 JavaScript 库时,Rollup 比 webpack 更有优势,因为其打包出来的代码更小、更快。

10.1.2 Vite

vite,是一种新型前端构建工具,能够显著提升前端开发体验

主要有两部分组成:

  • 一个开发服务器,它基于原生 ES 模块,通过了丰富的内建功能,如速度快到惊人的模块热更新
  • 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可以输出用于生成环境的优化过的静态资源。

其作用类似 webpack + webpack-dev-server,特点如下:

  • 快速的冷启动
  • 即时的模块热更新
  • 真正的按需编译

vite 会直接启动开发服务器,不需要进行打包操作,也就以为这不需要分析模块的依赖,不需要编译,因此启动速度非常快

利用现在浏览器支持 ES Module 的特性,当浏览器请求某个模块的时候,在根据需要对模块的内容进行编译,这种方式大大缩短了编译时间

原理图如下:

vite 原理

在热更新 HMR 方面,当修改一个模块的时候,仅需要让浏览器重新请求该模块即可,无需像 webpack 那样需要把该模块的相关依赖模块全部编译一次,效率更高