Webpack介绍

webpack 是什么,以及它有什么作用,解决了什么问题,这些都是我们需要在学习 webpack 的用法之前需要了解的。

如果你上网去搜的话,一般的介绍是它是一个打包工具,那么什么又叫做打包工具? 由于现在的开发都是多人协作开发,所以一般会将功能分成一个个的模块由不同程序员开发,但是这些模块之间并不是毫无关联的,模块之间会有依赖关系。我们分开为了多个文件,由于依赖关系复杂,如果这些文件加载顺序不当,就有可能发生错误,另外分开成多个文件也意味着有多个网络请求,这会对服务器造成压力,所以我们就有需要将它们合并成一个文件的需要,而将多个文件合成成一个文件,我们就称之为打包。

这里贴一张官网的图片

Webpack入门 - 图1

通过官网的图片可以看出,webpack 可以将依赖的文件打包成单个的资源文件。

但是 webpack 除了打包之外还有什么作用,或者说我们有什么需求需要它帮我们去完成。例如我们希望在项目中使用高级的 JS 语法,比如 ES6 或者更高级的语法,可是浏览器对于 ES6 的支持可能并不完全,但是我们就是想用,这个时候我们就有将 ES6 及更高级的语法转化为 ES5 或更低级语法的需要。

另外,随着项目的复杂,使用 CSS 编写样式不能满足日益复杂的项目的需要,或者说 CSS 并不是一个工程语言,这个意思指 CSS 难以阅读、修改、扩展。所以就出现像 less scss/sass 这样的工程化的语言,它可以很方便的写样式,并且十分方便管理,但是浏览器并不认识 less scss 等编写的样式,所以我们就有将 less、scss/sass 文件转化为 CSS 文件的需要。

再一个,我们会在项目中用到 TypeScript,因为 JavaScript 的灵活以及动态特性,导致很多的错误只有在运行时才暴露出来,所以 JavaScript 很容易出现 bug,使用 TypeScript 可以大大减少这种情况的发生,错误往往在编译时就能够发现,但是浏览器并不支持 TypeScript,所以我们有将 TypeScript 转化为 JavaScript 的需要。

其实还有很多的需求,比如压缩文件,比如为 CSS 样式添加浏览器前缀等等,这些等到我们后面在介绍。

webpack安装及简易入门

新建一个项目,这里我的项目名为 webpack-dev

  1. mkdir webpack-dev
  2. cd webpack-dev
  3. npm init -y

通过 npm init 命令我们初始化了一个 npm 项目,接着我们下载 webpackwebpack-cli

  1. npm install webpack webpack-cli --save-dev

为什么是本地安装而不是全局安装,如果是全局安装的话,在不同的电脑大家的 webpack 的版本可能是不一样的,所以可能会因为版本的不同而带来问题,为了避免这种情况的发生,我们选择本地局部安装,版本信息会被保存在 package.json 中,这样大家安装时版本就会是一样的,避免了不必要的问题,这里我们 webpack 版本如下

  1. "webpack": "^4.43.0",
  2. "webpack-cli": "^3.3.12"

下面我们新建一个 src 文件夹,src 文件夹一般是我们开发时所使用的文件夹,我们在这个文件夹下写代码,在 src 下新建 index.jsutil.js,内容如下

  1. // index.js
  2. import {add} from './util'
  3. let res = add(1, 2);
  4. console.log(res); // 3
  1. // util.js
  2. export const add = (x, y) => {
  3. return x + y;
  4. };

上面 index.js 依赖了 util.js,我们需要将这两个文件打包为一个文件,在命令行使用如下 webpack 命令进行打包

  1. npx webpack --entry=./src/index.js --output=./dist/bundle.js --mode=development

在命令行有如下输出

Webpack入门 - 图2

如果出现上面的输出,说明打包成功了。接下来解释一下上面的命令:因为 webpack 是本地局部安装的,所以我们要使用 npx 来调用 webpack 命令,除此以外,我们还传递了三个参数

参数 功能
entry 指定打包的入口文件
output 指定打包后的文件输出在哪个目录
mode 指定模式

