认识 webpack

认识 webpack

事实上随着前端的快速发展,目前前端的开发已经变的越来越复杂了:

  • 比如开发过程中我们需要通过模块化的方式来开发;
  • 比如也会使用一些高级的特性来加快我们的开发效率或者安全性,比如通过 ES6+、TypeScript 开发脚本逻辑,通过 sass、less 等方式来编写 css 样式代码;
  • 比如开发过程中,我们还希望实时的监听文件的变化来并且反映到浏览器上,提高开发的效率;
  • 比如开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化

但是对于很多的前端开发者来说,并不需要思考这些问题,日常的开发中根本就没有面临这些问题:

  • 这是因为目前前端开发我们通常都会直接使用三大框架来开发:VueReactAngular

  • 但是事实上,这三大框架的创建过程我们都是借助于脚手架(CLI)的;

  • 事实上 Vue-CLI、create-react-app、Angular-CLI 都是基于webpack来帮助我们支持模块化、less、

    TypeScript、打包优化等的;

脚手架依赖 webpack

事实上我们上面提到的所有脚手架都是依赖于 webpack 的:

image-20210925140936905

Webpack 到底是什么呢?

我们先来看一下官方的解释:

webpack is a static module bundler for modern JavaScript applications.

webpack 是一个静态的模块化打包工具,为现代的 JavaScript 应用程序;

我们来对上面的解释进行拆解:

  • 打包 bundler:webpack 可以将帮助我们进行打包,所以它是一个打包工具
  • 静态的 static:这样表述的原因是我们最终可以将代码打包成最终的静态资源(部署到静态服务器);
  • 模块化 module:webpack 默认支持各种模块化开发,ES Module、CommonJS、AMD 等;
  • 现代的 modern:我们前端说过,正是因为现代前端开发面临各种各样的问题,才催生了 webpack 的出现和发展

Webpack 官方的图片

image-20210925141143722

Vue 项目加载的文件有哪些呢?

  • JavaScript 的打包:
    将 ES6 转换成 ES5 的语法;
    TypeScript 的处理,将其转换成 JavaScript;
  • Css 的处理:
    CSS 文件模块的加载、提取;
    Less、Sass 等预处理器的处理;
  • 资源文件 img、font:
    图片 img 文件的加载;
    字体 font 文件的加载;
  • HTML 资源的处理:
    打包 HTML 资源文件;
  • 处理 vue 项目的 SFC 文件:
    vue 文件;

Webpack 的使用前提

webpack 的官方文档是https://webpack.js.org/

webpack 的中文官方文档是https://webpack.docschina.org/

DOCUMENTATION:文档详情,也是我们最关注的

Webpack 的运行是依赖 Node 环境的,所以我们电脑上必须有 Node 环境

  • 所以我们需要先安装 Node.js,并且同时会安装 npm;
  • 我当前电脑上的 node 版本是 v14.15.5,npm 版本是 6.14.11(你也可以使用 nvm 或者 n 来管理 Node 版本);
  • Node 官方网站:https://nodejs.org/

image-20210925141317091

Webpack 的安装

webpack 的安装目前分为两个:webpackwebpack-cli

那么它们是什么关系呢?

  • 执行 webpack 命令,会执行 node_modules 下的.bin 目录下的 webpack;
  • webpack 在执行时是依赖 webpack-cli 的,如果没有安装就会报错;
  • 而 webpack-cli 中代码执行时,才是真正利用 webpack 进行编译和打包的过程;
  • 所以在安装 webpack 时,我们需要同时安装 webpack-cli(第三方的脚手架事实上是没有使用 webpack-cli 的,而是类似于自己的 vue-service-cli 的东西)

image-20210925141419087

1
2
npm install webpack webpack-cli –g # 全局安装
npm install webpack webpack-cli –D # 局部安装

Webpack 的默认打包

我们可以通过 webpack 进行打包,之后运行打包之后的代码
在目录下直接执行 webpack 命令

1
webpack

生成一个 dist 文件夹,里面存放一个 main.js 的文件,就是我们打包之后的文件:

  • 这个文件中的代码被压缩和丑化了;
  • 我们暂时不关心他是如何做到的,后续我讲 webpack 实现模块化原理时会再次讲到;
  • 另外我们发现代码中依然存在 ES6 的语法,比如箭头函数、const 等,这是因为默认情况下 webpack 并不清楚我 们打包后的文件是否需要转成 ES5 之前的语法,后续我们需要通过 babel 来进行转换和设置;

