深入浅出 Webpack

金果 2021年10月5日15:34:38
评论
2,143

 

深入浅出 Webpack

Web 应用日益复杂,相关开发技术也百花齐放,这对前端构建工具提出了更高的要求。Webpack 从众多构建工具中脱颖而出成为目前最流行的构建工具,几乎成为目前前端开发里的必备工具之一,因此每位紧跟时代的前端工程师都应该掌握 Webpack。

一、Webpack 入门

1.1 前端的发展

近年来 Web 应用变得更加复杂与庞大,Web 前端技术的应用程序也更加广泛。从管理后台到移动网页,再到 RN 的原生应用开发方案,直接编写 JS、CSS、HTML的方式已经无法应对当前开发 Web 应用。近年来前端社区涌现出许多新思想和框架,如下:

1.1.1 模块化

模块化是把一个复杂的系统分解到多个模块以方便编码。以前开发网页通过命名空间方式来组织代码,例如 jQuery 库把 API 放在 window.$ 。

  • CommonJS
  • AMD
  • ES6 模块化
  • 样式文件的模块化

1.1.2 新框架

在 Web 应用变的庞大复杂时,采用直接操作 DOM 的方式去开发将会使代码变得复杂和难以维护,许多新思想引入到网页开发中减少开发难度、提升开发效率。

  • React
  • Vue
  • Angular2

1.1.3 新语言

新语言可以提升编码效率,但是必须把源代码转换成可以直接在浏览器环境下运行的代码。

  • ES6
  • TypeScript
  • Flow
  • SCSS

1.2 常见的构建工具及对比

构建就是把源代码转换成发布到线上可执行 JavaScript、CSS、HTML 代码,包括如下内容:

  • 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。
  • 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
  • 代码分割:提取多个页面公共代码、提取首屏不需要执行部分代码让其异步加载。
  • 模块合并:在采用模块化的项目会有多个模块和文件,把模块分类合并成一个文件。
  • 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
  • 代码校验:代码提交检验代码是否符合规范。
  • 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。

历史上先后出现一系列构建工具,它们各有其优缺点,如下:

1.2.1 Npm Script

Npm Script 是一个任务执行者。Npm 是在安装 Node.js 时附带的包管理器,Npm Script 则是 Npm 内置的一个功能,里面的 scripts 字段是一个对象,每个属性对应一段 Shell 脚本。

Npm Script 的优点是内置,无须安装其他依赖。缺点是功能太简单,不能方便管理多个任务之间的依赖。

1.2.2 Grunt

Grunt 也是一个任务执行者。Grunt 有大量现成的插件封装了常见的任务,也能管理任务之间的依赖关系,自动化执行依赖的任务,每个任务的具体执行代码和依赖关系写在配置文件 Gruntfile.js 里。

Grunt 的优点是灵活,负责执行你定义的任务;大量的可复用插件封装好了常见的构建任务。缺点是集成度不高,要写很多配置后才可以用,无法做到开箱即用。

1.2.3 Gulp

Gulp 是一个基于流的自动化构建工具。除了可以管理和执行任务,还支持监听文件、读写文件。 Gulp最大特点是引入流的概念,同时提供一系列常用的插件去处理流,流可以在插件之间传递。

Gulp 的优点是好用又不失灵活,既可以单独完成构建也可以和其它工具搭配使用。缺点是集成度不高,要写很多配置后才可以使用,无法做到开箱即用。

1.2.4 Fis3

Fis3 是一个来自百度的优秀国产构建工具。相对于 Grunt、Gulp 这些只提供基本功能的工具,Fis3 集成流 Web 开发中的常用构建功能。

Fis3 的优点是集成了各种 Web 开发所需要的构建功能,配置简单开箱即用。缺点是目前官方不再更新和维护,不支持最新版本的 Node.js。

1.2.5 Webpack