我们使用 entry 来指定打包的入口文件,即 webpack 会根据该入口文件,解析它所依赖的文件,将这些文件打包成一个文件;我们使用 output 来表明所打包后的那个文件放置在哪个目录,以及打包后的文件的文件名是什么,在上面我们设置在打包后的文件放在 dist 文件夹,文件名为 bundle.js,使用上面的命令之后,可以观察到自动生成了一个 dist 文件夹,以及在该文件夹下有一个 bundle.js 的文件

Webpack入门 - 图3

mode 是用来指定模式的,可选的选项有 developmentproductiondevelopment 指的是开发环境,这个时候我们打包出来的代码不会被压缩,当指定 modeproduction 时,即生产环境下,那么打包后的代码会被压缩。

在上面我们使用的是 ES6 的模块语法,即 importexport,但是很多的 npm 项目使用的是 CommonJS 模块规则,即 requiremodule.exports,所以在项目中很可能我们会同时使用上面两种模块进行导出和导入,这样的代码是不可能在浏览器中执行的,使用 webpack 它可以处理这种情况,我们可以在项目中自由的使用这两种导入导出的规则,webpack 会自动帮我们处理为可以在浏览器中执行的代码,在 src 下新建 foo.js 如下

  1. // foo.js
  2. module.exports = {
  3. sub: (x, y) => {
  4. return x - y;
  5. }
  6. };

并且修改 index.js 如下

  1. // index.js
  2. import {add} from './util'
  3. const sub = require('./foo').sub;
  4. let res1 = add(1, 2);
  5. let res2 = sub(1, 2);
  6. console.log(res1); // 3
  7. console.log(res2); // -1

index.js 中,我们同时应用了两种模块语法,我们在命令行中再次执行

  1. npx webpack --entry=./src/index.js --output=./dist/bundle.js --mode=development

为了观察打包后的文件是否能够在浏览器执行,我们在 dist 下新建 index.html 并引用 bundle.js,如下

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <script src="bundle.js"></script>
  9. </body>
  10. </html>

在浏览器打开 index.html,在控制台可以观察到如下

Webpack入门 - 图4

可见 webpack 为我们自动处理了这种语法。

为了不每次在命令行输入上面一行特长的命令,我们可以配置 package.json 中的 script 属性,如下

  1. "scripts": {
  2. "build": "webpack --entry=./src/index.js --output=./dist/bundle.js --mode=development",
  3. }

下面我们只要在命令行输入

  1. npm run build

它就会执行那一串命令,即可进行打包。

配置文件

在上面的简易入门中,我们没有为 webpack 写配置文件,只是在命令行中设置了 entry output mode 这些参数,但是其实这些参数都有默认值

参数 默认值
entry src/index.js
output dist/main.js
mode production

这意味着即使我们在命令行不配置这些参数,也能够进行正常的打包,会在 dist 目录下生成一个 main.js 文件而不是 bundle.js 文件。所以可以说 webpack 是零配置,不需要配置对用户来说是一件好事,可以减少很多的心智负担,但是零配置也意味着功能较弱,为了使用 webpack 强大的功能,我们必须要学习 webpack 许许多多的配置,但是这些配置都写在命令行中,那是十分的不方便,这些东西应该放在配置文件中。

webpack 的默认配置文件名为 webpack.config.js 或者 webpackfile.js,由于业界大多数使用的都是 webpack.config.js,所以我们在这里也使用 webpack.config.js。在项目的根目录(而不是 src 目录)下新建 webpack.config.js,内容如下

  1. const path = require('path');
  2. module.exports = {
  3. entry: "./src/index.js",
  4. output: {
  5. filename: "bundle.js",
  6. path: path.resolve(__dirname, "dist")
  7. },
  8. mode: "development"
  9. };

这时我们就不必在命令行中指定参数了,我们修改 package.jsonscript 字段如下

  1. "scripts": {
  2. "build": "webpack"
  3. }

接着在命令行运行 npm run build 即可看到打包成功的消息。下面就更加详细的讲解一下 entryoutput 的配置。

entry

entry 指代的就是入口文件,这是我们在上面就已经介绍过的。其实 entry 可以有多个入口,webpack webpack 会根据每一个入口文件打包出一个 chunk

Webpack入门 - 图5

