目录:
- 基本概念
- loader开发入门
- 本地loader调试
- 发布并引用loader
- Demo: 雪碧图loader
1. 基本概念
众所周知,webpack是个模块打包器。但是webpack只能处理js和json文件。
loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。
loader本质上是一个导出函数的JS模块,函数的入参和出参可以理解为文件流(String或Buffer类),函数对传入的文件流进行处理,然后返回处理后的新文件流。
loader
可以是同步的:
module.exports = function (content, map, meta) {
// content就是传进来的文件内容
// 对content进行处理
const newContent = doSomething(content);
return newContent; // 返回处理后的文件内容
};
也可以是异步的:
module.exports = function (content, map, meta) {
const callback = this.async(); // 获取到callback函数
// 对content进行处理
const newContent = doSomething(content);
// callback的参数有4个,按顺序分别是:
// 1. 错误参数Error或null,必传
// 2. String/Buffer类型的content,必传
// 3. 可选参数sourceMap
// 4. 可选参数meta
callback(null, newContent, map, meta);
};
官方建议尽量用异步loader。
在webpack社区中有很多第三方loader,安装完成后在webpack.config.js中配置即可使用:
// webpack.config.js配置loader
module: {
rules: [{
test: /\.css$/i,
use: ['style-loader', 'css-loader']
}]
},
上面配置了两个常用的样式处理loader:style-loader和css-loader。
rules规则数组用于指定对模块应用哪些loader。
可以看到rules里的对象有两个属性:
- test属性的值是个正则表达式,用于进行文件类型匹配,这里匹配的是以.css或.CSS结尾的文件;
- use属性的值是一个loader数组,指明要对这些css文件执行哪些loader操作。这里多个loader会从右到左进行链式调用,比如上面的配置中,会先执行css-loader,再执行style-loader。和gulp的task有点类似,但是执行顺序不一样,gulp是从左到右顺序执行;而loader更像是复合函数,从右往左执行。
目前为止,我们已大致了解了loader是什么,有什么用,怎么配置使用。
2. loader开发入门
首先npm init -y
初始化项目,这里我的项目名为yzz-sprite-loader
,
cd进入项目,创建index.js
文件,内容如下:
// yzz-sprite-loader/index.js
module.exports = function (content, map, meta) {
const callback = this.async();
callback(null, content, map, meta);
}
至此,我们就写完了一个啥也不干的异步loader。
官方对于loader开发给出了一些参考准则:
- 保持简单,一个loader只作一件事
- 利用链式调用
- 模块化
- 无状态,每次运行都与之前的运行结果无关
- 利用loader-utils包
- 用addDependency标明使用的外部文件
- 需要解决代码中的模块依赖问题,比如css中的 @import,可以转换成require方式等
- 提取公共代码
- 避免绝对路径
- 使用 peerDependency
https://webpack.docschina.org/contribute/writing-a-loader/#guidelines
3. 本地loader调试
写完了loader,怎么在项目中引用调试?
先简单搭建一个webpack项目test-webpack
:
- cd test-webpack,进入项目
- npm init -y,初始化项目
- npm install webpack webpack-cli --save-dev,安装webpack依赖
- 新增文件:webpack.config.js,index.html,src/index.js,src/style.css
- 在
test-webpack/webpack.config.js
中引入我们的loader:
// test-webpack/webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
mode: "development",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [{
test: /\.css$/i,
use: [{
loader: path.resolve("../yzz-sprite-loader/index.js"),
},
],
},
],
},
};
这里使用相对路径引入了本地的loader。
- 在
test-webpack/index.js
中引入style.css:
import './style.css';
为了验证我们的loader正常运行,在yzz-sprite-loader/index.js
中加一句console:
module.exports = function (content, map, meta) {
const callback = this.async();
console.log('my loader is running');
callback(null, content, map, meta);
}
在test-webpack路径下执行yarn run webpack
,就能看到控制台打印出my loader is running
:
成功!✅
4. 发布并引用loader
只需三步🚶🚶🚶:
cd yzz-sprite-loader
进入loader项目npm login
登录npm账号- 执行
npm publish
引用方法如下:
在test-webpack项目中执行npm i yzz-sprite-loader -D
,并修改webpack.config.js:
rules: [{
test: /\.css$/i,
use: ['yzz-sprite-loader'],
}],
至此,我们的loader除了啥也不干之外,已基本上线完成~
5. Demo: 雪碧图loader
是时候加点实用功能了!
相信大部分切图仔都用过或者听说过雪碧图CSS Sprite,实际上就是把多张背景图合并到一张图片中以减少http请求次数,再利用CSS的background-position对合成的图片进行精确定位。
这里我直接用了一个开源的工具包:https://github.com/twolfson/spritesmith
spritesmith的用法如下:
var Spritesmith = require('spritesmith');
// 生成雪碧图
Spritesmith.run({
src: [], // 要合成的图片数组,值为图片路径
}, function handleResult (err, result) {
// If there was an error, throw it
if (err) {
throw err;
}
// 输出雪碧图result.image
// result.coordinates是个map对象,键为图片路径,值为一个保存了雪碧图信息的对象,包括雪碧图的水平定位x、垂直定位y、图片宽度width、图片高度height
});
可以看到,这个插件需要我们把想合成的图片路径都传进去。因此,实现思路如下:
- 用正则匹配,收集原始content中有设置背景图的语句,如
backgrount:url(…);
; - 将匹配到的图片路径保存到一个数组里,用于传给Spritesmith以合成雪碧图;
- 将匹配到的完整语句保存下来,用于后续的css代码替换;
- 将雪碧图保存到某个指定目录下,然后遍历图片路径数组,将content中的对应背景图设置代码替换成雪碧图定位方式;
- 最后返回替换后的content。
代码实现比较考验正则功力,这里就不展开细讲了(主要我的正则也很一般,勉强能用~),同时webpack对css的处理还需要搭配style-loader和css-loader使用,感兴趣的朋友可以直接看demo:https://github.com/youzouzou/webpack-sprites-loader-demo
按照项目中的README说明如此这般操作后,可以看到我这周摸的鱼🐟:
两个小图标被合成了一张图sprites.png,运行后可以看到css背景代码也已被替换:
⚠️重要提示:yzz-sprite-loader
仅为学习自定义loader的实现,不具备通用性,切勿在生产环境中使用!!!