Webpack 是一个打包模块化 JavaScript 的工具,在 Webpack 里一切文件皆模块,通过 Loader 转换文件,通过 Plugin 注入钩子,最后输出由多个模块组成的文件。Webpack 专注于构建模块化项目。

Webpack 的优点是专注于处理模块化的项目,能做到开箱即用;通过 Plugin 扩展,完整好用不失灵活;使用场景不限于 Web 开发;社区庞大活跃。缺点是只能用于采用模块化开发的项目。

1.2.6 Rollup

Rollup 是一个和 Webpack 很类似但专注于 ES6 的模块打包工具。Rollup 的亮点在于能针对 ES6 源码进行 Tree Shaking 去除那些已被定义但没被使用的代码,以及 Scope Hoisting 减小输出文件大小提升运行性能。然而这些亮点随后就被 Webpack 模仿和实现。

Rollup 的优点是打包出来的代码更小更快。缺点是功能不够完善,很多场景都找不到现成的解决方案。

1.3 Webpack 安装与使用

在用 Webpack 执行构建任务时需要通过 webpack 可执行文件去启动构建任务,所以需要安装 webpack 可执行文件。在安装 webpack 之前需要保证系统安装了 Node.js。

创建项目并通过 npm 安装好 webpack 后,在项目根目录下执行 webpack 命令运行构建,会发现目录下多出一个 dist 目录,里面就是打包构建后的文件。

1.4 使用 Loader

Loader 可以看作具有文件转换功能的翻译员,配置里的 module.rules 数组配置了一组规则,告诉 Webpack 在遇到哪些文件时使用哪些 Loader 去加载和转换。在配置 Loader 时需要注意的是:

  • use 属性的值需要是一个由 Loader 名称组成的数组,Loader 的执行顺序是由后到前的;
  • 每一个 Loader 都可以通过 URL querystring 的方式传入参数,例如 css-loader?minimize 中的 minimize 告诉 css-loader 要开启 CSS 压缩。

1.5 使用 Plugin

Plugin 是用来扩展 Webpack 功能的,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性。

Webpack 是通过 plugins 属性来配置需要使用的插件列表的。 plugins 属性是一个数组,里面的每一项都是插件的一个实例,在实例化一个组件时可以通过构造函数传入这个组件支持的配置属性。

1.6 使用 DevServer

DevServer 会启动一个 HTTP 服务器用于服务网页请求,同时会帮助启动 Webpack ,并接收 Webpack 发出的文件更变信号,通过 WebSocket 协议自动刷新网页做到实时预览。

1.6.1 实时预览

Webpack 在启动时可以开启监听模式,开启监听模式后 Webpack 会监听本地文件系统的变化,发生变化时重新构建出新的结果。Webpack 默认是关闭监听模式的,你可以在启动 Webpack 时通过 webpack --watch 来开启监听模式。

通过 DevServer 启动的 Webpack 会开启监听模式,当发生变化时重新执行完构建后通知 DevServer。 DevServer 会让 Webpack 在构建出的 JavaScript 代码里注入一个代理客户端用于控制网页,网页和 DevServer 之间通过 WebSocket 协议通信, 以方便 DevServer 主动向客户端发送命令。 DevServer 在收到来自 Webpack 的文件变化通知时通过注入的客户端控制网页刷新。

1.6.2 模块热替换

模块热替换能做到在不重新加载整个网页的情况下,通过将被更新过的模块替换老的模块,再重新执行一次来实现实时预览。 模块热替换相对于默认的刷新机制能提供更快的响应和更好的开发体验。 模块热替换默认是关闭的,要开启模块热替换,你只需在启动 DevServer 时带上 --hot 参数,重启 DevServer 后再去更新文件就能体验到模块热替换的神奇了。

1.6.3 支持 Source Map

