Webpack系列(二)常用的 Css Loader 使用及自定义 Loader

242 阅读3分钟

我正在参加「掘金·启航计划」

Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。本文主要写了Webpack的常用的 Css Loader 使用以及实现自定义 Loader。

Webpack 系列专栏地址 进入

本文demo地址查看

less-loader

安装的包名后缀带 -loader 的基本都是是webapck生态的插件,如果不用最新版本的webapck,一般都需要锁定版本,和webpack的版本要兼容。

安装:

npm install less less-loader@7 -D

配置项:

module: {
	rules: [
                // ...
		{
			test: /\.less$/,
			use: [minicssPlugin.loader, "css-loader", "less-loader"],
		},
	],
},

多个loader作用于同一个模块时,按从后到前的顺序执行:

  1. less-loader 处理less文件解析为 css文件
  2. css-loader 将css 代码序列化
  3. minicssPlugin.loader 负责抽取样式 生成独立的css文件

postcss-loader

postcss相关文档

安装:

npm install postcss postcss-loader@4 -D

配置:

{
	test: /\.less$/,
	use: [
		minicssPlugin.loader,
		"css-loader",
		"postcss-loader",
		"less-loader",
	],
},

postcss-loader配置项放到 less-laoder的前面,由less-loader处理得到的css文件再交给postcss-loader

下面安装postcss的插件 autoprefixer,使用 autoprefixer 添加厂商前缀。解决css语法的一些兼容性问题。

安装:

npm install autoprefixer -D

新建postcss.config.js为postcss的配置文件:

module.exports = {
	plugins: [require("autoprefixer")],
};

此时,执行打包命令,发现css文件并没有变化。

这是因为还需要配置browserslist 声明目标浏览器集合的工具。

我们用到的需要兼容的工具,比如 babel autoprefixer 会通过browserslist声明的浏览器集合,针对性的输出兼容性的代码。

package.json 中写入配置:

	"browserslist": [
		"last 2 versions",
		">1%"
	]

上述配置表示:兼容所有浏览器最后两个版本,浏览器的市场占有率大于1%

再次执行打包命令,发现css样式被添加了一些兼容性代码。

捕获.PNG

cssnano

  • cssnano 是一个模块化的 CSS 压缩器。

安装:

npm install cssnano -D

配置:

// postcss.config.js
module.exports = {
	plugins: [require("autoprefixer"), require("cssnano")],
};

观察打包后的css代码,发现代码被压缩。

捕获.PNG

关于browserslist

browserslist 实际上就是声明了⼀段浏览器的集合,我们的⼯具可以根据这段集合描述,针对性的输出 兼容性代码。

配置

Browserslist 的配置可以放在 package.json 中,也可以单独放在配置⽂件 .browserslistrc 中。所 有的⼯具都会主动查找 browserslist 的配置⽂件,根据 browserslist 配置找出对应的⽬标浏览器集合。

Browserslist 的数据都是来⾃Can I Use:browserl.ist/

但是目前这个⽹站关闭了,现在需要⼿动检测:

npx browserslist "last 1 version, >1%"

捕获.PNG

常见集合范围说明

范围说明
last 2 versions浏览器最新两个版本,如chrome90 和 chrom91
> 1%全球超过 1%⼈使⽤的浏览器
cover 99.5%覆盖 99.5%主流浏览器
chrome > 50 或ie 6-8指定某个浏览器版本范围
not ie < 11排除 ie11 以下版本不兼容
current node当前环境的 node 版本
dead通过 last 2 versions 筛选的浏览器中,全球使⽤率低于 0.5% 且官⽅声明 不在维护或者事实上已经两年没有再更新的版本
defaults默认配置, > 0.5% last 2 versions Firefox ESR not dead

实现自定义 loader

通过前面的学习,可以了解到webpack打包项目时遇到非js 非json的模块,可以在webpack.config.js配置文件中通过module去配置,根据配置规则,将相应的模块给对应的loader去处理,并将loader编译后的内容再交给webpack进一步处理。

下面将手写实现一个loader:

新建 /myLoaders/k-loader.js

webpack.config.js配置文件:

module: {
	rules: [
        // ...
               {
		test: /\.js$/,
		use: [
			resolve(__dirname, "./myLoaders/k-loader.js")
		],
	},
    ],
}

use自定义的loader,需要使用 resolve 获取路径,不能直接写包名。