这个 chunk 可以有名字,当如下配置时

  1. entry: "./src/index.js"

打包出的 chunk 的名字默认为 main。我们也可以使用对象的方式配置 chunk 的名字

  1. entry: {
  2. index: "./src/index.js" // index 表示 chunk 名字,对应的值表示对应的入口文件
  3. },
  4. output: {
  5. filename: "[name].js",
  6. path: path.resolve(__dirname, "dist")
  7. },

在上面有两处改动,第一个就是将 entry 的值设置为了一个对象,该对象的值表示入口文件,而键表示入口文件打包后的 chunk 的名字;第二个改动就是在 output 中我们设置打包后的文件名称为 [name].js,这个 [name] 是一个变量,指的就是 chunk 的名字,我们没有手动的写死一个值,而是使打包后的文件名与 chunk 名字相同,所以此时运行 npm run build 可以在 dist 目录下打包出 index.js 文件。下面是运行打包命令在命令行的输出

Webpack入门 - 图6

output

output 的常见配置选项如下

选项 功能
filename 指定文件名称,可以为 [name] [chunkHash] 以及它们的组合
path 打包后的文件放置的路径
publicPath

filename 是用来指定打包后的文件名的,我们除了可以指定特定的名称如 bundle.js 外,还可以使用 [name] [chunkHash] 等变量,分别指打包后的 chunk 的名称和打包后文件内容计算出的 hash 值。二者可以进行组合使用,如

  1. output: {
  2. filename: "[name][chunkHash].js",
  3. path: path.resolve(__dirname, "dist")
  4. }

本地服务器

上面我们的开发过程为

Webpack入门 - 图7

每次我们更改程序后,都要重新刷新网页以观察最新的结果,而我们希望在我们更改程序后,页面能够自动的刷新,不需要再次使用 webpack 进行打包,然后手动刷新页面,这个时候我们就需要使用 webpack-dev-server 了,它可以在本地开启一个服务器,并且当我们修改程序时,自动的刷新页面。

首先下载 webpack-dev-server

  1. npm install webpack-dev-server --save-dev

接着我们在 webpack.config.js 中加入 devServer 配置项

  1. const path = require('path');
  2. module.exports = {
  3. // webpack-dev-server 的配置
  4. devServer: {
  5. port: 3000,
  6. contentBase: path.resolve(__dirname, 'dist')
  7. },
  8. entry: {
  9. "bundle": "./src/index.js"
  10. },
  11. output: {
  12. filename: "[name].js",
  13. path: path.resolve(__dirname, "dist")
  14. },
  15. mode: "development"
  16. };

在上面我们配置了两个选项

选项 功能
port 配置端口号
contentBase 配置服务器的根目录

接着我们为 package.json 中的 script 字段添加 dev 选项

  1. "scripts": {
  2. "build": "webpack",
  3. "dev": "webpack-dev-server"
  4. }

接着我们运行 npm run dev 即可在本地启动一个服务器了

Webpack入门 - 图8

接着我们在浏览器输入 http://localhost:3000 即可访问页面了。

注意: 这里有一点需要注意的是,使用 webpack-dev-server 并不会输出打包后的文件,它会根据入口文件的依赖关系进行打包,但是打包后的内容放在内存中,而不是输出一个文件到硬盘上,所以不会生成 bundle.js 文件,因为它只存在于内存中。

loader

loader 又是一个新的概念,我们可以简单的理解为处理器,处理什么呢? 比如我们需要将 ES6 转换为 ES5,这就需要一个 loader 进行转换;比如我们需要将 less 转换为 CSS,这需要一个 loader 进行转换,比如需要将 TypeScript 转换为 JavaScript,这也需要一个 loader 进行转换;等等。

下面我们就介绍一些常用的 loader

babel-loader

我们常常有需要将 ES6 或更高级的语法转化为 ES5的需要,这个时候我们就要用到 babel-loader,我们需要下载这些安装包

  1. npm install babel-loader @babel/core --save-dev

