深入 Webpack5 等构建工具系列二(4) - webpack 中 css-loader 的使用和 Rule 配置规则

534 阅读9分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

上一篇文章,下面,我们来编写一个案例代码(本案例是在官方文档案例的基础上修改而来)。

  • 创建一个 component.js 文件
    • 通过 JavaScript 创建一个元素,并且希望给它设置一些样式;

好,我们先在项目的 ./src/js/ 路径下创建 component.js 文件,文件内容如下:

function component() {
  const element = document.createElement('div');

  // 这里是有意使用了这样一个数组形式的案例,因为我们后面还会讲 lodash 的打包,
  // 就是 lodash 到底会不会和其它代码打包到一块,然后如何对这个东西做一个分割。
  // 使用数组的 join() 方法将一个字符串数组转成了一个字符串,同时使用空格做分割。
  element.innerHTML = ['Hello', 'webpack'].join(' ');
  element.className = 'content';

  return element;
}
// 调用 component() 函数创建一个包含了指定内容的 div 元素,再将其添加到 body 元素的最后,以便展示效果。
document.body.appendChild(component());

这时,如果我们直接执行 npm run build 命令对项目代码进行打包,问:这个 div 元素能不能在浏览器中被看到?答案是不能。为甚莫呢?因为这里虽然创建了 component.js 文件,但是它没有在任何地方被引入,所以就不会被打包。

那如果我们希望对这个文件进行打包的话,可以在项目中引入它,比如我们在 main.js 中引入它:

image-20210123161954816.png

然后再执行 npm run build 命令后在浏览器中打开 index.html 页面,就能看到效果啦:

image-20210123162203506.png

下面,我们希望通过这个 divcontent 这个类给这个 div 添加一些样式。

好,我们先在项目的 src 目录下创建 css 文件夹,再在 css 文件夹下创建 index.css 文件,文件内容如下:

.content {
  color: red;
}

我们先来思考一下,当前情况下,这个 index.css 文件到时候会被打包吗?显然,不会。因为它不在依赖关系图中。

那为了让这个文件也被打包,我们需要对它进行一个引入。可以在入口文件中引入它,但考虑到这个文件是和 component.js 关联的,我们在 component.js 中对它进行引入:

image-20210123172543710.png

引入之后就意味着到时候这个 index.css 文件会和其它被引入的 js 文件一起,被打包到配置文件中指定的生成文件的输出位置(这里是 ./build/bundle.js)。

那当前的话能不能进行打包呢?我们执行 npm run build 命令看下:

image-20210123212555578.png

可以看到,运行报错了,大意是:在加载 ./src/css/index.css 文件时,模块解析失败了,你可能需要一个合适的 loader 来处理这种文件类型,当前没有配置任何 loader 来处理这个文件。再来看我们的配置文件(wk.config.js),里面没有配置任何 loader,所以才会报错。

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

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

你可能会问,之前 js 怎么知道处理呢?这是因为 js 的处理过程是 webpack 已经内置过的(但是它其实内置的也不是特别好,所以我们后面还会讲到 babel-loader,它可以对 js 再做进一步的处理)。

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

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

css-loader 的安装(因为是开发时依赖,所以安装时使用 -D 即可):

npm install css-loader --save-dev

上面的命令也可以简写为:

npm i css-loader -D

好,下面我们执行 npm i css-loader -D 命令,在当前项目中安装 css-loader,安装完成后就意味着当前项目中的 node_modules 文件夹下已经有 css-loader 了,然后我们就可以通过这个 css-loader 来处理 css 文件了。

那么,现在我们直接执行 npm run build 命令,webpack 是不是就能正常处理了呢?当然不是,不信你看:

image-20210123221806398.png

还是报了之前的错误。这是为甚莫呢?因为你并没有告诉 webpack 当前项目中的 css 文件要被 css-loader 处理,所以虽然已经安装了 css-loader,但是它和 css 文件还没有关联起来。那么怎么样把 css-loadercss 文件关联起来呢?

使用 loader

使用 loader 来加载 css 文件,3 种方式

  1. 内联方式;
  2. CLI 方式(webpack5 中不再使用);
  3. 配置方式(推荐);

