一个最简单的 loader 代码结构
定义:loader 只是一个导出为函数的 JavaScript 模块
module.exports = function(source) { return source; };
多 Loader 时的执行顺序
多个 Loader 串行执行, 顺序从后到前
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist') },
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
' less-loader'
]
}
]
}
};
函数组合的两种情况
1. Unix 中的 pipline
2. Compose(webpack采取的是这种)
compose = (f, g) => (...args) => f(g(...args));
验证loader 的执行顺序
a-loader.js:
module.exports = function(source) {
console.log ('loader a is executed');
return source;
};
b-loader.js:
module.exports = function(source) {
console.log ('loader b is executed');
return source;
};
package.json
{
"name": "loader-order",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"loader-utils": "^1.2.3",
"webpack": "^4.39.1",
"webpack-cli": "^3.3.6"
}
}
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'main.js'
},
module: {
rules: [
{
test: /\.js$/,
use: [
path.resolve('./loaders/a-loader'),
path.resolve('./loaders/b-loader')
]
}
]
}
}
loaders/a-loader.js
const loaderUtils = require('loader-utils');
module.exports = function(source) {
console.log('Loader a is excuted!');
const url = loaderUtils.interpolateName(this, '[name].[ext]', source);
console.log(url);
this.emitFile(url, source);
return source;
}
loaders/b-loader.js
module.exports = function(source) {
console.log('Loader b is excuted!');
return source;
}
src/index.js
const a = 1;
执行结果
loader-runner
loader-runner 的介绍
定义:loader-runner 允许你在不安装 webpack 的情况下运行 loaders
作用:
1.作为 webpack 的依赖,webpack 中使用它执行 loader
2.进行 loader 的开发和调试
loader-runner 的使用
import { runLoaders } from "loader-runner";
runLoaders({
resource: "/abs/path/to/file.txt?query", // String: 资源的绝对路径(可以增加查询字符串)
loaders: ["/abs/path/to/loader.js?query"], // String[]: loader 的绝对路径(可以增加查询字符串)
context: { minimize: true }, // 基础上下文之外的额外 loader 上下文
readResource: fs.readFile.bind(fs) // 读取资源的函数
}, function(err, result) {
// err: Error?
// result.result: Buffer | String
})
开发一个raw-loader
目录结构
run-loader.js
const { runLoaders } = require('loader-runner');
const fs = require('fs');
const path = require('path');
runLoaders({
resource: path.join(__dirname, './src/demo.txt'),
loaders: [
{
loader: path.join(__dirname, './src/raw-loader.js'),
options: {
name: 'test'
}
}
],
context: {
emitFile: () => {}
},
readResource: fs.readFile.bind(fs)
}, (err, result) => {
err ? console.log(err) : console.log(result);
});
package.json
{
"name": "raw-loader",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"loader-runner": "^3.0.0"
},
"devDependencies": {
"loader-utils": "^1.2.3"
}
}
src/raw-loader.js
const loaderUtils = require('loader-utils');
const fs = require('fs');
const path = require('path');
module.exports = function(source) {
const { name } = loaderUtils.getOptions(this);
const url = loaderUtils.interpolateName(this, "[name].[ext]", {
source,
});
console.log(url);
this.emitFile(path.join(__dirname, url), source);
// this.cacheable(false);
// const callback = this.async();
// console.log('name', name);
const json = JSON.stringify(source)
// .replace('foo', 'baishu')
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
// fs.readFile(path.join(__dirname, './async.txt'), 'utf-8', (err, data) => {
// if (err) {
// callback(err, '');
// }
// callback(null, data);
// });
//
// throw new Error('Error');
return `export default ${json}`;
// this.callback(null, json, 2, 3, 4);
}
src/async.txt
async
src/demo.txt
foobar
执行结果
node run-loader.js
loader的参数获取
通过 loader-utils 的 getOptions 方法获取
const loaderUtils = require("loader-utils");
module.exports = function(content) {
const { name } = loaderUtils.getOptions(this);
};
loader的异常处理
1.loader 内直接通过 throw 抛出
2.通过 this.callback 传递错误
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any );
loader 的异步处理
通过 this.async 来返回一个异步函数
第一个参数是 Error,第二个参数是处理的结果
示意代码:
module.exports = function(input) {
const callback = this.async();
// No callback -> return synchronous results
// if (callback) { ... }
callback(null, input + input);
};
在 loader 中使用缓存
webpack 中默认开启 loader 缓存
·可以使用 this.cacheable(false) 关掉缓存
缓存条件: loader 的结果在相同的输入下有确定的输出
·有依赖的 loader 无法使用缓存
loader 如何进行文件输出?
通过 this.emitFile 进行文件写入
const loaderUtils = require("loader-utils");
module.exports = function(content) {
const url = loaderUtils.interpolateName(this, "[hash].[ext]", { content,
});
this.emitFile(url, content);
const path = `__webpack_public_path__ + ${JSON.stringify(url)};`; return `export default ${path}`;
};
实战开发一个自动合成雪碧图的 loader
支持的语法
background: url('a.png?__sprite');
background: url('b.png?__sprite');
-> background: url('sprite.png');
准备知识:如何将两张图片合成一张图片?
使用 spritesmith: https://www.npmjs.com/package/spritesmith
spritesmith 使用示例
const sprites = ['./images/1.jpg', './images/2.jpg'];
Spritesmith.run({src: sprites}, function handleResult (err, result) {
result.image;
result.coordinates;
result.properties;
});
目录结构
run-loader.js
const { runLoaders } = require('loader-runner');
const fs = require('fs');
const path = require('path');
runLoaders({
resource: path.join(__dirname, './loaders/index.css'),
loaders: [path.resolve(__dirname, './loaders/sprite-loader.js')],
readResource: fs.readFile.bind(fs)
}, (err, result) => {
err ? console.log(err) : console.log(result);
});
test.js
const Spritesmith = require('spritesmith')
const fs = require('fs');
const path = require('path');
const sprites = ['./loaders/images/1.jpg', './loaders/images/2.jpg'];
Spritesmith.run({src: sprites}, (err, result)=>{
console.log(result.image)
console.log(result.coordinates)
console.log(result.properties)
fs.writeFileSync(path.join(__dirname,'dist/sprite.jpg'),result.image)
});
package.json
{
"name": "raw-loader",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"loader-runner": "^3.0.0",
"spritesmith": "^3.4.0"
}
}
loaders/sprite-loader.js
const Spritesmith = require('spritesmith')
const fs = require('fs');
const path = require('path');
const loaderUtils = require('loader-utils');
module.exports = function(source) {
const callback = this.async();
const imgs = source.match(/url\((\S*)\?__sprite/g);
const matchedImgs = [];
for(let i = 0; i < imgs.length; i++){
const img = imgs[i].match(/url\((\S*)\?__sprite/)[1];
matchedImgs.push(path.join(__dirname,img));
}
Spritesmith.run({
src: matchedImgs
}, (err, result)=>{
fs.writeFileSync(path.join(process.cwd(),'dist/sprite.jpg'),result.image)
source = source.replace(/url\((\S*)\?__sprite/g,(match) => {
return `url("dist/sprite.jpg"`
})
fs.writeFileSync(path.join(process.cwd(),'dist/index.css'),source)
callback(null,source)
});
}
loaders/index.css
.img1{
background: url(./images/1.jpg?__sprite);
}
.img2{
background: url(./images/2.jpg?__sprite);
}
dist/index.css
.img1{
background: url("dist/sprite.jpg");
}
.img2{
background: url("dist/sprite.jpg");
}
- 本文链接:http://example.com/2022/03/17/webpack/loader/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。