@babel/corebabel 的核心包,具体的转换要依赖该包,接着我们需要在 webpack.config.js 中进行配置

  1. const path = require('path');
  2. module.exports = {
  3. devServer: {
  4. port: 3000,
  5. contentBase: path.resolve(__dirname, 'dist')
  6. },
  7. entry: {
  8. "bundle": "./src/index.js"
  9. },
  10. output: {
  11. filename: "[name].js",
  12. path: path.resolve(__dirname, "dist")
  13. },
  14. mode: "development",
  15. module: {
  16. rules: [
  17. // 配置 loader
  18. {
  19. test: /\.js$/,
  20. use: "babel-loader"
  21. }
  22. ]
  23. }
  24. };

我们在 module.rules 中配置 loader,其中最少需要配置两项 testusetest 用来表示对什么文件进行转换,比如上面对所有以 .js 结尾的文件进行转换;use 用来指定使用什么 loader 进行转换,这里指定为 babel-loader。综合上面两个配置,表示的意思就是对所有以 .js 结尾的文件使用 babel-loader 进行处理。

其中 use 也可以是一个对象,如下

  1. use: {
  2. loader: "babel-loader",
  3. options: {
  4. }
  5. }

use 是一个对象时,我们可以通过 optionsloader 配置一些参数。

除了配置 testuse,还可以配置两个选项

选项 功能
include 指定检查的路径
exclude 指定排除的路径

上面的两个选项指定只对哪些路径下的文件进行检查,或者不检查哪些路径下的文件,一般来说二者只需要配置一个即可,但是如果两个都配置了,并且有冲突,那么 exclude 的优先级高。

我们接着修改 index.js 的内容,为其中添加 ES6 的语法

  1. let x = 1;
  2. let y = 2;
  3. const add = (x, y) => {
  4. return x + y;
  5. };
  6. let result = add(1, 2);
  7. let p = new Promise((resolve, reject) => {
  8. resolve(1);
  9. }).then(value => {
  10. console.log(value);
  11. });

index.js 中,我们使用了 ES6 才出现的语法,如 let const Promise 箭头函数,现在我们运行 npm run build 进行打包,部分内容如下

  1. eval("let x = 1;\nlet y = 2;\n\nconst add = (x, y) => {\n return x + y;\n};\n\nlet result = add(1, 2);\nlet p = new Promise((resolve, reject) => {\n resolve(1);\n}).then(value => {\n console.log(value);\n});\n\n//# sourceURL=webpack:///./src/index.js?");

发现并没有将 ES6 的语法转化为 ES5 的语法,因为 babel 需要特定的插件支持,babel 为了实现按需加载的功能,它将 ES6ES5 的功能分为了 20+ 个插件,你需要哪个就下哪个,但是这样却是有点麻烦,好在 babel 推出了一个插件集合 preset,我们直接下载 @babel/preset-env 即可,里面包含了所有的 ES6 语法转 ES5 语法的插件合集

  1. npm install @babel/preset-env --save-dev

接着我们在 webpack.config.js 中进行配置

  1. test: /\.js$/,
  2. use: {
  3. loader: "babel-loader",
  4. options: {
  5. "presets": [
  6. "@babel/preset-env"
  7. ]
  8. },
  9. exclude: path.resolve(__dirname, "node_modules/") // 对 node_modules 中的文件不进行转换
  10. }

但是观察打包后的文件,let const 和箭头函数都被转为了 ES5,但是 Promise 并没有被转换,因为这是新增的 API 而不是语法,这种新增的 APIpreset-env 是转化不了的,它只能转化语法,所以 class 以及 Object.assign 等这些新增的 API 它是不能转换的,如果在版本比较低的浏览器中,由于不支持这些 API,那么就会报错。