我们发现是可以正常进行打包的,但是有一个问题,webpack 是如何确定我们的入口的呢?

  • 事实上,当我们运行 webpack 时,webpack 会查找当前目录下的 src/index.js 作为入口;
  • 所以,如果当前项目中没有存在 src/index.js 文件,那么会报错;

当然,我们也可以通过配置来指定入口和出口

1
npx webpack --entry ./src/main.js --output-path ./build

创建局部的 webpack

前面我们直接执行 webpack 命令使用的是全局的 webpack,如果希望使用局部的可以按照下面的步骤来操作。

第一步:创建 package.json 文件,用于管理项目的信息、库依赖等

1
npm init

第二步:安装局部的 webpack

1
npm install webpack webpack-cli -D

第三步:使用局部的 webpack

1
npx webpack

第四步:在 package.json 中创建 scripts 脚本,执行脚本打包即可

1
npm run build

image-20210925142650783

Webpack 配置文件

在通常情况下,webpack 需要打包的项目是非常复杂的,并且我们需要一系列的配置来满足要求,默认配置必然是不可以的。

我们可以在根目录下创建一个 webpack.config.js 文件,来作为 webpack 的配置文件:

image-20210925142729948

继续执行 webpack 命令,依然可以正常打包

1
npm run build

指定配置文件

但是如果我们的配置文件并不是 webpack.config.js 的名字,而是其他的名字呢?

  • 比如我们将 webpack.config.js 修改成了 wk.config.js;

  • 这个时候我们可以通过 –config 来指定对应的配置文件;

    1
    webpack --config wk.config.js

但是每次这样执行命令来对源码进行编译,会非常繁琐,所以我们可以在 package.json 中增加一个新的脚本:

image-20210925142847525

之后我们执行 npm run build 来打包即可。

Webpack 的依赖图

webpack 到底是如何对我们的项目进行打包的呢?

  • 事实上 webpack 在处理应用程序时,它会根据命令或者配置文件找到入口文件;
  • 从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js 文件、cs 文件、图片、字体等);
  • 然后遍历图结构,打包一个个模块(根据文件的不同使用不同的 loader 来解析);

image-20210925142949978

编写案例代码

我们创建一个 component.js

通过 JavaScript 创建了一个元素,并且希望给它设置一些样式;

image-20210925143033261

css-loader 的使用

上面的错误信息告诉我们需要一个 loader 来加载这个 css 文件,但是loader是什么呢?

  • loader 可以用于对模块的源代码进行转换;
  • 我们可以将 css 文件也看成是一个模块,我们是通过 import 来加载这个模块的;
  • 在加载这个模块时,webpack 其实并不知道如何对其进行加载,我们必须制定对应的 loader 来完成这个功能;

那么我们需要一个什么样的 loader 呢?

  • 对于加载 css 文件来说,我们需要一个可以读取 css 文件的 loader;
  • 这个 loader 最常用的是 css-loader;

css-loader 的安装:

1
npm install css-loader -D

css-loader 的使用方案

如何使用这个 loader 来加载 css 文件呢?有三种方式:

  • 内联方式;
  • CLI 方式(webpack5 中不再使用);
  • 配置方式;
  1. 内联方式:内联方式使用较少,因为不方便管理;
    在引入的样式前加上使用的 loader,并且使用!分割
    image-20210925143244986

  2. CLI 方式

    在 webpack5 的文档中已经没有了–module-bind;

    实际应用中也比较少使用,因为不方便管理;

  3. loader 配置方式

    配置方式表示的意思是在我们的 webpack.config.js 文件中写明配置信息:

    • module.rules 中允许我们配置多个 loader(因为我们也会继续使用其他的 loader,来完成其他文件的加载);
    • 这种方式可以更好的表示 loader 的配置,也方便后期的维护,同时也让你对各个 Loader 有一个全局的概览;

    module.rules 的配置如下:

    • rules 属性对应的值是一个数组:**[Rule]**

    • 数组中存放的是一个个的 Rule,Rule 是一个对象,对象中可以设置多个属性:

      test属性:用于对 resource(资源)进行匹配的,通常会设置成正则表达式;

      use属性:对应的值时一个数组:**[UseEntry]**

      • UseEntry 是一个对象,可以通过对象的属性来设置一些其他属性
        loader:必须有一个 loader 属性,对应的值是一个字符串;
        options:可选的属性,值是一个字符串或者对象,值会被传入到 loader 中;
        query:目前已经使用 options 来替代;
      • 传递字符串(如:use: [ ‘style-loader’ ])是 loader 属性的简写方式(如:use: [ { loader: ‘style-loader’} ]);
    • loader属性: Rule.use: [ { loader } ] 的简写。

  4. Loader 的配置代码
    image-20210925143638256

