前言

Webpack 想要实现的是整个前端项目的模块化,项目中的各种资源(包括 CSS 文件、图片等)都应该属于需要被管理的模块。换句话说, Webpack 不仅是 JavaScript 模块打包工具,还是整个前端项目(前端工程)的模块打包工具。也就是说,我们可以通过 Webpack 去管理前端项目中任意类型的资源文件。

因为 Webpack 实现不同种类资源模块加载的核心就是 Loader,所以今天我来和你聊聊 Webpack 的 Loader 机制。

CSS样式loader

首先,我们尝试通过 Webpack 打包项目中的一个 CSS 文件,由此开始探索 Webpack 是如何加载资源模块的?

main.css:

  1. h2 {
  2. position: absolute;
  3. left: 50%;
  4. transform: translate(0, -50%);
  5. color: red;
  6. }

因为我们要尝试打包相应的css文件,所以在index.js文件中引入main.css一同打包:

  1. import create from "./heading";
  2. //引入css
  3. import "./css/main.css";
  4. const heading = create();
  5. document.body.append(heading);

下面我们尝试直接打包,显示错误:
image.png
错误信息大体的意思是说,在解析模块过程中遇到了非法字符,而且错误出现的位置就是在我们的 CSS 文件中。

出现这个错误的原因是因为 Webpack 内部默认只能够处理 JS 模块代码,也就是说在打包过程中,它默认把所有遇到的文件都当作 JavaScript 代码进行解析,但是此处我们让 Webpack 处理的是 CSS 代码,而 CSS 代码是不符合 JavaScript 语法的,所以自然会报出模块解析错误。

这里有一个非常重要的提示:You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. (我们需要用适当的加载器来处理这种文件类型,而当前并没有配置一个可以用来处理此文件的加载器)。

根据这个错误说明,我们发现 Webpack 是用 Loader(加载器)来处理每个模块的,而内部默认的 Loader 只能处理 JS 模块,如果需要加载其他类型的模块就需要配置不同的 Loader。这也就引出了我们今天的主角:Loader。

loader 的使用方式

处理上面的错误需要的是一个可以加载 CSS 模块的 Loader,最常用到的是 css-loader。我们需要通过 npm 先去安装这个 Loader,然后在配置文件中添加对应的配置,具体操作和配置如下所示:

  1. cnpm install --save-dev css-loader

修改webpack配置文件:

  1. ...
  2. module.exports = {
  3. ...
  4. module: {
  5. rules: [
  6. {
  7. test: /\.css$/,
  8. use: 'css-loader'
  9. },
  10. ],
  11. }
  12. }

在配置对象的 module 属性中添加一个 rules 数组。这个数组就是我们针对资源模块的加载处理的规则配置,其中的每个规则对象都需要设置两个属性:

  • 首先是 test 属性,它是一个正则表达式,用来匹配打包过程中所遇到文件路径,这里我们是以 .css 结尾;
  • 然后是 use 属性,它用来指定匹配到的文件需要使用的 loader,这里用到的是 css-loader。
    配置完成过后,我们回到命令行终端重新运行打包命令,打包过程就不会再出现错误了,因为这时 CSS 文件会交给 css-loader 处理过后再由 Webpack 打包。

    样式模块加载问题

    修改上面配置文件之后,我们再次打包:
    image.png
    成功打包css文件,但是当我们运行index.html文件时,css并没有起作用。结果显示
    image.png
    根据我们的main.css,上面“打包尝试”应该水平居中且字体为红色。因此这里的main.css并没有起作用。

下面我们来分析产生这个问题的真正原因,首先,我们找到刚刚生成的 webpack.js 文件,因为这个文件是 Webpack 打包后的结果,所有的模块都应该在这个文件中出现。

我们在webpack.js中找到css打包后对应的函数:
image.png
仔细阅读这个文件,我们会发现 css-loader 的作用是将 CSS 模块转换为一个 JS 模块,具体的实现方法是将我们的 CSS 代码 push 到一个数组中,这个数组是由 css-loader 内部的一个模块提供的,但是整个过程并没有任何地方使用到了这个数组。

因此这里样式没有生效的原因是: css-loader 只会把 CSS 模块加载到 JS 代码中,而并不会使用这个模块。

所以这里我们还需要在 css-loader 的基础上再使用一个 style-loader,把 css-loader 转换后的结果通过 style 标签追加到页面上。

安装style-loader:

  1. cnpm install style-loader --save-dev

安装完 style-loader 之后,我们将配置文件中的 use 属性修改为一个数组,将 style-loader 也放进去。这里需要注意的是,一旦配置多个 Loader,执行顺序是从后往前执行的,所以这里一定要将 css-loader 放在最后,因为必须要 css-loader 先把 CSS 代码转换为 JS 模块,才可以正常打包,具体配置如下:

  1. const path = require('path')
  2. module.exports = {
  3. entry: './src/index.js',
  4. mode: 'none',
  5. output: {
  6. filename: 'main.js',
  7. path: path.join(__dirname, 'dist')
  8. },
  9. module: {
  10. rules: [
  11. {
  12. test: /\.css$/,
  13. use: ['style-loader', 'css-loader']
  14. },
  15. ],
  16. }
  17. }

配置完成之后,再次回到命令行重新打包,此时 webpack.js 文件中会额外多出两个模块。篇幅的关系,我们这里不再仔细解读。style-loader 的作用总结一句话就是,将 css-loader 中所加载到的所有样式模块,通过创建 style 标签的方式添加到页面上。
**
image.png