这个时候需要 polyfill 为浏览器提供 ES6 的环境,即使用 ES5 或更低的语法去实现上面的 API,有两种解决办法。

  1. 下载 @babel/polyfill,并且在入口文件即 index.js 中引入bash npm install @babel/polyfill --save
    接着在 index.js 中引入```javascript // 引入 @babel/polyfill import ‘@babel/polyfill’

let x = 1; let y = 2; const add = (x, y) => { return x + y; };

let result = add(1, 2);

let p = new Promise((resolve, reject) => { resolve(1); }).then(value => { console.log(value); });

  1. <br />`@babel/polyfill` 使用 `ES5` 的语法实现低版本浏览器中没有的 `API`,所以即使在低版本的浏览器中也能够运行上面的程序,但是 `@babel/polyfill` 同时也覆盖了原有的 `API`,另外我们直接导入了 `@babel/polyfill` 中的所有内容,其中有一些 `API` 我们根本没有用到,比如 `class`,这就会导致打包出的文件体积较大。
  2. 2. 为了实现按需加载,我们使用另一种解决办法,下载一个插件 `@babel/plugin-transform-runtime`,因为 `@babel/plugin-transform-runtime` 依赖于 `@babel/runtime`,所以我们也要下载 `@babel/runtime`,并且 `@babel/runtime` 生产环境下也要用到,所以不能使用 `--save-dev` 安装```bash
  3. npm install @babel/plugin-transform-runtime --save-dev
  4. npm install @babel/runtime


接着我们在 webpack.config.js 中加入插件的配置```javascript test: /.js$/, use: { loader: “babel-loader”, options: { “presets”: [ “@babel/preset-env” ], “plugins”: [ “@babel/plugin-transform-runtime” ] } }

  1. <br />接着我们去掉 `index.js` 头部添加的 `import '@babel/polyfill'`,再次使用 `webpack` 进行打包即可。
  2. <a name="0c37461e"></a>
  3. ### css-loader、style-loader
  4. 除了可以对 `JavaScript` 进行打包以外,还可以对 `CSS` 进行打包,不过 `webpack` 只能处理 `JavaScript`,即不能把 `CSS` 文件当做入口文件进行打包,不过我们可以在 `JavaScript` 中导入 `CSS`,当对 `JavaScript` 进行打包,也会同时对导入的 `CSS` 进行打包,在 `src` 中新建 `index.css` 和 `foo.css`
  5. ```css
  6. /* index.css */
  7. @import "foo.css";
  8. body {
  9. background-color: #5d4141;
  10. }
  1. /* foo.css */
  2. body {
  3. color: white;
  4. font-size: 4em;
  5. }

我们在 index.css 中通过 @import 语法引入了 foo.css,接着我们在 index.js 中引入 index.css

  1. import './index.css'
  2. document.write("Hello World");

我们通过 import './index.css' 引入了 CSSwebpack 将一切都看作是模块,CSS 文件也可以看做是模块,我们就可以通过 import 将该模块导入。

我们希望有一个 loader 来处理 import './index.css' 的语法,使样式生效,这里我们需要两个 loader,分别为 css-loaderstyle-loader,首先进行安装

  1. npm install css-loader style-loader --save-dev

css-loader 的作用是处理 CSS 文件中 @importurl() 这样的语法的,而 style-loader 是将解析完的 CSS 样式作为 style 标签插入到页面的 head 元素中。我们在 webpack.config.jsloader 进行配置

  1. test: /\.css$/,
  2. use: ["style-loader", "css-loader"]

loader 的执行是有先后顺序,webpack 会根据 use 中定义的 loader 从后往前执行,所以我们把 css-loader 写在 style-loader 后面,这样就会先执行 css-loader,然后执行 style-loader

我们运行 npm run dev,在 http://localhost:3000 可以观察到下面的页面

Webpack入门 - 图9

说明我们的配置成功了。

除了处理 CSS 文件,有时候我们会使用 less sass/scss 编写样式,但是这些文件浏览器不能解析,所以我们需要使用相应的 loaderless sass/scss 转为 CSS 文件,例如 less-loader sass-loader

  1. npm install less less-loader node-sass sass-loader --save-dev

接着我们在 webpack.config.js 中添加相应的配置

  1. {
  2. test: /\.less$/,
  3. use: ["style-loader", "css-loader", "less-loader"]
  4. },
  5. {
  6. test: /\.(sass|scss)$/,
  7. use: ["style-loader", "css-loader", "sass-loader"]
  8. }

less-loadersass-loader 只是把要处理的文件转化为 CSS 文件,最后我们还是需要通过 css-loaderstyle-loader 来处理路径以及将 CSS 插入到页面中。

file-loader、url-loader