注意:虽然 webpack5 的官方文档中早前还列出了 CLI 方式(事实上,虽然 webpack 已经更新到了 5 版本,但其官方文档还没有做实时的更新,有些内容可能还是 v4 版本的),但因为该方式中需要的 --module-bind 参数(webpack4 中存在)在 webpack5 中已经没有了(通过在命令行终端运行 webpack --help=verbose 命令查看所有支持的命令和选项列表,也不难发现 webpack5 中已经不存在 --module-bind 参数了),所以这种 CLI 方式在 webpack5 中已经不支持了(实践的结果是会报错:error: unknown option '--module-bind')。当然,我们大可不必纠结这个东西,因为开发中通常不会使用这种 CLI 方式,而是会用配置方式,因为它通俗易懂也方便管理。

  • 内联方式:内联方式使用较少,因为不方便管理;

    • 在引入的样式前加上使用的 loader,并且使用 ! 分割;

    image-20210124101809872.png

  • CLI 方式(webpack5 已不支持)

    • webpack5 的文档中已经没有了 --module-bind
    • 实际应用中也比较少使用,因为不方便管理;
  • 配置方式(推荐)

    • 在配置文件(通常为 webpack.config.js)中写明配置信息
      • module.rules 中允许我们配置多个 loader(因为我们也会继续使用其它的 loader,来完成其它文件的加载);
      • 这种方式可以更好地表示 loader 的配置,也方便后期的维护,同时也让你对各个 loader 有一个全局的概览;
    • module.rules 的配置如下:
      • rules 属性对应的是一个数组:[Rule]
      • 数组中存放的是一个个的 RuleRule 是一个对象,对象中可以设置多个属性;
        • test 属性:用于对 resource(资源)进行匹配,通常会设置成正则表达式;
        • use 属性:对应的值是一个数组:[UseEntry]
          • UseEntry 是一个对象,可以通过该对象的属性来设置一些其它属性
            • loader:必须有一个 loader 属性,对应的值是一个字符串;
            • options:可选的属性,值是一个字符串或者对象,值会被传入到 loader 中;
            • query:目前已经使用 options 来替代;
          • 传递字符串(如:use: [ 'style-loader' ])是 loader 属性的简写方式(如:use: [ { loader: 'style-loader'} ]);
        • loader 属性:Rule.use: [ { loader } ] 的简写。

下面我们分别使用内联方式和配置方式来实践一下:

内联方式

首先确定已经安装了 css-loader(当然,我们这里刚才已经安装过了),安装好以后就意味着项目中已经有这个模块了,那现在该如何指定它来处理 css 文件呢?

我们打开引入了 index.css 文件的 component.js 文件,将

import '../css/index.css';

这行代码为修改为:

import 'css-loader!../css/index.css';

修改后的内容就表示到时候在加载 ../css/index.css 这个文件时,使用 css-loader 来帮忙进行处理。

补充:如果后面还需要使用另外一个 loader,比如说 style-loader,那么继续在前面添加即可:

import 'style-loader!css-loader!../css/index.css';

好,那我们这里只添加 css-loader 进行处理,下面我们执行 npm run build 命令看下效果:

image-20210124095942132.png

可以看到,没有报错信息了,打包成功。我们再去浏览器中看下效果:

image-20210124100345754.png

咦~样式没生效啊,别急哈,要让样式生效还需要另外一个东西,但可以确定的是,当前 css-loader 已经对 css 文件正常处理过了,因为如果它不知道怎么处理,肯定是会报错的。而现在没有报错,说明已经正常处理了。至于页面里的样式并没有显示出来的原因,则是当前编写的样式并没有被插入到页面中,待会还需要通过另外一个 loader 将其插入到页面中,然后样式才会生效。

配置方式

我们已经知道,配置文件 webpack.config.js(我们这里已改名为 wk.config.js)中导出的是一个对象,那对象里面的话是可以添加很多的属性的。那配置 loader 呢就需要添加 module 属性,module 属性的值也是一个对象,既然是对象,里面又可以放键值对啦,那我们这里需要放一个键为“rules”,值为一个数组的键值对。基本结构如下:

image-20210124141102444.png