在浏览器中运行的 JavaScript 代码都是编译器输出的代码,这些代码的可读性很差。如果在开发过程中遇到一个不知道原因的 Bug,则你可能需要通过断点调试去找出问题。 在编译器输出的代码上进行断点调试是一件辛苦和不优雅的事情, 调试工具可以通过 Source Map 映射代码,让你在源代码上断点调试。 Webpack 支持生成 Source Map,只需在启动时带上 --devtool source-map 参数。

1.7 核心概念

虽然Webpack 功能强大且配置项多,但只要你理解了其中的几个核心概念,就能随心应手地使用它。 Webpack 有以下几个核心概念:

  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
  • Output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果。
  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
  • Plugin:扩展插件,在 Webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。

Webpack 启动后会从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module, 就会根据配置的 Loader 去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。最后 Webpack 会把所有 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。

二、Webpack 配置

2.1 Entry

Entry 是配置模块的入口,可抽象成输入,Webpack 执行构建的第一步将从入口开始搜寻及递归解析出所有入口依赖的模块。

Entry 配置是必填的,若不填则将导致 Webpack 报错退出。

Entry 类型可以是 string、array、object,如果是 string、array,就只会生成一个 chunk,这时 chunk 的名称是 main,如果是 object,就可能会出现多个 chunk,chunk 的名称就是 object 键值对里键的名称。

配置动态 Entry 的方式是设置成一个函数去动态返回配置。

2.2 Output

output 配置如何输出最终想要的代码。output 是一个 object,里面包含一系列配置项,下面分别介绍它们。

2.2.1 filename

output.filename 配置输出文件的名称,为 string 类型。其中常用的内置变量有name、hash、chunkhash、contenthash。

name 是 chunk 的名称。

hash 是构建生成的唯一 hash 值。针对构建的所有内容进行计算,多入口文件(app.js、main.js),如果只改变 app.js 的内容,那么即使 main.js 的内容不变,它的 hash 值也会改变。

chunkhash 是根据不同的 entry 进行依赖解析构建对应的 chunk,生成对应的 hash 值。多入口文件中 app.js 引入了 app.css,那么改变 app.css,main.js 的 chunkhash 不变,但是 app.js 的 chunkhash 还是会改变。因为 app.js 和 app.css 都属于一个 chunk。

contenthash 是根据文件内容生成 hash 值。如果只改动 app.css 文件,则 app.js 的 contenthash 不会改变,只改变 app.css 文件本身的 contenthash。

2.2.2 chunkFilename

output.chunkFilename 配置无入口的 chunk 在输出时的文件名称。chunkFilename 和上面的 filename 非常类似,但 chunkFilename 只用于指定在运行过程中生成的 chunk 在输出时的文件名称。

2.2.3 path

output.path 配置输出文件存放在本地的目录,必须是 string 类型的绝对路径。

2.2.4 publicPath

在复杂的项目里可能会有一些构建出的资源需要异步加载,加载这些异步资源需要对应的 URL 地址。

output.publicPath 配置发布到线上资源的 URL 前缀,为string 类型。 默认值是空字符串 '',即使用相对路径。

2.2.5 crossOriginLoading

Webpack 输出的部分代码块可能需要异步加载,而异步加载是通过 JSONP 方式实现的。 JSONP 的原理是动态地向 HTML 中插入一个 <script src="url"></script> 标签去加载异步资源。 output.crossOriginLoading 则是用于配置这个异步插入的标签的 crossorigin 值。

2.2.6 libraryTarget 和 library

当用 Webpack 去构建一个可以被其他模块导入使用的库时需要用到它们。

  • output.libraryTarget 配置以何种方式导出库。
  • output.library 配置导出库的名称。

它们通常搭配在一起使用。

2.2.7 libraryExport

output.libraryExport 配置要导出的模块中哪些子模块需要被导出。 它只有在 output.libraryTarget 被设置成 commonjs 或者 commonjs2 时使用才有意义。

2.3 Module

2.3.1 配置 Loader

