构建现代CSS工程环境

前言

在开发Web应用时,我们不仅使用 HTML 标签构建页面结构、编写大量的 JavaScript 代码控制页面逻辑,还需要 CSS 来控制页面的样式。但我们都知道 CSS 一直专注于页面样式的表现力,而缺乏工程化能力、逻辑判断能力,所以我们在开发现代大型 Web 应用时,通常会使用 Webpack 结合其他预处理器,如:Less/Sass/Stylus等预处理器编写样式代码。 本章主要介绍 Webpack 中如何使用 CSS 代码处理工具,包括:

  • 如何使用 css-loaderstyle-loadermini-css-extract-plugin 处理原生 CSS 文件?
  • 如何使用 Less/Sass/Stylus 预处理器?
  • 如何使用 PostCSS ?

处理原生 CSS 文件

Webpack 如果没做任何额外的配置是不能处理 CSS 文件的,如果导入了 .css 文件,编译过程则会报错:

所以我们在Webpack中处理CSS文件通常都需要用到:

  • css-loader:这个 Loader 会将 CSS 等价编译成 JavaScript 代码,使得 Webpack 可以像处理 JavaScript 代码一样处理 CSS 内容与资源依赖。
  • style-loader:这个 Loader 会将 CSS 的内容以<style>标签的形式,注入到页面中使得样式生效。
  • mini-css-extract-plugin:这个插件会将 CSS 的内容单独拆分为.css文件,在页面中以<link>标签的方式引入 CSS 使得样式生效 。

一般我们使用css-loader先将 CSS 内容进行处理,在开发环境中使用style-loader注入CSS内容使样式生效,在生产环境中使用mini-css-extract-plugin将 CSS 内容拆分为单独的 CSS 文件再以 <link> 标签的方式插入到页面中。 需要注意的是,style-loadermini-css-extract-plugin 不能同时使用,需要通过环境变量判断当前环境并使用恰当的 loader 。如下:

const path = require('path');
const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
	// ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          (process.env.NODE_ENV === 'production' ? MiniCSSExtractPlugin.loader : 'style-loader'),
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCSSExtractPlugin(),
    new HTMLWebpackPlugin()
  ]
}

可以看到我们在定义 .css 文件的解析规则时,use 使用的是数组,这等同于 style-loader(css-loader(css)) 调用,所以数组内 loader 的顺序不能错。 通常在实际开发中,我们很少直接使用 CSS 进行大型 Web 应用的开发,而是配合各种方便的 CSS 预处理器,比如 LessSassStylus,而使用预处理后,我们也只需要在 Webpack 中引入对应的 loader 即可。

使用预处理器

拿 Less 举例,我们需要先安装 lessless-loader

yarn add -D less less-loader

然后我们去修改 Webpack 配置如下:

{
  test: /\.less$/,
    use: [
    (process.env.NODE_ENV === 'production' ? MiniCSSExtractPlugin.loader : 'style-loader'),
    'css-loader',
    'less-loader'
  ]
}

其余两种常用的 Sass 和 Stylus 引入方式和 Less 基本一样,都是下载各自的 loader 然后再修改 Webpack 配置即可。 Sass: yarn add -D sass sass-loader

// sass
{
  test: /\.less$/,
    use: [
    (process.env.NODE_ENV === 'production' ? MiniCSSExtractPlugin.loader : 'style-loader'),
    'css-loader',
    'sass-loader'
  ]
}

Stylus: yarn add -D stylus stylus-loader

// sass
{
  test: /\.less$/,
    use: [
    (process.env.NODE_ENV === 'production' ? MiniCSSExtractPlugin.loader : 'style-loader'),
    'css-loader',
    'stylus-loader'
  ]
}

使用 PostCSS

和上面提到的 Less/Sass/Stylus 一系列预处理器类似,PostCSS 也可以在原生 CSS 的基础上增加更多的可读性、兼容性、模块化以及格式校验等功能,但它和预处理器不同的是,预处理专门定义了一套超集语法,而 PostCSS 和 Babel 类似,只是将你的 CSS 代码进行转换为 AST,然后使用插件做处理的流程框架。 举个例子,Less/Sass/Stylus 等预处理器和 CSS 的关系就像是 TypeScript 和 JavaScript,而 PostCSS 和 CSS 的关系就像是 Babel 和 JavaScript。 好消息是,正如上面的类比一样,PostCSS 可以和预处理器同时存在,并不是互斥关系,也就是说你可以在使用预处理器的基础上再使用 PostCSS。

yarn add -D postcss postcss-loader

修改 Webpack 配置如下:

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          (process.env.NODE_ENV === 'production' ? MiniCSSExtractPlugin.loader : 'style-loader'),
          {
            loader: 'css-loader',
            options: {
              // 在处理到使用 @import 语法时 先用 css-loader 的前 n 个 loader 处理
              // n = importLoaders
              importLoaders: 1
            }
          },
          'postcss-loader',
          'less-loader'
        ]
      }
    ]
  },

此时这样只是相当于安装了一个"空壳",正如上面所说,需要使用 PostCSS 插件来处理。下面我们安装一个 autoprefixer 插件,该插件可以自动添加各浏览器厂商前缀。

yarn add -D autoprefixer

项目根目录下添加 postcss.config.js 文件进行插件引用配置。

module.exports = {
  plugins: [
    require("autoprefixer")
  ]
}

至此,我们已经完成了现在 CSS 工程环境搭建的全部过程。

总结

最后总结一下,因为我们的原生 CSS 不能直接被 Webpack 所识别,所以我们先使用 css-loader + style-loadercss-loader + mini-css-extract-plugin 处理原生 CSS 文件,而因为原生 CSS 存在一些缺陷,后续我们又使用了 Less/Sass/Stylus 预处理器以及 PostCSS 以帮助我们写出更清晰简洁,复用性更高的代码。