好,那 rules 数组里放什么呢?嗯,我们放的是 rule 对象。rule 对象中可以设置多个属性,我们这里先讲最常用的几个属性。

先上示例代码:

module: {
  rules: [
    {
      // 规则使用正则表达式
      test: /\.css$/, // 匹配资源
      loader: 'css-loader'
    }
  ]
}

下面对上面代码中的 testloader 属性分别做解释。

首先是 test 属性,用来匹配资源,就是说那些能被 test 属性对应的规则匹配到的资源待会就可以被 loader 属性中指定的 loader 做处理了。那这个规则怎么写呢?我们使用正则表达式来编写规则,也就是说最终匹配资源文件时是通过正则表达式来匹配的。那这里的正则表达式 /\.css$/ 的意思是匹配以 .css 结尾的文件(正则表达式中的 . 是特殊字符,所以这里要匹配 . 的话需要在前面添加 \ 进行转义,而 $ 字符表示匹配以前面的东西结尾的内容)。

其次是 loader 属性,其值可以是一个字符串,即要指定的 loader 的名字(实际上,loader 属性是另外一种写法的简写,这个我们后面再讲)。

所以上面这一条 rule 的意思就是:当匹配到 css 文件时,就使用 css-loader 对它(css 文件)进行处理。

但是因为 loader 属性是另外一种写法的简写,而且开发中我们其实很少用到这个 loader 属性,所以接下来我们对上面的示例代码进行修改:

module: {
  rules: [
    {
      // 规则使用正则表达式
      test: /\.css$/, // 匹配资源
      use: [
        { loader: 'css-loader' }
      ]
    }
  ]
}

我们用 use 属性替换掉了 loader 属性,use 属性对应一个数组,里面可以放多个 UseEntry 对象,我们这里先放一个对象,在该对象中再对 loader 属性进行设置后,其实就等价于前一个示例的写法了,但这次的写法是完整版,更常用哦~

而关于 UseEntry 对象中的 options 参数,我们现在只需要知道有这么个属性即可,后面讲自定义 loader 时我们再细讲。

其实还有一种指定 loader 的写法,就是直接在 use 属性对应的数组中传递字符串,如:

module: {
  rules: [
    {
      // 规则使用正则表达式
      test: /\.css$/, // 匹配资源
      use: [
        'css-loader'
      ]
    }
  ]
}

需要说明的是,传递字符串(如:use: [ 'style-loader' ])是 loader 属性的简写方式(如:use: [ { loader: 'style-loader'} ])。前面 Rule 对象的 loader 属性也是 Rule.use: [ { loader } ] 的简写。

所以呢,以下三种写法是等价的:

loader: 'css-loader'
use: [
  { loader: 'css-loader' }
]
use: [
  'css-loader'
]

1 种和第 3 种写法都是第 2 种写法的简写形式。当只需要指定一个 loader 时,可以采用第 1 种写法,因为写起来更简单;而当需要指定多个 loader 同时除了 loader 没有其它选项需要传递(即不需要传 options 参数)时,可以采用第 3 种写法,因为写起来更简单。

好,那我们这里使用第 3 种方式(use 数组中传递字符串)来进行配置,配置文件 wk.config.js 中的完整代码如下:

const path = require('path')

module.exports = {
  entry: "./src/main.js",
  output: {
    filename: "bundle.js",
    // 必须是一个绝对路径
    path: path.resolve(__dirname, './build')
  },
  module: {
    rules: [
      {
        // 规则使用正则表达式
        test: /\.css$/, // 匹配资源
        use: [
          // { loader: 'css-loader' }
          'css-loader'
        ],
        // loader: 'css-loader'
      }
    ]
  }
}

然后回到 component.js 文件中,将 import 'css-loader!../css/index.css'; 这行代码修改为 import '../css/index.css';,再到 package.json 文件中确认一下 build 这项脚本的内容是否有问题(我们这里 "build": "webpack -c wk.config.js",就是没问题的)。接下来,我们来运行 npm run build 命令,看看刚才配置之后 webpack 是否能找到我们的指定的 loader,来对有关文件进行处理。运行结果如下:

image-20210124163310746.png

可见,除了一个警告没有任何报错,说明 index.css 文件已经被 css-loader 正常处理了。