webpack的样式处理
分离样式文件
让我们从最简单的情况说起——处理工程中的纯 CSS。style-loader 与 css-loader,通过 JS 引用 CSS 的方式打包样式,可以更清晰地描述模块间的依赖关系。
然而,当时还有一个问题没有解决,我们是通过附加 style 标签的方式引入样式的,那么如何输出单独的 CSS 文件呢?一般来说,在生产环境下,我们希望样式存在于 CSS 文件中而不是 style 标签中,因为文件更有利于客户端进行缓存。Webpack 社区有专门的插件:extract-text-webpack-plugin(适用于 Webpack 4 之前版本)和 mini-css-extract-plugin(适用于 Webpack 4 及以上版本),它们就是专门用于提取样式到 CSS 文件的。
extract-text-webpack-plugin
我们先通过一个简单的例子来直观认识该插件是如何工作的。使用 npm 安装:
npm install extract-text-webpack-plugin
在 webpack.config.js 中引入:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './app.js',
output: {
filename: 'bundle.js',
},
mode: 'development',
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader',
}),
}
],
},
plugins: [
new ExtractTextPlugin("bundle.css")
],
};
在 module.rules 中我们设置了处理 CSS 文件的规则,其中的 use 字段并没有直接传入 loader,而是使用了插件的 extract 方法包了一层。内部的 fallback 属性用于指定当插件无法提取样式时所采用的 loader,use(extract 方法里面的)用于指定在提取样式之前采用哪些 loader 来预先进行处理。除此之外,还要在 Webpack 的 plugins 配置中添加该插件,并传入提取后的资源文件名。
plugins 用于接收一个插件数组,我们可以使用 Webpack 内部提供的一些插件,也可以加载外部插件。Webpack 为插件提供了各种 API,使其可以在打包的各个环节中添加一些额外任务,就像 extract-text-webpack-plugin 所实现的样式提取一样。
多样式文件的处理
样式的提取是以资源入口开始的整个 chunk 为单位的。假设我们的应用从 index.js 开始一层层引入了几百个模块,也许其中很多模块都引入了各自的样式,但是最终只会生成一个 CSS 文件,因为它们都来自同一个入口模块。
上面我们将 bundle.css 作为文件名传给了 extract-text-webpack-plugin,但当工程有多个入口时就会发生重名问题。就像在前面的章节中我们配置动态的 output.filename 一样,这里我们也要对插件提取的 CSS 文件使用类似模板的命名方式。
// webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
···
plugins: [
new ExtractTextPlugin('[name].css')
],
};
mini-css-extract-plugin
mini-css-extract-plugin 可以理解成 extract-text-webpack-plugin 的升级版,它拥有更丰富的特性和更好的性能,从 Webpack 4 开始官方推荐使用该插件进行样式提取(Webpack 4 以前的版本是用不了的)。
说到 mini-css-extract-plugin 的特性,最重要的就是它支持按需加载 CSS,以前在使用 extract-text-webpack-plugin 的时候我们是做不到这一点的。举个例子,a.js 通过 import()函数异步加载了 b.js,b.js 里面加载了 style.css,那么 style.css 最终只能被同步加载(通过 HTML 的 link 标签)。但是现在 mini-css-extract-plugin 会单独打包出一个 0.css(假设使用默认配置),这个 CSS 文件将由 a.js 通过动态插入 link 标签的方式加载。
请看下面的例子:
// app.js
import './style.css';
import('./next-page');
document.write('app.js<br/>');
// next-page.js
import './next-page.css';
document.write('Next page.<br/>');
/* style.css */
body { background-color: #eee; }
/* next-page.css */
body { background-color: #999; }
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './app.js',
output: {
filename: '[name].js',
},
mode: 'development',
module: {
rules: [{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
},
},
'css-loader'
],
}],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
})
]
};”
在配置上 mini-css-extract-plugin 与 extract-text-webpack-plugin 有以下几点不同:
- loader 规则设置的形式不同,并且 mini-css-extract-plugin 支持配置 publicPath,用来指定异步 CSS 的加载路径。
- 不需要设置 fallback。
- 在 plugins 设置中,除了指定同步加载的 CSS 资源名(filename),还要指定异步加载的 CSS 资源名(chunkFilename
样式预处理
样式预处理指的是在开发中我们经常会使用一些样式预编译语言,如 SCSS、Less 等,在项目打包过程中再将这些预编译语言转换为 CSS。借助这些语言强大和便捷的特性,可以降低项目的开发和维护成本。
PostCSS
严格说来,PostCSS 并不能算是一个 CSS 的预编译器,它只是一个编译插件的容器。它的工作模式是接收样式源代码并交由编译插件处理,最后输出 CSS。开发者可以自己指定使用哪些插件来实现特定的功能。
- postcss-loader 可以将 PostCss 和 webpack 连接起来
- 可以于 Autoprefixer 连接起来实现自动前缀,添加支持的特性和需要兼容的浏览器。
- stylelint 是一个 CSS 质量检测工具
- CSSNext 从而使用最新的 CSS 特性
CSS Modules
CSS Modules 是近年来比较流行的一种开发模式,其理念就是把 CSS 模块化,让 CSS 也拥有模块的特点,具体如下:
- 每个 CSS 文件中的样式都拥有单独的作用域,不会和外界发生命名冲突。
- 对 CSS 进行依赖管理,可以通过相对路径引入 CSS 文件。
- 可以通过 composes 轻松复用其他 CSS 模块。 使用 CSS Modules 不需要额外安装模块,只要开启 css-loader 中的 modules 配置项即可。