构建CSS工程环境

13views

本篇主要总结 Webpack 中如何处理 CSS,包括:

  1. 如何使用 css-loaderstyle-loadermini-css-extract-plugin处理 CSS 文件?
  2. 如何使用 Less/Sass/Stylus?
  3. 如何使用 PostCSS

如何处理CSS资源

众所周知,Webpack 并不能识别 CSS 文件,为此通常需要用到:

  1. css-loader

该 Loader 会将 CSS 等价翻译为形如module.exports = "${css}"的JS代码,使得 Webpack 能够如同处理 JS 代码一样解析 CSS 内容与资源依赖;

  1. style-loader

该 Loader 将在产物中注入一系列 runtime 代码,这些代码会将 CSS 内容注入到页面的 <style> 标签,使得样式生效;

  1. mini-css-extract-plugin

该插件会将 CSS 代码抽离到单独的 .css 文件,并将文件通过 <link> 标签方式插入到页面中。

假如有如下CSS代码:

image.png

那么css-loader会处理成:

image.png

style-loader会处理成:

image.png

css-loader

css-loader提供了很多处理 CSS 代码的基础能力,包括 CSS 到 JS 转译、依赖解析、Sourcemap、css-in-module 等,基于这些能力,Webpack 才能像处理 JS 模块一样处理 CSS 模块代码。

我们现在先简要配置一下:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["css-loader"],
      },
    ],
  },
};

在经过css-loader处理后,得到这样的结果:

// 源码
.main-hd {
  font-size: 10px;
}
// 转译后

//...

var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));

// Module

___CSS_LOADER_EXPORT___.push([

  module.id, 

  ".main-hd {\n    font-size: 10px;\n}", ""

]);

// Exports

/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);

//...

但是这段字符串只是被当作普通 JS 模块处理,并不会实际影响到页面样式,后续还需要:

  1. 开发环境:使用style-loader将样式代码注入到页面\<style>标签;
  2. 生产环境:使用mini-css-extract-plugin将样式代码抽离到单独产物文件,并以\<link>标签方式引入到页面中。

注意:为什么生产环境和开发环境使用两种方式。

  1. style-loader支持 HMR,不生成额外 CSS 文件,提升开发速度。且CSS 样式的加载依赖于 JS 文件的执行,因此页面渲染需要等待 JS 完全加载。
  2. mini-css-extract-plugin提取独立 CSS 文件,适合优化资源加载和缓存。且独立的 CSS 文件支持浏览器预加载。CSS 不依赖 JS 执行,浏览器可以并行加载 CSS 文件。

style-loader

此时如果我们再使用style-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};

执行后代码会被转译为类似这样的代码:

// Part1: css-loader 处理结果,对标到原始 CSS 代码
const __WEBPACK_DEFAULT_EXPORT__ = (
"body {\n    background: yellow;\n    font-weight: bold;\n}"
);
// Part2: style-loader 处理结果,将 CSS 代码注入到 `style` 标签
injectStylesIntoStyleTag(
 __WEBPACK_DEFAULT_EXPORT__
)

经过 style-loader + css-loader 处理后,样式代码最终会被写入 Bundle 文件,并在运行时通过 style 标签注入到页面。这种将 JS、CSS 代码合并进同一个产物文件的方式有几个问题:

  • JS、CSS 资源无法并行加载,从而降低页面性能;
  • 资源缓存粒度变大,JS、CSS 任意一种变更都会致使缓存失效。

mini-css-extract-plugin

因此,生产环境中通常会用 mini-css-extract-plugin 插件替代 style-loader,将样式代码抽离成单独的 CSS 文件。

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HTMLWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    module: {
        rules: [{
            test: /\.css$/,
            use: [
                // 根据运行环境判断使用那个 loader
                (process.env.NODE_ENV === 'development' ?
                    'style-loader' :
                    MiniCssExtractPlugin.loader),
                'css-loader'
            ]
        }]
    },
    plugins: [
        new MiniCssExtractPlugin(),
        new HTMLWebpackPlugin()
    ]
}

需要注意:

  1. mini-css-extract-plugin 库同时提供 Loader、Plugin 组件,需要同时使用
  2. mini-css-extract-plugin 不能与 style-loader 混用,否则报错
  3. mini-css-extract-plugin 需要与 html-webpack-plugin 同时使用,才能将产物路径以 link 标签方式插入到 html 中

预处理器

社区在 CSS 原生语法基础上扩展出一些更易用,功能更强大的 CSS 预处理器(Preprocessor),比较知名的有 LessSassStylus 。这些工具各有侧重,但都在 CSS 之上补充了扩展了一些逻辑判断、数学运算、嵌套封装等特性,基于这些特性,我们能写出复用性、可读性、可维护性更强,条理与结构更清晰的样式代码。

以 Less 为例,可以进行如下配置:

module.exports = {
    module: {
        rules: [{
            test: /\.less$/,
            use: [
                'style-loader',
                'css-loader',
                'less-loader'
            ]
        }]
    }
}

post-css

PostCSS与 @babel/core 类似,只是实现了一套将 CSS 源码解析为 AST 结构,并传入 PostCSS 插件做处理的流程框架,具体功能都由插件实现。

预处理器之于 CSS,就像 TypeScript 与 JavaScript 的关系;而 PostCSS 之于 CSS,则更像 Babel 与 JavaScript。

可以进行如下配置:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          "style-loader", 
          "css-loader", 
          "postcss-loader"
        ],
      },
    ],
  }
};

不过,这个时候的 PostCSS 还只是个空壳,下一步还需要使用适当的 PostCSS 插件进行具体的功能处理,例如我们可以使用 autoprefixer 插件自动添加浏览器前缀。

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          "style-loader", 
          {
            loader: "css-loader",            
            options: {
              importLoaders: 1
            }
          }, 
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                // 添加 autoprefixer 插件
                plugins: [require("autoprefixer")],
              },
            },
          }
        ],
      },
    ],
  }
};

PostCSS 最大的优势在于其简单、易用、丰富的插件生态,基本上已经能够覆盖样式开发的方方面面。实践中,经常使用的插件有:

  1. autoprefixer:基于 Can I Use 网站上的数据,自动添加浏览器前缀
  2. postcss-preset-env:一款将最新 CSS 语言特性转译为兼容性更佳的低版本代码的插件
  3. postcss-less:兼容 Less 语法的 PostCSS 插件,类似的还有:postcss-sass、poststylus
  4. stylelint:一个现代 CSS 代码风格检查器,能够帮助识别样式代码中的异常或风格问题