rules 配置模块的读取和解析规则,通常用来配置 Loader。其类型是一个数组,数组里每一项都描述了如何去处理部分文件。 配置一项 rules 时大致通过以下方式:

  1. 条件匹配:通过 test 、 include 、 exclude 三个配置项来命中 Loader 要应用规则的文件。
  2. 应用规则:对选中后的文件通过 use 配置项来应用 Loader,可以只应用一个 Loader 或者按照从后往前的顺序应用一组 Loader,同时还可以分别给 Loader 传入参数。
  3. 重置顺序:一组 Loader 的执行顺序默认是从右到左执行,通过 enforce 选项可以让其中一个 Loader 的执行顺序放到最前或者最后。

2.3.2 noParse

noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析和处理,这样做的好处是能提高构建性能。 原因是一些库例如 jQuery 、ChartJS 它们庞大又没有采用模块化标准,让 Webpack 去解析这些文件耗时又没有意义。

noParse 是可选配置项,类型需要是 RegExp、[RegExp]、function 其中一个。

2.3.3 parser

因为 Webpack 是以模块化的 JavaScript 文件为入口,所以内置了对模块化 JavaScript 的解析功能,支持 AMD、CommonJS、SystemJS、ES6。 parser 属性可以更细粒度的配置哪些模块语法要解析哪些不解析,和 noParse 配置项的区别在于 parser 可以精确到语法层面, 而 noParse 只能控制哪些文件不被解析。

2.4 Resolve

Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。 Webpack 内置 JavaScript 模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你也可以根据自己的需要修改默认的规则。

2.4.1 alias

resolve.alias 配置项通过别名来把原导入路径映射成一个新的导入路径。

2.4.2 mainFields

Webpack 会根据 mainFields 的配置去决定优先采用那份代码

2.4.3 extensions

在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试访问文件是否存在。 resolve.extensions 用于配置在尝试过程中用到的后缀列表。

2.4.4 modules

resolve.modules 配置 Webpack 去哪些目录下寻找第三方模块,默认是只会去 node_modules 目录下寻找。

2.4.5 descriptionFiles

resolve.descriptionFiles 配置描述第三方模块的文件名称,也就是 package.json 文件。

2.4.6 enforceExtension

resolve.enforceExtension 如果配置为 true 所有导入语句都必须要带文件后缀。

2.4.7 enforceModuleExtension

enforceModuleExtension 和 enforceExtension 作用类似,但 enforceModuleExtension 只对 node_modules 下的模块生效。

2.5 Plugins

Plugin 用于扩展 Webpack 功能,各种各样的 Plugin 几乎让 Webpack 可以做任何构建相关的事情。

Plugin 的配置很简单,plugins 配置项接受一个数组,数组里每一项都是一个要使用的 Plugin 的实例,Plugin 需要的参数通过构造函数传入。

2.6 DevServer

2.6.1 hot

devServer.hot 配置是否启用模块热替换功能。

2.6.2 inline

devServer.inline 用于配置是否自动注入这个代理客户端到将运行在页面里的 Chunk 里去,默认是会自动注入。

2.6.3 historyApiFallback

devServer.historyApiFallback 用于方便的开发使用了 HTML5 History API 的单页应用。 这类单页应用要求服务器在针对任何命中的路由时都返回一个对应的 HTML 文件。

2.6.4 contentBase

devServer.contentBase 配置 DevServer HTTP 服务器的文件根目录。

2.6.5 headers

devServer.headers 配置项可以在 HTTP 响应中注入一些 HTTP 响应头。

2.6.6 host

 

2.6.7 port

devServer.port 配置项用于配置 DevServer 服务监听的端口,默认使用 8080 端口。

2.6.8 allowedHosts

devServer.allowedHosts 配置一个白名单列表,只有 HTTP 请求的 HOST 在列表里才正常返回。

2.6.9 disableHostCheck

