Loader与样式处理
Loader 介绍
在 webpack 中只默认支持处理 js 和 json 文件,对于 CSS 文件、Sass 文件、图片文件等其他文件类型 webpack 是无法直接处理的,此时 webpack 就必须借助 loader 来处理这些类型的模块,并且处理过程中还可以对源代码进行转换。
loader 都是在 webpack.config.js
配置文件导出对象的 module.rules
中配置,配置项包括:
test
正则校验,用于匹配特定的文件(必选)loader
表示 loader 的字符串名称或者使用use
链式调用(二选一)include
手动添加必须处理的某个文件或目录(可选)exclude
手动移除不需要处理的某个文件或目录(可选)options
用于向 loader 传递参数(可选)
css-loader
首先是 css-loader
,可以意译为 CSS 加载器,在 webpack 根据依赖图打包时,遇到样式文件就需要使用 css-loader
来处理。
假如我们有如下结构的一个项目。
webpack-demo
├── src
│ ├── component.js
│ └── style.css
├── index.js
└── webpack.config.js
import "./style.css";
export default function component() {
const element = document.createElement("div");
element.innerHTML = "hello css loader";
element.className = "content";
return element;
}
.content {
background-color: skyblue;
}
import component from "./src/component.js";
document.body.appendChild(component());
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "build"),
},
mode: "production",
};
此时,如果我们在项目根目录,运行 webpack
命令,想要打包这个项目。
打包过程会报错,提示无法解析 webpack-demo/src/style.css
这个模块。这时 css-loader
就登场了。
首先我们要下载 css-loader
。
$ npm i css-loader -D
然后在 webpack.config.js
文件中进行如下配置。
const path = require("path");
module.exports = {
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "./build"),
},
module: {
rules: [{ test: /\.css$/, use: ["css-loader"] }],
},
};
表示 webpack 在打包资源过程中,遇到 .css
结尾的模块,就会使用 css-loader
来对其处理打包。
这样配置之后,我们运行 webpack
命令,就能对项目正常打包了。
但是值得注意的是,如果此时我们将打包后的 bundle.js
通过 script
标签插入到一个 .html
文件中,然后打开这个 html 文件,会发现样式并没有在页面中显示出来。
原因是 css-loader
的作用仅仅是让 webpack 在打包过程中能够解析 CSS 文件,为了能让样式加载到页面中还需要使用 style-loader
。
important
关于在 module.rules
中给特定模块配置 loader
的语法有多种,除了上面那种以外,还有下面两种。
{ test: /\.css$/, use: [{ loader: "css-loader" }]}
这种方式一般用于给 loader
传入一些参数,可以使用 options
属性来传递参数,它可以是字符串或对象,比如:
{ test: /\.css$/, use: [{ loader: "css-loader", options: "123" }]}
{
test: /\.css$/,
use: [{
loader: "css-loader",
options: {
param: "hello"
}
}]
}
此时就将 options
内容传递给了 css-loader
。
当然,如果你使用的 loader
不需要传递参数,那么可以使用下面更简洁的语法。
{ test: /\.css$/, loader: "css-loader" }
style-loader
之前我们使用了 css-loader 来解析了 css 文件,目前并没有把样式加入到页面中,那么 style-loader 的作用就是用来将 css-loader 解析后的内容加入到页面中去,下面先安装 style-loader。
$ npm i style-loader -D
然后在 webpack.config.js
中配置 style-loader
。
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
"css-loader"
]
}
],
},
此时 webpack 在打包过程中,如果遇到了 .css
后缀的模块,就会先使用 css-loader
对其解析,然后再使用 style-loader
将样式进行打包。
important
webpack 中 loader
的加载顺序在 use
数组中是从后往前的。
实际上 style-loader
原理很简单,在 css-loader
解析完 CSS 文件后,style-loader
会创建一个 style 标签。
document.createElement("style"); // 创建一个 style 标签
然后将 css-loader
解析的内容插入到 style 标签中,然后再将这个标签插入到 html 文件中 head 标签下。
sass-loader
该 loader
的作用是用来解析 .scss
文件,来获取 .scss
文件中的内容。当获取到 .scss
中的内容后还需要通过 sass
编译器来解析其代码,转换为 CSS 代码。因此打包 .scss
文件,我们需要下载 sass-loader
和 sass
包中的编译器。
$ npm i sass-loader sass -D
然后在 webpack.config.js
中进行如下配置。
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
"css-loader"
]
},
{
test: /\.scss$/,
use: [
"style-loader",
"css-loader",
"sass-loader"
]
}
],
},
在 webpack 打包过程中,如果遇到 .scss
文件:
- 首先使用
sass-loader
,用来解析 scss 文件,在sass-loader
中使用到了sass
模块用于将 scss 转换为 css 代码 - 然后使用
css-loader
,用来解析由 scss 转换为的 css 代码 - 最后使用
style-loader
,将css-loader
解析后的样式添加到页面中
browserslist
作用
我们在开发过程中浏览器的兼容性问题,我们应该如何去解决和处理?这里的兼容性指的是不同浏览器对 css 特性、js 语法之间的兼容性。
对于不同的浏览器来说,它们之间的兼容性肯定是有差异的,对于 css 来说,可以使用 autoprefixer 来为一些特定的属性加上前缀到达兼容的目的,对于 js 来说可以使用 babel 来进行转译。
但是 这些转译工具并不知道我们的项目需要去适配哪些浏览器,此时就需要用到 browserslist 工具了。
在我们的 React 项目中的 package.json 里面一般都有一个下面的配置。
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
它的作用就是告诉 autoprefixer、babel 这类工具,我们的项目需要兼容哪些浏览器,它主要被以下工具使用:
- Autoprefixer
- Babel
- post-preset-env
- eslint-plugin-compat
- stylelint-unsupported-browser-features
- postcss-normalize
这些工具就可以根据提供的目标浏览器的环境,来智能添加 css 前缀,js 的 polyfill,来兼容旧版本浏览器,而不是一股脑的添加。避免不必要的兼容代码,以提高代码的编译质量。
配置方式
写到 package.json 中
"browserslist": [
">1%",
"last 2 version",
"not dead"
]
写到根目录的 .browserslistrc 文件中
defaults
> 1%
last 2 version
not dead
常用参数
参数 | 说明 |
---|---|
defaults | browserslist 的默认参数 ( >5%, last 2 version, Firefox ESR, not dead ) |
> n% | 大于 n% 的市场占有率的浏览器,这里的大于符号可以换成 ( <, <=, >= ) |
dead | 超过 24 个月官方没有支持或更新的浏览器 |
last 2 version | 最新的两个版本内 |
逻辑运算
运算符 | 说明 |
---|---|
, 和 or | 表示并集 |
and | 表示交集 |
not | 取反 |
例子
"browserslist": [
">1%",
"last 2 version",
"not dead"
]
"browserslist": [
"defaults"
]
"browserslist": [
">1 or last 2 version or not dead"
]
"browserslist": [
">1 and last 2 version and not dead"
]
运行过程
webpack 中集成了 browserslist 工具,配置好上面的参数后,当我们打包项目的时候如果使用到上述的前端工具,这些工具就会来读取我们的 browserslist 的配置参数,通过 caniuse-lite
模块,查询 "市场占有率大于 1%, 最新的两个版本, 没有死亡" 的浏览器,这些数据都来自 caniuse 官网。
PostCSS
官网的定义是「一个用 JavaScript 工具和插件转换 CSS 代码的工具」。
在 webpack 中,我们可以通过 postcss-loader
来使用它。在打包过程中,可以让 postcss-loader
来使用其他插件对 css 进行不同的处理:
- Autoprefixer 自动获取浏览器的流行度和能够支持的属性,并根据这些数据帮你自动为 CSS 规则添加前缀
- PostCSS Preset Env 帮你将最新的 CSS 语法转换成大多数浏览器都能理解的语法,并根据你的目标浏览器或运行时环境来确定你需要的 polyfills
- CSS 模块 能让你你永远不用担心命名太大众化而造成冲突,只要用最有意义的名字就行了
其中最常用的一个插件就是 postcss-preset-env
,下面我们以使用它为例介绍如何在 webpack 中配置 postcss-loader
。
在 webpack.config.js 中配置
首先安装 postcss-loader
和 postcss-preset-env
。
$ npm i postcss-loader postcss-preset-env -D
然后在 webpack.config.js
中进行如下配置。
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env" // require("postcss-preset-env"),
],
},
},
},
],
},
],
},
webpack 在打包过程中,遇到 CSS 模块后,postcss-loader
就会使用 postcss-preset-env
对 CSS 代码进行兼容性处理(根据 browserslist),然后再交给 css-loader
对处理后的代码进行解析,再由 style-loader
将样式插入到页面中。
在 postcss.config.js 中配置
在 webpack.config.js
中直接配置 postcss-loader
及其插件,似乎有点麻烦,实际上我们可以在项目根目录中创建一个 postcss.config.js
文件,来单独对 postcss-loader
进行插件配置。
module.exports = {
plugins: [require("postcss-preset-env")],
};
然后在 webpack.config.js
中只需要直接写上 postcss-loader
即可,不需要再到里面配置插件。
module: {
rules: [{
test: /\.css$/,
use: [
"style-loader",
"css-loader",
"postcss-loader"
]
}]
},
@import 问题
如果我们在 CSS 文件中使用 @import
导入了其他样式文件中的样式,postcss-loader
是无法解析 @import
为其他文件中的样式进行处理的,解析 CSS 代码是 css-loader
的工作。
因此当 css-loader
解析完 @import
,得到导入的样式代码,此时还需要使用 postcss-loader
对其处理一次,那么可以进行如下配置。
module: {
rules: [{
test: /\.css$/,
use: [
"style-loader",
{
loader: "css-loader",
oprions: {
importLoaders: 1
}
},
"postcss-loader"
]
}]
},
上面高亮的代码表示当 css-loader
解析完 CSS 代码后,再交给其前面一个 loader
即 postcss-loader
来处理一次。
如果我们使用的是 Sass,打包过程中 css-loader
解析完 @import
得到其他的样式代码,那么就需要重新再执行一次 sass-loader
将其解析为 CSS 代码,然后通过 postcss-loader
再处理一次。
module: {
rules: [{
test: /\.scss$/,
use: [
"style-loader",
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
"postcss-loader",
"sass-loader"
]
}]
},
sass-loader
先将 .scss 文件转换为 .css 文件postcss-loader
进行兼容性处理css-loader
解析 css 文件,处理了@import
语句后,再重新被前面的两个 loader 处理一次style-loader
将样式插入到页面中