Skip to main content

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
webpack-demo/src/component.js
import "./style.css";

export default function component() {
const element = document.createElement("div");
element.innerHTML = "hello css loader";
element.className = "content";

return element;
}
webpack-demo/src/style.css
.content {
background-color: skyblue;
}
webpack-demo/index.js
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 标签下。

pic.nm

sass-loader

loader 的作用是用来解析 .scss 文件,来获取 .scss 文件中的内容。当获取到 .scss 中的内容后还需要通过 sass 编译器来解析其代码,转换为 CSS 代码。因此打包 .scss 文件,我们需要下载 sass-loadersass 包中的编译器。

$ 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

常用参数

参数说明
defaultsbrowserslist 的默认参数 ( >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-loaderpostcss-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  进行插件配置。

postcss.config.js
module.exports = {
plugins: [require("postcss-preset-env")],
};

然后在 webpack.config.js 中只需要直接写上 postcss-loader 即可,不需要再到里面配置插件。

webpack.config.js
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 对其处理一次,那么可以进行如下配置。

webpack.config.js
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 再处理一次。

webpack.config.js
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 将样式插入到页面中