分离样式

现在我们有一个很好的打包了,但所有的 CSS 都去了哪里?根据配置,它已被内联到 JavaScript!虽然这在开发过程中很方便,但听起来并不理想。

当前的解决方案 CSS 是无法缓存的,并且还有一个未样式化元素闪动(FOUC)问题。发生 FOUC 是因为浏览器需要一段时间才能加载 JavaScript,并且到那时才会应用样式。将 CSS 分离到自己的文件可以让浏览器单独管理它,从而避免了这个问题。

Webpack 提供了一种使用 mini-css-extract-plugin(MCEP)生成单独的 CSS 包的方法。它可以将多个 CSS 文件聚合为一个。出于这个原因,它配备了一个 loader 来专门处理这个过程。然后,插件会获取 loader 抽取的结果并发出单独的文件。

由于这个过程会产生比较大的开销,所以,MiniCssExtractPlugin 只会作用于编译阶段,它不适用于热模块更换(HMR)。鉴于这个插件只是在生产环境中使用,所以也不是什么大的问题。

在生产环境中,使用内联样式可能有潜在危险,因为它向外提供了一个攻击途径。关键路径渲染借鉴了这个思路,它将关键 CSS 内联到初始 HTML 中,从而提高了站点的感知性能。在有限的上下文中,内联少量的 CSS 可能是加速初始加载(更少的请求)的可行选择。

配置 MiniCssExtractPlugin

首先安装插件:

npm install mini-css-extract-plugin --save-dev

MiniCssExtractPlugin 包括一个 MiniCssExtractPlugin.loader,用来标记要提取的资源。然后,插件基于这个标记执行资源提取的工作。

将以下配置添加到配置的开头:

webpack.parts.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

exports.extractCSS = ({ include, exclude, use = [] }) => {
  // 将 css 抽出
  const plugin = new MiniCssExtractPlugin({
    filename: "[name].css",
  });

  return {
    module: {
      rules: [
        {
          test: /\.css$/,
          include,
          exclude,

          use: [
            MiniCssExtractPlugin.loader,
          ].concat(use),
        },
      ],
    },
    plugins: [plugin],
  };
};

[name] 占位符使用引用 CSS 的入口的名称。“ 添加哈希值到文件名”一章中详细讨论了占位符和散列。

如果要将结果文件输出到特定目录,可以通过传递路径来完成。例如:filename: "styles/[name].css"

合并配置

使用以下配置将部分配置合并到主配置上:

webpack.config.js

const productionConfig = merge([
  parts.extractCSS({
    use: "css-loader",
  }),
]);

const developmentConfig = merge([
  ...

  parts.loadCSS(),

]);

使用此设置,您仍然可以在开发期间受益于 HMR。但是对于生产构建,可以生成单独的 CSS。HtmlWebpackPlugin 自动拾取并将其注入到 index.html

如果您使用的是 CSS Modules,请按照“ 加载样式”一章中的说明进行相应的调整。您可以为标准 CSS 和 CSS Modules 维护单独的设置,以便通过独立的逻辑加载它们。

通过 npm run build 运行后,您应该看到类似于以下内容的输出:

Hash: 45a5e26cc963eb12db02
Version: webpack 4.1.1
Time: 752ms
Built at: 3/16/2018 4:24:40 PM
     Asset       Size  Chunks             Chunk Names
   main.js  700 bytes       0  [emitted]  main
  main.css   33 bytes       0  [emitted]  main
index.html  220 bytes          [emitted]
Entrypoint main = main.js main.css
   [0] ./src/index.js + 1 modules 247 bytes {0} [built]
       | ./src/index.js 99 bytes [built]
       | ./src/component.js 143 bytes [built]
   [1] ./src/main.css 41 bytes {0} [built]
...

现在样式已被抽取到单独的 CSS 文件中。因此,JavaScript 包变得略小。你也避免了 FOUC 问题。浏览器不必等待 JavaScript 加载以获取样式信息。相反,它可以单独处理CSS,避免画面闪动。

如果您收到 Module build failed: CssSyntaxError:Module build failed: Unknown word 的错误,请确保您的通用配置没有设置与 CSS 相关的部分。

管理 JavaScript 之外的样式

尽管通过 JavaScript 引入样式,然后再打包是推荐的做法;但我们也可以在入口通过 glob 找到 CSS 文件来达到同样的目的:

...
const glob = require("glob");

...

const commonConfig = merge([
  {
    entry: {
      ...
      style: glob.sync("./src/**/*.css"),
    },
    ...
  },
  ...
]);

在进行此类更改后,您不必从应用程序代码中引用样式。这也意味着 CSS Modules 不再起作用,你也必须小心 CSS 规则的排序。

按照上面的配置,您应该同时获得 style.cssstyle.js。后一个文件包含类似 webpackJsonp([1,3],[function(n,c){}]); 的内容,它会像 webpack issue 1967 中所讨论的那样不做任何事情。

如果您想要严格控制排序,可以设置一个单独的 CSS 入口,然后使用 @import 语句将其余部分的样式导入到项目中。另外一种做法是设置JavaScript 入口并通过 import 语句获得相同的效果。

css-entry-webpack-plugin 旨在帮助实现这种模式。该插件可以从入口中提取 CSS,而不使用 mini-css-extract-plugin 那种方式。

总结

当前的配置将样式与 JavaScript 干净地分离开来。即使该技术对 CSS 最有价值,它也可用于提取 HTML 模板或您使用的任何其他文件类型。困难的是,这与 MiniCssExtractPlugin 的设置有关,但这种复杂性可隐藏在抽象背后。

回顾一下:

  • 使用 MiniCssExtractPlugin 样式解决了 FOUC 的问题、CSS 缓存失效问题以及潜在的攻击风险。
  • 如果您不希望通过 JavaScript 引入样式,还可以通过入口来引入它。但是,在这种情况下,您必须小心样式规则的排序。

在下一章中,您将学习如何从项目中去除未使用的 CSS。

用户头像
登录后发表评论