认识 style-loader

我们已经可以通过 css-loader 来加载 css 文件了

但是你会发现这个 css 在我们的代码中并没有生效(页面没有效果)。

这是为什么呢?

因为 css-loader 只是负责将.css 文件进行解析,并不会将解析之后的 css 插入到页面中;

如果我们希望再完成插入 style 的操作,那么我们还需要另外一个 loader,就是 style-loader;

安装 style-loader

1
npm install style-loader -D

配置 style-loader

那么我们应该如何使用 style-loader?

在配置文件中,添加 style-loader;

注意:因为 loader 的执行顺序是从右向左(或者说从下到上,或者说从后到前的),所以我们需要将 style-loader 写到 css-loader 的前面;

image-20210925143850466

重新执行编译 npm run build,可以发现打包后的 css 已经生效了

当前目前我们的 css 是通过页内样式的方式添加进来的;

后续我们也会讲如何将 css 抽取到单独的文件中,并且进行压缩等操作;

如何处理 less 文件?

在我们开发中,我们可能会使用 less、sass、stylus 的预处理器来编写 css 样式,效率会更高。

那么,如何可以让我们的环境支持这些预处理器呢?

  • 首先我们需要确定,less、sass 等编写的 css 需要通过工具转换成普通的 css;

比如我们编写如下的 less 样式:

image-20210925143951238

Less 工具处理

我们可以使用 less 工具来完成它的编译转换:

1
npm install less -D

执行如下命令:

1
npx lessc ./src/css/title.less title.css

less-loader 处理

但是在项目中我们会编写大量的 css,它们如何可以自动转换呢?

这个时候我们就可以使用 less-loader,来自动使用 less 工具转换 less 到 css;

1
npm install less-loader -D

配置 webpack.config.js

image-20210925144130208

执行

1
npm run build

less 就可以自动转换成 css,并且页面也会生效了

认识 PostCSS 工具

什么是 PostCSS 呢?

PostCSS 是一个通过 JavaScript 来转换样式的工具;

这个工具可以帮助我们进行一些 CSS 的转换和适配,比如自动添加浏览器前缀、css 样式的重置;

但是实现这些功能,我们需要借助于 PostCSS 对应的插件;

如何使用 PostCSS 呢?主要就是两个步骤

第一步:查找 PostCSS 在构建工具中的扩展,比如 webpack 中的 postcss-loader;

第二步:选择可以添加你需要的 PostCSS 相关的插件;

命令行使用 postcss

当然,我们能不能也直接在终端使用 PostCSS 呢?

也是可以的,但是我们需要单独安装一个工具 postcss-cli;

我们可以安装一下它们:postcsspostcss-cli

1
npm install postcss postcss-cli -D

我们编写一个需要添加前缀的 css:

https://autoprefixer.github.io/

我们可以在上面的网站中查询一些添加 css 属性的样式;

image-20210925144443971

插件 autoprefixer

因为我们需要添加前缀,所以要安装 autoprefixer:

1
npm install autoprefixer -D

直接使用使用 postcss 工具,并且制定使用 autoprefixer

1
npx postcss --use autoprefixer -o end.css ./src/css/style.css

转化之后的 css 样式如下:

image-20210925144538808

postcss-loader

真实开发中我们必然不会直接使用命令行工具来对 css 进行处理,而是可以借助于构建工具:

在 webpack 中使用 postcss 就是使用postcss-loader来处理的;

我们来安装 postcss-loader:

1
npm install postcss-loader -D

我们修改加载 css 的 loader:(配置文件已经过多,给出一部分了)

注意:因为 postcss 需要有对应的插件才会起效果,所以我们需要配置它的 plugin;

image-20210925144753559

单独的 postcss 配置文件

当然,我们也可以将这些配置信息放到一个单独的文件中进行管理:

在根目录下创建 postcss.config.js

image-20210925144823888

postcss-preset-env

事实上,在配置postcss-loader时,我们配置插件并不需要使用 autoprefixer。

我们可以使用另外一个插件:postcss-preset-env

  • postcss-preset-env 也是一个 postcss 的插件;
  • 它可以帮助我们将一些现代的 CSS 特性,转成大多数浏览器认识的 CSS,并且会根据目标浏览器或者运行时环境添加所需的 polyfill;
  • 也包括会自动帮助我们添加 autoprefixer(所以相当于已经内置了 autoprefixer);

