往期文章:
- 重学webpack4之原理分析
- 重学webpack4之基础篇
- 重学webpack4之loader开发
- 重学webpack4之plugin开发
- webpack插件开发之秒开缓存插件
- 重学webpack4之打包库和组件
- 重学webpack4之构建速度提升和体积优化
loader
loader只是一个导出为函数的js模块
module.exports = function(source) {
return source
}
多loader串行执行,顺序从后往前
use: ['style-loader', 'css-loader', 'less-loader']
为什么是从后往前?
函数组合的两种情况
- Unix中的pipline
- compose(webpack采用这种) 扒洋葱
compose = (f,g) => (...args) => f(g(...args))
实现一个小例子:loader-order
├── dist
│ ├── index.js
│ └── main.js
├── loaders
│ ├── a-loader.js
│ └── b-loader.js
├── package\ copy.json
├── package.json
├── src
│ └── index.js
└── webpack.config.js
package.json
{
"name": "loader-order",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"loader-utils": "^2.0.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12"
}
}
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')
]
}
]
}
}
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;
}
b-loader.js
module.exports = function(source) {
console.log('Loader b is excuted!');
return source;
}
index.js
const a = 1
npm run build,打印日志
Loader b is excuted!
Loader a is excuted!
使用loader-runner本地开发调试loader
- loader-runner允许可以在不安装webpack的情况下运行loaders
- 作为webpack依赖,webpack中使用它执行loader,并进行loader的开发和调试
实现个小例子:raw-loader
├── package.json
├── run-loader.js
└── src
├── async-loader.js
├── async.txt
├── demo.txt
└── raw-loader.js
package.json
{
"name": "raw-loader",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "node ./run-loader.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"loader-runner": "^3.1.0"
},
"devDependencies": {
"loader-utils": "^2.0.0"
}
}
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'),
// 给loader传递参数
options: {
name: 'test'
}
},
{
loader: path.join(__dirname, './src/async-loader.js')
}
],
context: {
emitFile: () => {}
},
readResource: fs.readFile.bind(fs)
}, (err, result) => {
err ? console.log(err) : console.log(result)
})
demo.txt
foobar
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)
console.log(name)
// 同步 loader
const json = JSON.stringify(source)
.replace('foo', '')
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
//return `export default ${json}`
// throw new Error('Error');
// this.callback(new Error('Error'), json)
this.callback(null, json, 2, 3, 4)
}
async.txt
async
async-loader.js
const fs = require('fs')
const path = require('path')
module.exports = function(source) {
const callback = this.async()
fs.readFile(path.join(__dirname, './async.txt'), 'utf-8', (err, data) => {
if (err) {
callback(err, '')
}
callback(null, data)
})
}
npm run test,日志打印
test
{
result: [ '"async"', 2, 3, 4 ],
resourceBuffer: <Buffer 66 6f 6f 62 61 72>,
cacheable: true,
fileDependencies: [
'/Users/bytedance/mywork/new-life/webpack-rest/编写loader和插件/raw-loader/src/demo.txt'
],
contextDependencies: [],
missingDependencies: []
}
loader 参数获取
- 通过loader-utils 的 getOptions 方法获取
module.exports = function(source) {
const { name } = loaderUtils.getOptions(this)
}
异步loader
module.exports = function(source) {
const callback = this.async()
callback(null, source)
}
loader中使用缓存
- webpack默认开启loader缓存
-
- 可以使用 this.cacheable(false) 关闭缓存
- 缓存条件:loader的结果在相同输入下有确定输出
-
- 有依赖的loader 无法使用缓存
loader 如何进行文件输出
- this.emitFile进行文件写入
const loaderUtils = require('loader-utils')
const url = loaderUtils.interpolateName(this, "[name].[ext]", {
source,
})
this.emitFile(path.join(__dirname, url), source);
const path = `__webpack_public_path__+${JSON.stringify(url)};`
return `export default ${path}`
最后来个图片处理的loader
├── dist
│ ├── index.css
│ └── sprite.jpg
├── package.json
├── run-loader.js
└── src
├── images
│ ├── 1.jpg
│ └── 2.jpg
├── index.css
└── sprite-loader.js
package.json
{
"name": "sprite-loader",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "node ./run-loader.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"run-loader": "^0.0.6"
},
"dependencies": {
"loader-utils": "^2.0.0",
"spritesmith": "^3.4.0"
}
}
run-loader.js
const { runLoaders } = require('loader-runner')
const fs = require('fs')
const path = require('path')
runLoaders({
resource: path.join(__dirname, './src/index.css'),
loaders: [
path.join(__dirname, './src/sprite-loader')
],
readResource: fs.readFile.bind(fs)
}, (err, result) => (err ? console.log(err) : null))
index.css
图片后面加上 ?__sprite,表示需要合并的图片
.img1 {
background: url(./images/1.jpg?__sprite);
}
.img2 {
background: url(./images/2.jpg?__sprite);
}
sprite-loader.js
const Spritesmith = require('spritesmith')
const fs = require('fs')
const path = require('path')
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, mach => {
return `url("dist/sprite.jpg"`
})
fs.writeFileSync(path.join(process.cwd(), 'dist/index.css'), source)
callback(null, source)
})
}
npm run test,结果:
- sprite.jpg
- index.css
.img1 {
background: url("dist/sprite.jpg");
}
.img2 {
background: url("dist/sprite.jpg");
}
这里先不处理 图片的position了
❤️ 加入我们
字节跳动 · 幸福里团队
Nice Leader:高级技术专家、掘金知名专栏作者、Flutter中文网社区创办者、Flutter中文社区开源项目发起人、Github社区知名开发者,是dio、fly、dsBridge等多个知名开源项目作者
微信扫码:期待您的加入,一起用技术改变生活!!!
期待您的加入,一起用技术改变生活!!!