devServer.disableHostCheck 配置项用于配置是否关闭用于 DNS 重绑定的 HTTP 请求的 HOST 检查。

2.6.10 https

DevServer 默认使用 HTTP 协议服务,它也能通过 HTTPS 协议服务。

2.6.11 clientLogLevel

devServer.clientLogLevel 配置在客户端的日志等级,这会影响到你在浏览器开发者工具控制台里看到的日志内容。

2.6.12 compress

devServer.compress 配置是否启用 gzip 压缩。boolean 为类型,默认为 false。

2.6.13 open

devServer.open 用于在 DevServer 启动且第一次构建完时自动用你系统上默认的浏览器去打开要开发的网页。 同时还提供 devServer.openPage 配置项用于打开指定 URL 的网页。

2.7 其它配置项

2.7.1 Target

JavaScript 的应用场景越来越多,从浏览器到 Node.js,这些运行在不同环境的 JavaScript 代码存在一些差异。 target 配置项可以让 Webpack 构建出针对不同运行环境的代码。

2.7.2 Devtool

devtool 配置 Webpack 如何生成 Source Map,默认值是 false 即不生成 Source Map。

2.7.3 Watch 和 WatchOptions

Webpack 的监听模式,它支持监听文件更新,在文件发生变化时重新编译。

2.7.4 Externals

Externals 用来告诉 Webpack 要构建的代码中使用了哪些不用被打包的模块,也就是说这些模版是外部环境提供的,Webpack 在打包时可以忽略它们。

2.7.5 ResolveLoader

ResolveLoader 用来告诉 Webpack 如何去寻找 Loader,因为在使用 Loader 时是通过其包名称去引用的, Webpack 需要根据配置的 Loader 包名去找到 Loader 的实际代码,以调用 Loader 去处理源文件。

三、Webpack 实战

3.1 使用 ES6 语言

ECMAScript 6.0 是2015年发布的下一代 JavaScript 语言标准,它引入了新的语法和 API 来提升开发效率。

Babel 是一个 JavaScript 编译器,能将 ES6 代码转为 ES5 代码,让你使用最新的语言特性而不用担心兼容性问题,并且可以通过插件机制根据需求灵活的扩展。 在 Babel 执行编译的过程中,会从项目根目录下的 .babelrc 文件读取配置。.babelrc 是一个 JSON 格式的文件。

plugins 属性告诉 Babel 要使用哪些插件,插件可以控制如何转换代码。

presets 属性告诉 Babel 要转换的源码使用了哪些新的语法特性,一个 Presets 对一组新语法特性提供支持,多个 Presets 可以叠加。

Webpack 通过 Loader 去接入 Babel,通过 babel-loader 去调用 Babel 完成转换工作。

3.2 使用 TypeScript 语言

TypeScript 是 JavaScript 的一个超集,主要提供了类型检查系统和对 ES6 语法的支持,但不支持新的 API。 目前没有任何环境支持运行原生的 TypeScript 代码,必须通过构建把它转换成 JavaScript 代码后才能运行。

TypeScript 官方提供了能把 TypeScript 转换成 JavaScript 的编译器。 你需要在当前项目根目录下新建一个用于配置编译选项的 tsconfig.json 文件,编译器默认会读取和使用这个文件。

要让 Webpack 支持 TypeScript,通过 Loader 把 TypeScript 转换成 JavaScript,Webpack 修改默认的 resolve.extensions 配置寻找模块对应文件尝试 ts 后缀。

3.3 使用 Flow 检查器

Flow 是一个 Facebook 开源的 JavaScript 静态类型检测器,它是 JavaScript 语言的超集。

由于使用了 Flow 项目一般都会使用 ES6 语法,所以把 Flow 集成到使用 Webpack 构建的项目里最方便的方法是借助 Babel。安装 babel-preset-flow 依赖到项目,修改 .babelrc 配置文件,加入 Flow Preset。

3.4 使用 SCSS