首先,我们需要安装postcss-preset-env

1
npm install postcss-preset-env -D

之后,我们直接修改掉之前的 autoprefixer 即可:

image-20210925144955788

注意:我们在使用某些 postcss 插件时,也可以直接传入字符串

image-20210925145010287

加载图片案例准备

为了演示我们项目中可以加载图片,我们需要在项目中使用图片,比较常见的使用图片的方式是两种:

img元素,设置src属性;

其他元素(比如 div),设置background-image的 css 属性;

image-20210925145300307

file-loader

要处理 jpg、png 等格式的图片,我们也需要有对应的 loader:file-loader

file-loader 的作用就是帮助我们处理import/require()方式引入的一个文件资源,并且会将它放到我们输出的文件夹中;

当然我们待会儿可以学习如何修改它的名字和所在文件夹;

安装 file-loader

1
npm install file-loader -D

配置处理图片的 Rule

image-20210925145421723

图片就出来了

image-20210925145440502

文件的命名规则

有时候我们处理后的文件名称按照一定的规则进行显示:

  • 比如保留原来的文件名、扩展名,同时为了防止重复,包含一个 hash 值等;

这个时候我们可以使用 PlaceHolders 来完成,webpack 给我们提供了大量的 PlaceHolders 来显示不同的内容:

我们这里介绍几个最常用的 placeholder:

  • [ext]: 处理文件的扩展名;
  • [name]:处理文件的名称;
  • [hash]:文件的内容,使用 MD4 的散列函数处理,生成的一个 128 位的 hash 值(32 个十六进制);
  • [contentHash]:在 file-loader 中和[hash]结果是一致的(在 webpack 的一些其他地方不一样,后面会讲到);
  • [hash:<length>]:截图 hash 的长度,默认 32 个字符太长了;
  • [path]:文件相对于 webpack 配置文件的路径;

设置文件的名称

那么我们可以按照如下的格式编写:

这个也是 vue 的写法;

image-20210925145627560

设置文件的存放路径

当然,我们刚才通过 img/ 已经设置了文件夹,这个也是 vue、react 脚手架中常见的设置方式:

其实按照这种设置方式就可以了;

当然我们也可以通过outputPath来设置输出的文件夹;

image-20210925145701244

url-loader

url-loader 和 file-loader的工作方式是相似的,但是可以将较小的文件,转成 base64 的 URI。

安装 url-loader:

1
npm install url-loader -D

image-20210925145741781

显示结果是一样的,并且图片可以正常显示;

但是在 dist 文件夹中,我们会看不到图片文件:

  • 这是因为我的两张图片的大小分别是 38kb 和 295kb;
  • 默认情况下 url-loader 会将所有的图片文件转成 base64 编码

url-loader 的 limit

但是开发中我们往往是小的图片需要转换,但是大的图片直接使用图片即可

  • 这是因为小的图片转换base64之后可以和页面一起被请求减少不必要的请求过程
  • 大的图片也进行转换,反而会影响页面的请求速度

那么,我们如何可以限制哪些大小的图片转换和不转换呢?

  • purl-loader 有一个 options 属性limit,可以用于设置转换的限制;
  • 下面的代码 38kb 的图片会进行 base64 编码,而 295kb 的不会;

image-20210925145943079

认识 asset module type

  • 我们当前使用的 webpack 版本是 webpack5:
    在 webpack5 之前,加载这些资源我们需要使用一些loader,比如raw-loader 、url-loader、file-loader
    在 webpack5 开始,我们可以直接使用资源模块类型(asset module type),来替代上面的这些 loader;

  • **资源模块类型(asset module type)**,通过添加 4 种新的模块类型,来替换所有这些 loader:
    asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现;
    asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现;
    asset/source 导出资源的源代码。之前通过使用 raw-loader 实现;
    asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现;

asset module type 的使用

比如加载图片,我们可以使用下面的方式:

image-20210925150126053

但是,如何可以自定义文件的输出路径和文件名呢?

方式一:修改 output,添加 assetModuleFilename 属性;

image-20210925150147714

方式二:在 Rule 中,添加一个 generator 属性,并且设置 filename;

image-20210925150156349

url-loader 的 limit 效果

我们需要两个步骤来实现:

步骤一:将 type 修改为 asset;

步骤二:添加一个 parser 属性,并且制定 dataUrl 的条件,添加 maxSize 属性;

image-20210925150227524

加载字体文件

如果我们需要使用某些特殊的字体或者字体图标,那么我们会引入很多字体相关的文件,这些文件的处理也是一样的。

