现在我们有一个很好的打包了,但所有的 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.css 和 style.js。后一个文件包含类似 webpackJsonp([1,3],[function(n,c){}]); 的内容,它会像 webpack issue 1967 中所讨论的那样不做任何事情。
如果您想要严格控制排序,可以设置一个单独的 CSS 入口,然后使用 @import 语句将其余部分的样式导入到项目中。另外一种做法是设置JavaScript 入口并通过 import 语句获得相同的效果。
总结
当前的配置将样式与 JavaScript 干净地分离开来。即使该技术对 CSS 最有价值,它也可用于提取 HTML 模板或您使用的任何其他文件类型。困难的是,这与 MiniCssExtractPlugin 的设置有关,但这种复杂性可隐藏在抽象背后。
回顾一下:
使用 MiniCssExtractPlugin 样式解决了 FOUC 的问题、CSS 缓存失效问题以及潜在的攻击风险。
如果您不希望通过 JavaScript 引入样式,还可以通过入口来引入它。但是,在这种情况下,您必须小心样式规则的排序。
在下一章中,您将学习如何从项目中去除未使用的 CSS。