图片文件的处理loader

理图片有两个loader可用:

  • url-loader (像 file loader 一样工作,但如果文件小于限制,可以返回 data URL)
  • file-loader (将文件发送到输出文件夹,并返回(相对)URL)

在这里我们先提前安装两个依赖:

  1. cnpm install url-loader file-loader --save-dev

关键点:当图片大小小于limit属性设置值时,可以使用url-loader,但当图片过大,还是使用file-loader。
**
我们尝试使用图片资源,style.css文件:

  1. h2 {
  2. position: absolute;
  3. left: 50%;
  4. transform: translate(0, -50%);
  5. color: red;
  6. }
  7. .container {
  8. margin: 100px auto;
  9. height: 200px;
  10. background: url("../assets/webpack.png") no-repeat;
  11. }

index.html文件:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Webpack </title>
</head>

<body>
  <div class="container"></div>
  <script src="dist/main.js"></script>
</body>
</html>

运行 npx webpack 会发现有一个图片资源处理失败的提示,如下图:

image.png
我们添加图片资源处理 loader,webpack.config.js 文件:

const path = require('path')

module.exports = {
  entry: './src/index.js',
  mode: 'none',
  output: {
    filename: 'main.js',
    path: path.join(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          { loader: 'url-loader', options: {limit: 13000} },
        ],
      },
    ],
  }
}

打包后运行效果如下图所示:
image.png
我们再把 webpack.config.js 的限制大小处理调小,重新打包后,发现打包失败,由于图片过大,无法使用url-loader 而又找不到 file-loader。,且少 file-loader

{
  test: /\.(png|jpg|gif)$/,
  use: [
    { loader: 'url-loader', options: {limit: 1024} },
  ],
},

image.png

为什么要在 JS 中加载其他资源

说到这里,你可能会产生疑惑:Webpack 为什么要在 JS 中载入 CSS 呢?不是应该将样式和行为分离么?

其实 Webpack 不仅是建议我们在 JavaScript 中引入 CSS,还会建议我们在代码中引入当前业务所需要的任意资源文件。因为真正需要这个资源的并不是整个应用,而是你此时正在编写的代码。

常用的 loader

名称 链接
file-loader https://webpack.js.org/loaders/file-loader/
url-loader https://webpack.js.org/loaders/url-loader/
babel-loader https://webpack.js.org/loaders/babel-loader/
style-loader https://webpack.js.org/loaders/style-loader/
css-loader https://webpack.js.org/loaders/css-loader/
sass-loader https://webpack.js.org/loaders/sass-loader/
postcss-loader https://webpack.js.org/loaders/postcss-loader/
eslint-loader https://github.com/webpack-contrib/eslint-loader
vue-loader https://github.com/vuejs/vue-loader

其他 loader 使用

sass-loader

新增一个 sass 样式文件并且在 index.js 文件中引用,执行 npx webpack 构建命令。

test.scss

$bg: yellow;

body {
  background-color: $bg;
}
// src/index.js

import "./css/test.scss";

会发现没有找到对应的 loader 处理报错,如下图:

image.png
处理上面的错误需要的是一个可以加载 sass 模块的 Loader,最常用到的是 sass-loader。我们需要通过 npm 先去安装这个 Loader,然后在配置文件中添加对应的配置,具体操作和配置如下所示:

cnpm install sass sass-loader --save-dev

webpack.config.js 配置

module.exports = {
   module: {
    rules: [
      {
        test: /\.(c|sa|sc)ss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      },
      ...
    ],
  }
}

postcss-loader

有时候,我们写的一些 CSS 样式高级新特性,需要兼容包不同版本浏览器的样式,例如 display: flex; 在部分浏览器的写法是不一样的,为了提高效率,我们可以使用 postcss-loader 进行样式的 autoprefixer。

我们需要安装 postcss-loaderpostcss

cnpm install --save-dev postcss-loader postcss

给 test.scss 增加 样式,如下:

$bg: yellow;

body {
  background-color: $bg;
}

.container {
  display: flex;
}

修改 webpack.config.js 配置

module.exports = {
 ...
  module: {
    rules: [
      {
        test: /\.(c|sa|sc)ss$/,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
      },
    ]
}

上述只是配置了 postcss-loader 处理 css 资源,如果需要进行 autoprefixer 处理,还需要一个插件:postcss-preset-env,并且进行 postcss 的配置。

cnpm install postcss-preset-env --save-dev

根目录新建 postcss.config.js,并且进行配置,具体如下:

module.exports = {
    plugins: [
        [
            "postcss-preset-env",
        ],
    ],
};

为了明确对应明确兼容浏览器的范围,需要进行兼容浏览器列表的配置。在根目录新建 .browserslistrc 文件,配置如下:

> 1%
last 2 versions
not ie <= 8

拓展:

babel-loader

资源模块

资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
在 webpack 5 之前,通常使用:

资源模块类型(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,并且配置资源体积限制实现。

下面我们来使用资源模块加载资源,之前的 url-loader 配置可以改用一下的配置

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        // use: [
        //   { loader: 'url-loader', options: {limit: 1024} },
        // ],
        type: 'asset',
        // 设置文件名
        generator: {
          filename: 'static/[hash:6][ext][query]'
        },
        parser: {
          // url-loader 的 limit 设置
          dataUrlCondition: {
            maxSize: 3 * 1024 // 4kb
          }
        }
      },
    ],
  }
}

资料