下面介绍有关处理图片相关的 loader,假设在 index.js 的中引用了图片

  1. let img = new Image();
  2. img.src = "./beauty.jpg";
  3. document.body.appendChild(img);

我们进行打包,发现在浏览器中并没有出现我们想要的图片,因为我们在文件中引用的图片是在 src 目录下的,在 dist 目录下并没有这张图片,所以我们也需要将图片打包到 dist 目录下,如下

  1. import beauty from './beauty.jpg'
  2. let img = new Image();
  3. img.src = beauty;
  4. document.body.appendChild(img);

我们将图片作为模块导入,希望得到该图片的打包后的地址,然后将 img.src 设置为这个地址。这件事情需要一个 loader 去做,这里我们使用 file-loader,它的作用就是将引入的文件(一般是图片)打包到(或者说移动到) 指定目录下,并返回打包后的文件所在的路径,我们需要安装 file-loader

  1. npm install file-loader --save-dev

并在 webpack.config.js 中进行配置

  1. {
  2. test: /\.(png|jpe?g|gif|svg)$/,
  3. use: {
  4. loader: "file-loader",
  5. options: {
  6. name: "[name].[ext]",
  7. outputPath: "img/"
  8. }
  9. }
  10. }

其中 name 是用来配置输出的文件名的,这里我们使用了两个变量 [name][ext][name] 代表打包前文件的名称,[ext] 表示扩展名;outputPath 用来指定打包后文件输出的位置,这个位置是相对于 output.path 的路径。所以会在 dist/img 下打包出一张与原文件名相同的一张图片,并且将该打包后的图片的地址返回,这样在页面中就会出现图片。

有时候对于较小的图片,我们希望将它转为 base64,这样可以减少 http 请求次数。我们使用 url-loader 来做这个事情,它提供了一个 limit 选项,该选项指定一个值,当图片的大小小于该值时就转化为 base64,当大于该值时,就使用 file-loader 进行转换,我们修改 webpack.config.js 的配置

  1. test: /\.(png|jpe?g|gif|svg)$/,
  2. use: {
  3. loader: "url-loader",
  4. options: {
  5. name: "[name].[ext]",
  6. limit: 200*1024 // 小于 200KB 就转化为 base64 编码
  7. }
  8. }

ts-loader

我们在项目中也经常使用 TypeScript 来写程序,因为 TypeScript 的种种好处,可以帮我们减少 bug 产生的几率,但是浏览器也是不认识 TypeScript,我们需要将 TypeScript 转化为 JavaScript,这需要 ts-loader 来帮我们做这件事情,首先我们需要下载相关的包

  1. npm install typescript ts-loader --save-dev

接着我们需要配置 webpack.config.js 以及 tsconfig.json(在项目根目录下新建该文件)

  1. test: /\.ts$/,
  2. use: "ts-loader"
  1. {
  2. "compilerOptions": {
  3. "module": "commonjs",
  4. "target": "es5",
  5. "allowJs": true
  6. },
  7. "include": [
  8. "./src/"
  9. ],
  10. "exclude": [
  11. "./node_modules/"
  12. ]
  13. }

关于 tsconfig.json 的配置不是重点,可以去网上了解相关内容。现在我们就可以自由的写 TypeScript 了。

plugin

pluginwebpack 的另一大特色,它为我们提供了相当多的功能,在 webpack 打包的整个生命周期中会广播出很多事件,而 plugin 可以监听这些事件,在合适的时机通过 webapck 提供的 API 改变输出的结果。下面就介绍一些常用到的 plugin

mini-css-extract-plugin

在处理 CSSloader 中,我们最后使用 style-loader 将最后的样式以 style 标签的形式插入到页面中,但是如果我们希望将样式抽离出一个 CSS 文件呢? 这个时候我们就需要用到 mini-css-extract-plugin 插件了。首先进行下载

  1. npm install mini-css-extract-plugin --save-dev

接着在 webpack.config.js 中进行配置

  1. const path = require('path');
  2. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  3. module.exports = {
  4. // 省略其他配置
  5. module: {
  6. rules: [
  7. // 省略其他 loader 配置
  8. {
  9. test: /\.css$/,
  10. use: [MiniCssExtractPlugin.loader, "css-loader"]
  11. }
  12. ]
  13. },
  14. plugins: [
  15. new MiniCssExtractPlugin({
  16. filename: "main.css" // 抽离出的 css 文件名称
  17. })
  18. ]
  19. };

