基于 Webpack5 。
随着前端的快速发展,前端的开发已经变得越来越复杂了。比如:开发过程中需要通过模块化的方式开发;开发过程中会使用一些高级的特性来加快开发效率或者安全性(例如:通过 ES6+、TypeScript 开发脚本逻辑;通过 Sass、Less 等方式来编写 CSS 样式);开发过程中希望实时监听文件的变化并且反应到浏览器上;开发完成后打包代码时需要将代码进行压缩、合并以及其他相关的优化等等。目前前端开发的三大框架 React、Vue、Angular 的脚手架都基于 Webpack 来支持了这些需求。
Webpack 是一个为现代的 JavaScript 应用程序提供静态的模块化的打包工具。
- 现代的:正是因为现代前端开发面临各种各样的问题,才催生了Webpack 的出现和发展。
- 静态的:最终会将代码打包成静态资源。
- 模块化的:Webpack 支持各种模块化开发。
- 打包工具:Webpack 可以对代码进行打包。
静态资源:使用静态网页技术(HTML、CSS、JS 等)开发的资源。如 HTML、CSS、JS、文本、图片、音频、视频等都属于静态资源。所有用户访问得到的结果都一样。
动态资源:是从资源的服务器数据库里拿出来,使用动态网页技术(JSP、PHP、ASP 等)发布的资源。不同用户访问,得到的结果可能不一样。
Webpack、Gulp、Rollup、Vite 都是前端开发的构建工具,它们的区别是:
- Webpack 主要用于模块打包,支持各种资源。可以通过 Loader 转换各种文件,并在恰当的时机执行 Plugin 中的逻辑。
- Gulp 主要用于执行一些自动化的任务。
- Rollup 主要用于 JavaScript 模块的打包,可以打包输出多种格式,适用于各种库的构建。
- Vite:和 Webpack 类似,是基于 Rollup 的新一代前端开发工具,更快速和简单。
相比于 Webpack,开启本地服务的速度更快。
不需要对 CSS 代码、Less 代码、PostCSS 做什么额外的配置,可以直接处理;也可以直接处理 React 和 TypeScript 代码。
安装 Webpack:
从 Webpack4 开始,安装时就需要分别安装 webpack 和 webpack-cli。
- 可以全局安装
npm install webpack webpack-cli -g
。 - 也可以局部安装
npm install webpack webpack-cli --save-dev
。
Webpack 的运行需要依赖 Node 环境。
Webpack CLI 做的最重要的事就是把打包命令中的参数和配置文件中的配置进行合并。
执行
webpack
命令,会去执行node_modules
下的.bin
目录下的 webpack 文件;webpack 在执行时是依赖webpack-cli
的,如果没有安装就会报错;而webpack-cli
中代码执行时,才是真正利用 Webpack 进行编译和打包的过程。
a’li
使用 Webpack:
-
新建
webpack-demo
文件夹,在其中分别新建index.html
、src/index.js
和src/utils/math.js
文件并编写代码。
// index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script src="src/index.js"></script> </body> </html>
// src/index.js const math = require('./utils/math.js') console.log(math.sum(10, 20))
// src/utils/math.js const sum = (num1, num2) => { return num1 + num2 } module.exports = { sum, }
-
直接在浏览器中运行
index.html
,会发现报错了,这是因为浏览器不支持 CommonJS 的模块化语法。
-
可以在当前根目录下运行
npx webpack
命令 ,就会在根目录下生成dist/main.js
。将index.html
中引入的 JavaScript 文件修改为dist/main.js
,在浏览器中就可以正常运行了。webpack 默认会将 src/index.js 作为入口文件,dist/main.js 作为出口文件。
在执行 webpack 命令时,可以添加一些参数来修改这些默认的配置选项。例如:webpack --entry ./src/main --output-path ./build
,可以修改../src/main.js
为入口文件,./build
为出口文件的路径。使用 webpack 之后:就可以任意使用各种高级语法来编写代码;只需要运行
webpack
命令,Webpack 默认会去当前目录下寻找src/index.js
作为入口文件,以此来寻找其他的依赖文件,然后将代码转译成浏览器支持的语法后,进行压缩和丑化,打包进生成的dist/main.js
文件中;引入这个打包后生成的文件就可以正常运行了。观察打包后生成的
dist/main.js
文件,会发现,其中像 ES6 中的 const、箭头语句等仍然存在。这是因为 webpack 对各种模块语法提供了开箱即用般的支持,因此不论写 CommonJS 还是 ESModule 等 webpack 都可以直接转译,但是 webpack 不会更改代码中除了模块化语句以外的部分,如果使用了 ES6 等的特性,需要使用像 Babel 等的转译器来转译。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script src="dist/main.js"></script> </body> </html>
Webpack 是如何对文件进行打包的?
Webpack 在处理应用程序时,会根据命令或者配置文件找到入口文件,从入口开始,根据文件的依赖关系生成一个依赖关系图;然后遍历这个图结构,对不同的文件使用对应的 Loader 来进行转换,在恰当的时机执行 Plugin 里定义的逻辑;最后将文件打包输出。
Webpack 中的 module、chunk 和 bundle:
module:
module:模块。在 Webpack 中一切皆模块,任何一个文件都可以称之为是一个模块,例如 JavaScript 模块、CSS 模块、图片模块等等。
chunk:
chunk:代码块。是 Webpack 打包过程中的中间产物,包含一个或一组 module。
产生 chunk 的三个途径:
- 一个 entry 入口会生成一个 chunk。Webpack 的打包是从一个入口文件开始,入口模块引用这其他模块,模块再引用模块,Webpack 通过引用关系逐个打包模块,这些 module 就形成了一个chunk。
- 一个异步加载的模块也会被单独打包,生成一个 chunk。
- 代码分割也会生成 chunk。
bundle:
bundle:捆绑。是 Webpack 最终打包输出的文件,最终生成的每个文件都是一个 bundle。大多数情况下,一个 chunk 会生成一个 bundle,但有时候也不完全是一对一的关系,例如设置了 source-map
,一个 chunk 就会生成两个 bundle。
Webpack 中的 placeholder 占位符:
在给打包输出的文件命名的时候,可以使用 placeholder 占位符。
name:
name:代码块的名称 chunk name
。
id:
id:代码块的 id chunk id
。
hash、chunkhash、contenthash:
hash、chunkhash、contenthash 都是用来生成哈希值的,可以用于给打包输出的文件命名。使用哈希可以在文件内容没有发生更改的情况下使用缓存,提高加载效率;在文件更改之后重新下载,避免缓存问题导致获取不到新的资源。
都是通过 MD4 的散列函数生成,包含 32 位十六进制的值。
hash:
hash:生成整个项目的哈希值。Webpack 会根据构建的项目内容生成一个唯一的哈希值,因此所有打包输出的文件获取到的哈希值都是一样的。如果项目中的内容没有发生变化,再次打包时哈希值将不会发生变化;如果项目中的内容发生了变化,再次打包时哈希值就会发生变化。
-
新建
src/utils.js
、src/index.js
和src/main.js
,并编写代码。// src/utils.js console.log('utils')
// src/index.js console.log('index') import('./utils')
// src/main.js console.log('main')
-
在
webpack.config.js
中进行配置。// webpack.config.js module.exports = { entry: { 'main': './src/main.js', 'index': './src/index.js' }, output: { // 配置多个出口。其中的占位符 [name] 是在 entry 中配置的 key;hash 是依据整个项目生成的哈希值,:6 表示截取前 6 位 filename: '[name].[hash:6].js', chunkFilename: '[name].[hash:6].js', } }
-
运行
webpack
命令进行打包,会发现打包输出的文件的哈希值都一样。
-
修改
src/main.js
中的内容。// src/main.js console.log('main2')
-
再次运行
webpack
命令进行打包,会发现打包输出的文件的哈希值都发生了变化。 这样其实是不好的,只修改了一个文件的内容,但是所有打包输出的文件都需要重新编译打包,而且由于文件名都发生了变化,导致浏览器需要都重新下载一遍。
chunkhash:
chunkhash:生成 chunk 的哈希值。Webpack 会根据 chunk 的内容生成一个唯一的哈希值,因此每个 chunk 打包输出的文件获取到的哈希值都是不一样的。如果 chunk 中的内容没有发生变化,再次打包时哈希值将不会发生变化;如果 chunk 中的内容发生了变化,再次打包时哈希值就会发生变化。
- 新建
src/utils.js
、src/index.js
和src/main.js
,并编写代码。// src/utils.js console.log('utils')
// src/index.js import './utils' console.log('index')
// src/main.js console.log('main')
- 在
webpack.config.js
中进行配置。// webpack.config.js module.exports = { entry: { 'main': './src/main.js', 'index': './src/index.js' }, output: { // 配置多个出口。其中的占位符 [name] 是在 entry 中配置的 key;hash 是依据整个项目生成的哈希值,:6 表示截取前 6 位 filename: '[name].[chunkhash:6].js', chunkFilename: '[name].[chunkhash:6].js', } }
- 运行
webpack
命令进行打包,会发现不同的 chunk 打包输出的文件的哈希值不一样。
- 修改
src/util.js
中的内容。// src/util.js console.log('util2')
- 再次运行
webpack
命令进行打包,会发现打包输出的文件的哈希值都发生了变化。
contenthash:
contenthash:生成文件内容的哈希值。