Vue 项目之 Webpack 对 CSS 的打包(1)

2,413 阅读8分钟

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

前面我们用 webpack 打包的都是 JS 文件,如果我们还想对 CSS 文件进行打包呢?

下面,我们就用一个案例来讲解 webpackCSS 文件的打包。

上一节的最后,我们在 element.js 文件中创建了一个 <div> 元素,并且给它添加了 title 类。现在,我们想给这个 title 类设置对应的样式,并且是在 CSS 文件中编写样式。比如,我们在项目目录下的 src 目录下新建 css 目录,然后在 css 目录下新建 style.css 文件,向该文件中添加如下样式代码:

.title {
  color: red;
  font-size: 30px;
  font-weight: 700;
}

现在,./src/css 下已经有 style.css 这个 css 文件了,那么请问,现在运行打包命令,这个 css 文件会被打包吗?答案是不会。因为上一节我们已经讲过,webpack 是根据它的依赖图来打包模块的。而我们这个 style.css 文件当前并没有加入到依赖图中,因为从入口(./src/main.js)开始找,最终并没有找到这个文件。

但我们是希望对这个文件进行打包的,那该怎么做呢?我们可以在 ./src/main.js 中引入这个 css 文件:

import { sum } from './js/math.js';
const { formatPrice } = require('./js/format.js');

import './js/element';
import './css/style.css'; // 导入 style.css 文件
...

这样一来,作为打包入口的 main.js 文件就依赖 style.css 这个文件了,到时候也会对这个 css 文件进行打包。当然,我们也可以不在 main.js 中导入这个 css 文件,因为这个 css 文件其实和 ./js/element.js 文件的关联性更强,所以我们可以在 ./js/element.js 中导入这个 css 文件:

import '../css/style.css'; // 导入 style.css 文件

const el = document.createElement('div');
...

注意导入时的路径别写错了,不然打包时会报如下格式的错误:Module not found: Error: Can't resolve xxx in xxx

这样一来,作为打包入口的 main.js 文件依赖 element.js,而 element.js 又依赖 style.css,所以最终 style.css 文件也会被打包。

好,那我们运行 npm run build 命令进行打包:

image-20211102210741131

你会发现,打包失败了,出现了一个错误,大意是:./src/css/style.css 这个模块(在 webpack 中,一个文件可以看成一个模块)解析失败了,你可能需要一个合适的加载器(loader)来处理这种文件类型(这里指 css 文件类型),而当前没有配置加载器去处理这个文件。

就是说当前 webpack 不知道如何处理 css 文件,你可能会问:那之前我们打包 js 文件怎么就可以呢?这是因为 webpack 默认就支持 CommonJSAMDESM 等模块类型[^2],所以才默认支持 js 文件。而 css 文件 webpack 本身默认是不支持的。我们要知道,webpack 事实上是一个很大的生态,除了它本身的核心代码,还包括了 loadersplugins 等很多其它东西,这些东西一起造就了强大的 webpack。而在这个庞大的生态中,就有能够帮助我们处理 css 文件类型的 loadercss-loader(我们常说 webpack 能够打包 css 文件的原因就是它已经有帮助我们处理 cssloader 了)。所以,我们得有对应的 loader 来处理 csswebpack 才能对 css 文件进行打包。

  • loader 是什么?

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

    • 对于加载 css 文件来说,我们需要一个可以读取 css 文件的 loader
    • 这个 loader 最常用的是 css-loader
    • 当然,你也可以自己编写一个对应的 loader
  • 我们选择使用 css-loader,需要先去下载了安装它:

    • 因为我们已经有了 npm 这个包管理工具,所以我们可以直接使用 npm 来安装它:

      npm install -D css-loader
      

      因为我们这里加载 css 文件的需求只在开发阶段有,所以使用 -D 标志作为开发时依赖进行安装。

