前言
基本功能實現
- 這個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)
}
}
總結
- 這個把文件包成一個對象的思路很值得學習,又簡單,又好操作。如果多個文件要依某種順序執行某些函數,就可以套用這種方式。
- 異步實現就是把下一步的執行用函數包起來,等待用戶調用。