编写loader的几个要点:

  • 结构是一个函数,但不可以是箭头函数。
  • 必须有返回值,必须是String 或 Buffer
  • 使用 this.query loader API 接收options配置项
  • 使用 this.callback 返回多个信息
  • loader需要对异步逻辑进行特殊处理
  • 多个 loader 如何配置

k-loader.js:

module.exports = function (source) {
	console.log(source);
	return source.replace("hello", "你好");
};

source参数就是接受的模块文件,先简单测试一下,将hello替换成你好。执行打包命令,查看bundle文件,发现成功被替换。

this.query

添加options配置项:

// webapck.config.js
{
	test: /\.js$/,
	// use: [resolve(__dirname, "./myLoaders/k-loader.js")],
	use: [
		{
			loader: resolve(__dirname, "./myLoaders/k-loader.js"),
			options: { title: "你好" },
		},
	],
},

k-loader.js 中接收 options

module.exports = function (source) {
	console.log(source);
	// return source.replace("hello", "你好");
	return source.replace("hello", this.query.title);
};

this.query 这个loader API,可以获取到options对象。执行打包命令,bundle文件中hello被替换成title的值。

this.callback

this.callback 用于返回多个信息。有同步和异步两种调用方式。

k-loader.js:

module.exports = function (source) {
	console.log(source);
	// return source.replace("hello", "你好");
	// return source.replace("hello", this.query.title);
	const info = source.replace("hello", this.query.title);
	this.callback(null, info);
};

再次打包发现结果无差异。

处理异步逻辑

this.async 告诉 loader-runner 这个 loader 将会异步地回调。返回值是 this.callback

k-loader.js:

module.exports = function (source) {
	// 处理异步逻辑
	const callback = this.async();
	setTimeout(() => {
		const info = source.replace("hello", this.query.title);
		callback(null, info);
	}, 3000);
};

多个 loader 配置

新建 l-loader.js

module.exports = function (source) {
	return source.replace("webpack", "webpack!!!");
};

增加 webpack.config.js 配置:

			{
				test: /\.js$/,
				// use: [resolve(__dirname, "./myLoaders/k-loader.js")],
				use: [
					{
						loader: resolve(__dirname, "./myLoaders/k-loader.js"),
						options: { title: "你好" },
					},
                                        // 第二个loader配置
					{
						loader: resolve(__dirname, "./myLoaders/l-loader.js"),
					},
				],
			},

再次打包,index.js模块被打包后输出了 你好 webpack!!!,这也是我们预期的结果。

resolveLoader

resolveLoader 用于解析 webpack 的 loader包,可以简化引入自定义loader的方式。

resolveLoader中的modules数组拥有一个默认值node_modules,所以我们通过npm安装的loader,可以直接写loader的名称,会自动在node_module下搜索。那么,把我们自定义loader的路径添加进去,也可以实现在我们添加的路径下自动查找loader,这时候就不用再写完整的路径使用loader了。

webpack.config.js:

	resolveLoader: {
		modules: ["node_modules", "./myLoaders"],
	},
// 简化路径
{
	test: /\.js$/,
	use: [
		{
			// loader: resolve(__dirname, "./myLoaders/k-loader.js"),
			loader: "k-loader",
			options: { title: "你好" },
		},
		{
                    // loader: resolve(__dirname, "./myLoaders/l-loader.js"),
                    loader: "l-loader",
		},
	],
},

实现 mini 版的 处理 less 相关loader

新建下列文件

  • mini-style-loader.js
  • mini-css-loader.js
  • mini-less-loader.js

webpack.config.js配置:

{
	test: /\.less$/,
	use: [
	// minicssPlugin.loader,
	// "css-loader",
	// "postcss-loader",
	// "less-loader",
	// 替换成自己手写实现的loader
	"mini-style-loader",
	"mini-css-loader",
	"mini-less-loader",
	],
},

mini-less-loader

使用 less 模块将 .less 文件解析为css的代码。

const less = require("less");

module.exports = function (source) {
	less.render(source, (error, { css }) => {
		this, callback(error, css);
	});
};

mini-css-loader

将 css 序列化。

module.exports = function (source) {
	return JSON.stringify(source);
};

mini-style-loader

  • 操作 DOM,创建 style
module.exports = function (source) {
	return `
		const tag = document.createElement("style")
		tag.innerHTML = ${source}
		document.head.appendChild(tag)
	`;
};

返回字符串的形式,经过webpack的处理会把这个字符串交给eval函数执行。

最后执行打包命令,运行html文件,可以正常显示效果。