安装完 css-loader 后,我们还不能马上打包,因为 webpack 没有那么智能,我们还需要告诉它在加载 css 文件的时候使用 css-loaderwebpack 不会自动帮我们找到 css-loader,因此我们必须手动指定在加载 css 文件时让 webpack 使用 css-loader,甚至使用多个 loader(而且多个 loader 之间还有顺序要求)。所以,我们就要去进行配置了,既然是配置,就要想到去 webpack.config.js 文件中进行配置。

不过,使用 css-loader 来加载 css 文件的方式其实有 3 种:

  • 内联方式:内联方式使用较少,因为不方便管理,其用法如下:

    在引入的样式文件前加上要使用的 loader,并在 loader 和样式文件之前使用 ! 分隔:

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

    上面代码的意思是在导入 ../css/style.css 文件时,使用 css-loader 对它进行加载。

  • CLI 方式(webpack5 中不再使用):

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

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

使用配置方式来指定 webpack 使用 css-loader 来加载 css 文件的做法如下:

  • webpack.config.js 文件中,来到 module.exports 对应的对象中,之前我们已经配置了打包的入口(entry)和出口(output),现在我们还可以添加模块(module,前面说过,一个 css 文件就是一个模块)的配置:

    const path = require('path');
    
    module.exports = {
      entry: './src/main.js',
      output: {
        path: path.resolve(__dirname, './build'),
        filename: 'bundle.js'
      },
      module: {
        rules: [
          {
            test: /\.css$/, // 正则表达式
            loader: 'css-loader' // 写法一
          }
        ]
      }
    }
    

    module.rules 属性对应的值是一个数组,因为我们以后可能会有多个规则(rule)要配置;

    上面规则对象中的 test 属性对应的值的意思是:只要是以 .css 结尾的模块(文件),就会被匹配上当前的规则,就会使用当前规则中的 loader。需要注意正则表达式中的 . 具有特殊的含义(会匹配任意字符),我们这里想要匹配 . 这个字符,需要使用 \ 对它进行转义;

    上面规则对象中的 loader: 'css-loader' 其实是一种语法糖,也可以写成 use: 'css-loader',但一般我们在使用 use 时会使用数组:

    // 写法二
    use: [
      'css-loader'
    ]
    

    为什么要使用数组呢?因为在这里我们不确定在加载 css 模块时仅使用一个 loader 就能搞定,事实上,加载 css 模块时仅使用 css-loader 达不到最终的效果,你会发现页面上的样式并没有生效,因为我们还需要其它的 loader 把我们用 css-loader 加载出来的 css 代码通过 <style> 元素添加到 DOM 文档中,才能让样式真正生效。所以,通过数组,我们就可以使用多个 loader 了。

    但是,我们在使用某个 loader 时,也可能需要给它传递一些参数,因此,use 属性真正完整的写法是这样的:

    use: [
      { loader: 'css-loader', options: { ... } }
    ]
    

    但我们这里没有需要传递参数的需求,所以可以不加 options 属性:

    // 写法三
    use: [
      { loader: 'css-loader' }
    ]
    

    而当我们没有传递参数的需求时,我们一般就会这样写:

    use: [
      'css-loader'
    ]
    

我们通过内联方式或配置方式(常用)指定好 webpack 在遇到 css 文件时使用 css-loader 这个 loader 对文件进行加载后,再来运行 npm run build 命令进行打包:

image-20211103064620856

可以看到,除了一个 warning 信息(目前我们先忽略它),之前的报错已经没有了,这就说明 css 文件已经被成功加载了(成功加载但并没有生效,页面上还没有出现对应的效果,这涉及到另外一个原因,我们后面再说)。

注意:在使用配置方式指定 loader 的使用时,需要把前面通过内联方式添加的代码删掉(要还原为使用内联方式之前的代码),否则打包会报错:

image-20211103072639535

以上,就是关于 loadercss-loader)的基本配置过程,根据不同的情况,我们会使用不同的写法。