首先,我从阿里图标库中下载了几个字体图标:

image-20210925150318754

在 component 中引入,并且添加一个 i 元素用于显示字体图标:

image-20210925150330525

字体的打包

这个时候打包会报错,因为无法正确的处理eot、ttf、woff等文件:

我们可以选择使用 file-loader 来处理,也可以选择直接使用 webpack5 的资源模块类型来处理;

image-20210925150359119

认识 Plugin

Webpack 的另一个核心是 Plugin,官方有这样一段对 Plugin 的描述:While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables

上面表达的含义翻译过来就是:

Loader 是用于特定的模块类型进行转换;

Plugin 可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等;

image-20210925150518661

CleanWebpackPlugin

前面我们演示的过程中,每次修改了一些配置,重新打包时,都需要手动删除 dist 文件夹:

我们可以借助于一个插件来帮助我们完成,这个插件就是 CleanWebpackPlugin;

首先,我们先安装这个插件:

1
npm install clean-webpack-plugin -D

之后在插件中配置:

image-20210925150603715

HtmlWebpackPlugin

另外还有一个不太规范的地方:

  • 我们的 HTML 文件是编写在根目录下的,而最终打包的dist文件夹中是没有index.html文件的。
  • 进行项目部署的时候,必然也是需要有对应的入口文件index.html
  • 所以我们也需要对index.html进行打包处理

对 HTML 进行打包处理我们可以使用另外一个插件:HtmlWebpackPlugin

1
npm install html-webpack-plugin -D

image-20210925150738366

生成 index.html 分析

我们会发现,现在自动在 dist 文件夹中,生成了一个 index.html 的文件:

该文件中也自动添加了我们打包的 bundle.js 文件;

image-20210925150801481

这个文件是如何生成的呢?

  • 默认情况下是根据 ejs 的一个模板来生成的;
  • 在 html-webpack-plugin 的源码中,有一个 default_index.ejs 模块;

自定义 HTML 模板

如果我们想在自己的模块中加入一些比较特别的内容:

比如添加一个noscript标签,在用户的 JavaScript 被关闭时,给予响应的提示;

比如在开发vue或者react项目时,我们需要一个可以挂载后续组件的根标签<div id=”app”></div>;

这个我们需要一个属于自己的 index.html 模块:

image-20210925150951534

自定义模板数据填充

上面的代码中,会有一些类似这样的语法<% 变量 %>,这个是EJS模块填充数据的方式。

在配置 HtmlWebpackPlugin 时,我们可以添加如下配置:

template:指定我们要使用的模块所在的路径;

title:在进行 htmlWebpackPlugin.options.title 读取时,就会读到该信息;

image-20210925151034899

DefinePlugin 的介绍

但是,这个时候编译还是会报错,因为在我们的模块中还使用到一个 BASE_URL 的常量:

image-20210925151117631

这是因为在编译 template 模块时,有一个 BASE_URL:

1
2
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />

但是我们并没有设置过这个常量值,所以会出现没有定义的错误;

这个时候我们可以使用 DefinePlugin 插件;

DefinePlugin 的使用

DefinePlugin 允许在编译时创建配置的全局常量,是一个 webpack 内置的插件(不需要单独安装):

image-20210925151215364

这个时候,编译 template 就可以正确的编译了,会读取到BASE_URL的值;

CopyWebpackPlugin

在 vue 的打包过程中,如果我们将一些文件放到 public 的目录下,那么这个目录会被复制到 dist 文件夹中。

这个复制的功能,我们可以使用 CopyWebpackPlugin 来完成;

安装 CopyWebpackPlugin 插件:

1
npm install copy-webpack-plugin -D

接下来配置 CopyWebpackPlugin 即可:

复制的规则在 patterns 中设置;

from:设置从哪一个源中开始复制;

to:复制到的位置,可以省略,会默认复制到打包的目录下;

globOptions:设置一些额外的选项,其中可以编写需要忽略的文件:

  • .DS_Store:mac 目录下回自动生成的一个文件;
  • index.html:也不需要复制,因为我们已经通过 HtmlWebpackPlugin 完成了 index.html 的生成;

image-20210925151344177

Mode 配置

前面我们一直没有讲 mode。

Mode 配置选项,可以告知 webpack 使用响应模式的内置优化:

  • 默认值是 production(什么都不设置的情况下);
  • 可选值有:’none’ | ‘development’ | ‘production’;

这几个选项有什么样的区别呢?

image-20210925151434001

Mode 配置代表更多

image-20210925151451207