webpack默认只能识别js的文件,如果有其他文件需要增加其他的loader去处理
一、工作流
三种组件各司其职:
css-loader
让 Webpack 能够正确理解 CSS 代码、分析资源依赖;style-loader
、mini-css-extract-plugin
则通过适当方式将 CSS 插入到页面,对页面样式产生影响:为此,在 Webpack 中处理 CSS 文件,通常需要用到:
css-loader
:该 Loader 会将 CSS 等价翻译为形如module.exports = "${css}"
的JavaScript 代码,使得 Webpack 能够如同处理 JS 代码一样解析 CSS 内容与资源依赖;
style-loader
:该 Loader 将在产物中注入一系列 runtime 代码,这些代码会将 CSS 内容注入到页面的<style>
标签,使得样式生效;
mini-css-extract-plugin
:该插件会将 CSS 代码抽离到单独的.css
文件,并将文件通过<link>
标签方式插入到页面中。
PS:当 Webpack 版本低于 5.0 时,请使用
extract-text-webpack-plugin
代替 mini-css-extract-plugin
。二、css-loader
下面我们先从
css-loader
聊起,css-loader
提供了很多处理 CSS 代码的基础能力,包括 CSS 到 JS 转译、依赖解析、Sourcemap、css-in-module 等,基于这些能力,Webpack 才能像处理 JS 模块一样处理 CSS 模块代码,转义后的结果如下。三、style-loader
但这段字符串只是被当作普通 JS 模块处理,并不会实际影响到页面样式,后续还需要:
- 开发环境:使用
style-loader
将样式代码注入到页面<style>
标签;
- 生产环境:使用
mini-css-extract-plugin
将样式代码抽离到单独产物文件,并以<link>
标签方式引入到页面中。
经过
css-loader
处理后,CSS 代码会被转译为等价 JS 字符串,但这些字符串还不会对页面样式产生影响,需要继续接入 style-loader
加载器。与其它 Loader 不同,
style-loader
并不会对代码内容做任何修改,而是简单注入一系列运行时代码,用于将 css-loader
转译出的 JS 字符串插入到页面的 style
标签。接入时同样需要安装依赖:module.exports = { module: { rules: [ { test: /\.css$/i, use: ["style-loader", "css-loader"], }, ], }, };
上述配置语义上相当于
style-loader(css-loader(css))
链式调用,执行后样式代码会被转译为类似下面这样的代码:// 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__ )
至此,运行页面触发
injectStylesIntoStyleTag
函数将 CSS 代码注入到 <style>
标签,样式才真正开始生效。例如:四、css不提取出单独文件的缺陷
经过
style-loader
+ css-loader
处理后,样式代码最终会被写入 Bundle 文件,并在运行时通过 style
标签注入到页面。这种将 JS、CSS 代码合并进同一个产物文件的方式有几个问题:- JS、CSS 资源无法并行加载,从而降低页面性能;
- 资源缓存粒度变大,JS、CSS 任意一种变更都会致使缓存失效。
因此,生产环境中通常会用
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() ] }
这里需要注意几个点:
mini-css-extract-plugin
库同时提供 Loader、Plugin 组件,需要同时使用
mini-css-extract-plugin
不能与style-loader
混用,否则报错,所以上述示例中第 9 行需要判断process.env.NODE_ENV
环境变量决定使用那个 Loader
mini-css-extract-plugin
需要与html-webpack-plugin
同时使用,才能将产物路径以link
标签方式插入到 html 中
五、使用预处理器less等
CSS至今依然没有提供诸如循环、分支判断、扩展复用、函数、嵌套之类的特性,以至于原生 CSS 已经难以应对当代复杂 Web 应用的开发需求。
module.exports = { module: { rules: [{ test: /\.less$/, use: [ 'style-loader', 'css-loader', 'less-loader' ] }] } }
六、使用 post-css
与上面介绍的 Less/Sass/Stylus 这一类预处理器类似,PostCSS 也能在原生 CSS 基础上增加更多表达力、可维护性、可读性更强的语言特性。两者主要区别在于预处理器通常定义了一套 CSS 之上的超集语言;PostCSS 并没有定义一门新的语言,而是与
@babel/core
类似,只是实现了一套将 CSS 源码解析为 AST 结构,并传入 PostCSS 插件做处理的流程框架,具体功能都由插件实现。预处理器之于 CSS,就像 TypeScript 与 JavaScript 的关系;而 PostCSS 之于 CSS,则更像 Babel 与 JavaScript。
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 与预处理器并非互斥关系,我们完全可以在同一个项目中同时使用两者,例如:
module.exports = { module: { rules: [ { test: /\.less$/, use: [ "style-loader", { loader: "css-loader", options: { importLoaders: 1 } }, "postcss-loader", "less-loader" ], }, ], } };
基于这一特性,我们既能复用预处理语法特性,又能应用 PostCSS 丰富的插件能力处理诸如雪碧图、浏览器前缀等问题。
PostCSS 最大的优势在于其简单、易用、丰富的插件生态,基本上已经能够覆盖样式开发的方方面面。实践中,经常使用的插件有:
- autoprefixer:基于 Can I Use 网站上的数据,自动添加浏览器前缀
- postcss-preset-env:一款将最新 CSS 语言特性转译为兼容性更佳的低版本代码的插件
- postcss-less:兼容 Less 语法的 PostCSS 插件,类似的还有:postcss-sass、poststylus
- stylelint:一个现代 CSS 代码风格检查器,能够帮助识别样式代码中的异常或风格问题