首先我们通过 require('mini-css-extract-plugin') 引入该插件,接着在 plugins 选项中配置了该插件,并设置了抽离后的 CSS 文件的名称,接着因为我们希望最后抽离出一个 CSS 文件而不是将文件以 style 标签的形式插入到页面中,所以我们使用了 MiniCssExtractPlugin.loader 来替换了 style-loader

我们运行 npm run build 就可以观察到在 dist 目录下生成了一个 main.css 文件。为了观察到效果,我们在 dist/index.html 中将该 main.css 引入

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <link rel="stylesheet" href="./main.css">
  9. <script src="bundle.js"></script>
  10. </body>
  11. </html>

打开该 html 就可以看到效果了。

clean-webpack-plugin

该插件的作用是在每次打包前删除 dist 文件夹,这样我们每次都可以看到最新的打包文件。可能你还不能理解这个应用场景,现在我们修改 output.filename 如下

  1. output: {
  2. filename: "[name]@[chunkHash].js",
  3. path: path.resolve(__dirname, "dist")
  4. }

每次生成的文件由名字和文件内容生成的 hash 值组成,每次我们修改程序,然后打包,就会产生不同的哈希值,所以 dist 下的文件会不断的增多,我们根本不知道哪个文件是我们最新打包出来的,如下

Webpack入门 - 图10

所以我们就有这个需要,在生成新的打包文件之前,将之前的打包文件进行删除。首先下载 clean-webpack-plugin

  1. npm install clean-webpack-plugin --save-dev

接着配置 webpack.config.js

  1. // 省略其他
  2. const {CleanWebpackPlugin} = require('clean-webpack-plugin');
  3. module.exports = {
  4. // 省略其他配置
  5. plugins: [
  6. new MiniCssExtractPlugin({
  7. filename: "main.css"
  8. }),
  9. new CleanWebpackPlugin()
  10. ]
  11. };

再次进行打包,发现之前的 dist 文件夹下之前的打包文件都被删掉了。

html-webpack-plugin

使用 clean-webpack-plugin 会将我们之前在 dist 下新建的 index.html 文件也会删除掉,为了观察打包效果,我们不得不在 dist 目录下再次新建 index.html 并且将打包后的 JavaScript 文件和 CSS 文件引入,每次打包一次都要这么做实在令人恼火,我们使用 html-webpack-plugin 来解决这个问题。

html-webpack-plugin 会根据一个 html 文件模板,在 dist 文件夹下生成一个 html 文件,并且会将打包后的 JavaScriptCSS 自动引入,大大的解放了我们的双手。首先我们下载 html-webpack-plugin

  1. npm install html-webpack-plugin --save-dev

接着在 webpack.config.js 中进行配置

  1. // 省略其他
  2. const HtmlWebpackPlugin = require('html-webpack-plugin');
  3. module.exports = {
  4. // 省略其他配置
  5. plugins: [
  6. new MiniCssExtractPlugin({
  7. filename: "main.css"
  8. }),
  9. new CleanWebpackPlugin(),
  10. new HtmlWebpackPlugin({
  11. template: "./src/index.html",
  12. filename: "index.html"
  13. })
  14. ]
  15. };

其中我们在 HtmlWebpackPlugin 中传入了两个配置项

配置项 作用
template 指定模板的路径
filename 指定生成的 html 文件的文件名

在上面,我们将模板指定为 src/index.html,所以我们在 src 新建 index.html 如下

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. </body>
  9. </html>

只是一个标准的 html 文件,里面什么都没有。运行 npm run build 即可观察到在 dist 文件夹下生成了一个 index.html 文件

Webpack入门 - 图11

生成的 index.html 的内容如下

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <link href="main.css" rel="stylesheet"></head>
  7. <body>
  8. <script src="bundle%4063ea546939ae13c70c31.js"></script></body>
  9. </html>

可见它已经自动为我们引入了打包后的 JavaScript 文件和 CSS 文件。

参考文章