前言
基本功能实现
- 这个loader实现有点意思啊,一开始我还觉得可能是中间件什么的很绕的实现方式,毕竟这个pitch和normal的运行方式让人感觉可能是这么搞得,原来就是搞个对象把资源报起来,然后弄个指针在对象里,每走一次函数操作相应指针,然后拿取对应的loader就行了。
- 简易实现下:
const path = require('path')
const fs = require('fs')
let entry = './src/index.js'
let options = {
resource: path.join(__dirname, entry),
loaders: [
path.join(__dirname, 'webpackconfig/loaders/myloader.js'),
path.join(__dirname, 'webpackconfig/loaders/myloader2.js'),
path.join(__dirname, 'webpackconfig/loaders/myloader3.js')
]
}
function createLoaderObject(loaderPath) {
let loaderObject = { data: {} }
loaderObject.path = loaderPath
loaderObject.normal = require(loaderPath)
loaderObject.pitch = loaderObject.normal.pitch
return loaderObject
}
function runLoaders(options, finalCallback) {
let loaderContext = {}
let resource = options.resource
let loaders = options.loaders
loaders = loaders.map(createLoaderObject)
loaderContext.loaderIndex = 0
loaderContext.readResource = fs.readFileSync
loaderContext.resource = resource
loaderContext.loaders = loaders
Object.defineProperty(loaderContext, 'request', {
get() {
return loaderContext.loaders.map(loaderObject => loaderObject.path)
.concat(loaderContext.resource)
.join('!')
}
})
Object.defineProperty(loaderContext, 'previousRequest', {
get() {
return loaderContext.loaders.slice(0, loaderContext.loaderIndex)
.map(loaderObject => loaderObject.path)
.join('!')
}
})
Object.defineProperty(loaderContext, 'remainingRequest', {
get() {
return loaderContext.loaders.slice(loaderContext.loaderIndex + 1)
.map(loaderObject => loaderObject.path)
.concat(loaderContext.resource).join('!')
}
})
Object.defineProperty(loaderContext, 'data', {
get() {
return loaderContext.loaders[loaderContext.loaderIndex].data
}
})
iteratePitchLoaders(loaderContext, finalCallback)
function processResource(loaderContext, finalCallback) {
let buffer = loaderContext.readResource(loaderContext.resource)
iterateNormalLoaders(loaderContext, buffer, finalCallback)
}
function iterateNormalLoaders(loaderContext, args, finalCallback) {
if (loaderContext.loaderIndex < 0) {
return finalCallback(null, args)
}
let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]
let normalFn = currentLoaderObject.normal
args = normalFn.call(loaderContext, args)
loaderContext.loaderIndex--;
iterateNormalLoaders(loaderContext, args, finalCallback)
}
function iteratePitchLoaders(loaderContext, finalCallback) {
if (loaderContext.loaderIndex >= loaderContext.loaders.length) {
loaderContext.loaderIndex--;
return processResource(loaderContext, finalCallback)
}
let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]
let pitchfn = currentLoaderObject.pitch
if (!pitchfn) {
loaderContext.loaderIndex++
return iteratePitchLoaders(options)
}
let args = pitchfn.apply(loaderContext,
[loaderContext.remainingRequest,
loaderContext.previousRequest,
loaderContext.data])
if (args) {
loaderContext.loaderIndex--
iterateNormalLoaders(loaderContext,args,finalCallback)
} else {
loaderContext.loaderIndex++;
return iteratePitchLoaders(loaderContext, finalCallback)
}
}
}
runLoaders(options, (err, result) => {
console.log('xxx', result.toString())
})
异步async实现
- loader里面可以调异步就是那个this.async或者那个this.callback
function myloader(source) {
console.log('1normal')
let finalCallback = this.async()
setTimeout(() => {
finalCallback(null, source)
}, 3000);
}
myloader.pitch = function () {
console.log('1pitch')
}
module.exports = myloader
- 原理就是做个标志,当调用时,会把同步标志改成异步,然后把要传给下一个loader的方法作为函数存起来,等待用户异步完成后调用。
loaderContext.isSync = true
loaderContext.async = () => {
loaderContext.isSync = false
return innerCallback
}
const innerCallback = loaderContext.callback = (err, args) => {
loaderContext.loaderIndex--
loaderContext.isSync = true
iterateNormalLoaders(loaderContext, args, finalCallback)
}
- 修改normal执行逻辑,如果是异步,等待用户调用走下一个函数。
function iterateNormalLoaders(loaderContext, args, finalCallback) {
if (loaderContext.loaderIndex < 0) {
return finalCallback(null, args)
}
let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]
let normalFn = currentLoaderObject.normal
args = normalFn.call(loaderContext, args)
if (loaderContext.isSync) {
loaderContext.loaderIndex--;
iterateNormalLoaders(loaderContext, args, finalCallback)
}
}
实现raw逻辑
- 如果有raw属性,会传过来buffer。做个判断就行了。
function iterateNormalLoaders(loaderContext, args, finalCallback) {
if (loaderContext.loaderIndex < 0) {
return finalCallback(null, args)
}
let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]
let normalFn = currentLoaderObject.normal
if (normalFn.raw && Buffer.isBuffer(args)) {
args = new Buffer(args, 'utf8')
} else if(!normalFn.raw && Buffer.isBuffer(args)) {
args = args.toString('utf8')
}
args = normalFn.call(loaderContext, args)
if (loaderContext.isSync) {
loaderContext.loaderIndex--;
iterateNormalLoaders(loaderContext, args, finalCallback)
}
}
总结
- 这个把文件包成一个对象的思路很值得学习,又简单,又好操作。如果多个文件要依某种顺序执行某些函数,就可以套用这种方式。
- 异步实现就是把下一步的执行用函数包起来,等待用户调用。