SCSS 可以让你用更灵活的方式写 CSS。 它是一种 CSS 预处理器,语法和 CSS 相似,但加入了变量、逻辑等编程元素。

通过 sass-loader 把 SCSS 源码转换为 CSS 代码,再把 CSS 代码交给 css-loader 去处理。

css-loader 会找出 CSS 代码中的 @import 和 url() 这样的导入语句,告诉 Webpack 依赖这些资源。同时还支持 CSS Modules、压缩 CSS 等功能。处理完后再把结果交给 style-loader 去处理。

style-loader 会把 CSS 代码转换成字符串后,注入到 JavaScript 代码中去,通过 JavaScript 去给 DOM 增加样式。

3.5 使用 PostCSS

PostCSS 是一个 CSS 处理工具,和 SCSS 不同的地方在于它通过插件机制可以灵活的扩展其支持的特性,而不是像 SCSS 那样语法是固定的。 PostCSS 的用处非常多,包括给 CSS 自动加前缀、使用下一代 CSS 语法等。

虽然使用 PostCSS 后文件后缀还是 .css 但这些文件必须先交给 postcss-loader 处理一遍后再交给 css-loader。

3.6 使用 React 框架

要在使用 Babel 的项目中接入 React 框架是很简单的,只需要加入 React 所依赖的 Presets babel-preset-react。

3.7 使用 Vue 框架

目前最成熟和流行的开发 Vue 项目的方式是采用 ES6 加 Babel 转换,这和基本的采用 ES6 开发的项目很相似,差别在于要解析 .vue 格式的单文件组件。 好在 Vue 官方提供了对应的 vue-loader 可以非常方便的完成单文件组件的转换。

3.8 加载图片

file-loader 可以把 JavaScript 和 CSS 中导入图片的语句替换成正确的地址,并同时把文件输出到对应的位置。

url-loader 可以把文件的内容经过 base64 编码后注入到 JavaScript 或者 CSS 中去。

3.9 加载 SVG

raw-loader 可以把文本文件的内容读取出来,注入到 JavaScript 或 CSS 中去。

svg-inline-loader 和上面提到的 raw-loader 非常相似, 不同在于 svg-inline-loader 会分析 SVG 的内容,去除其中不必要的部分代码,以减少 SVG 的文件大小。

四、Webpack 优化

4.1 缩小文件搜索范围

4.1.1 优化 loader 配置

使用 Loader 时可以通过 test 、 include 、 exclude 三个配置项来命中 Loader 要应用规则的文件。 为了尽可能少的让文件被 Loader 处理,可以通过 include 去命中只有哪些文件需要被处理。

4.1.2 优化 resolve.modules 配置

resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块。当安装的第三方模块都放在项目根目录下的 ./node_modules 目录下时,没有必要按照默认的方式去一层层的寻找,可以指明存放第三方模块的绝对路径,以减少寻找。

4.1.3 优化 resolve.mainFields 配置

resolve.mainFields 用于配置第三方模块使用哪个入口文件。为了减少搜索步骤,在你明确第三方模块的入口文件描述字段时,你可以把它设置的尽量少。

4.1.4 优化 resolve.alias 配置

resolve.alias 配置项通过别名来把原导入路径映射成一个新的导入路径。默认情况下 Webpack 会从入口文件开始递归的解析和处理依赖的几十个文件,这会时一个耗时的操作。 通过配置 resolve.alias 可以让 Webpack 直接使用单独完整的文件,从而跳过耗时的递归解析操作。

4.1.5 优化 reolve.extensions 配置

resolve.extensions 用于配置在尝试过程中用到的后缀列表。如果这个列表越长,或者正确的后缀在越后面,就会造成尝试的次数越多,所以 resolve.extensions 的配置也会影响到构建的性能。

4.1.6 优化 resolve.noParse 配置

module.noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。

4.2 使用 DllPlugin

为什么给 Web 项目构建接入动态链接库的思想后,会大大提升构建速度呢? 原因在于包含大量复用模块的动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译,而是直接使用动态链接库中的代码。 由于动态链接库中大多数包含的是常用的第三方模块,例如 react、react-dom,只要不升级这些模块的版本,动态链接库就不用重新编译。

Webpack 已经内置了对动态链接库的支持,需要通过2个内置的插件接入,它们分别是:

  • DllPlugin 插件:用于打包出一个个单独的动态链接库文件。
  • DllReferencePlugin 插件:用于在主要配置文件中去引入 DllPlugin 插件打包好的动态链接库文件。

4.3 使用 HappyPack

由于有大量文件需要解析和处理,构建是文件读写和计算密集型的操作,特别是当文件数量变多后,Webpack 构建慢的问题会显得严重。 HappyPack 把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,让 Webpack 同一时刻处理多个任务。

在整个 Webpack 构建流程中,最耗时的流程可能就是 Loader 对文件的转换操作了,因为要转换的文件数据巨多,而且这些转换操作都只能一个个挨着处理。 HappyPack 的核心原理就是把这部分任务分解到多个进程去并行处理,从而减少了总的构建时间。

4.4 使用 ParallelUglifyPlugin

当 Webpack 有多个 JavaScript 文件需要输出和压缩时,原本会使用 UglifyJS 去一个个挨着压缩再输出, 但是 ParallelUglifyPlugin 则会开启多个子进程,把对多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过 UglifyJS 去压缩代码,但是变成了并行执行。 所以 ParallelUglifyPlugin 能更快的完成对多个文件的压缩工作。

4.5 使用 Tree Shaking

Tree Shaking 可以用来剔除 JavaScript 中用不上的死代码。它依赖静态的 ES6 模块化语法,例如通过 import 和 export 导入导出。 Tree Shaking 最先在 Rollup 中出现,Webpack 在 2.0 版本中将其引入。

4.6 使用 Prepack

Prepack 由 Facebook 开源,它采用较为激进的方法:在保持运行结果一致的情况下,改变源代码的运行逻辑,输出性能更高的 JavaScript 代码。 实际上 Prepack 就是一个部分求值器,编译代码时提前将计算结果放到编译后的代码中,而不是在代码运行时才去求值。

Prepack 的工作原理和流程大致如下:

  1. 通过 Babel 把 JavaScript 源码解析成抽象语法树(AST),以方便更细粒度地分析源码;
  2. Prepack 实现了一个 JavaScript 解释器,用于执行源码。借助这个解释器 Prepack 才能掌握源码具体是如何执行的,并把执行过程中的结果返回到输出中。

从表面上看去这似乎非常美好,但实际上 Prepack 还不够成熟与完善。Prepack 目前还处于初期的开发阶段,局限性也很大,例如:

  • 不能识别 DOM API 和 部分 Node.js API,如果源码中有调用依赖运行环境的 API 就会导致 Prepack 报错;
  • 存在优化后的代码性能反而更低的情况;
  • 存在优化后的代码文件尺寸大大增加的情况。

4.7 开启 Scope Hoistiong

Scope Hoisting 的实现原理其实很简单:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。 因此只有那些被引用了一次的模块才能被合并。

由于 Scope Hoisting 需要分析出模块之间的依赖关系,因此源码必须采用 ES6 模块化语句,不然它将无法生效。

五、Webpack 原理

5.1 工作原理概括

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  3. 确定入口:根据配置中的 entry 找出所有的入口文件;
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每个 chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

5.2 编写 Loader

 

5.3 编写 Plugin

 

下载信息 深入浅出 Webpack
下载地址:发表评论并刷新可见

  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
金果
  • 本文由 发表于 2021年10月5日15:34:38
  • 转载请务必保留本文链接:https://www.ingot.cn/webpack-shenru-qianchu/
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: