【webpack】webpack構建流程筆記(五)

前言

  • 前面講了loader,這篇手寫loader流程。

基本功能實現

  • 這個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//loader3元素
    loaderObject.normal = require(loaderPath)
    loaderObject.pitch = loaderObject.normal.pitch
    return loaderObject
}

function runLoaders(options, finalCallback) {
    let loaderContext = {}//webpack loader中this
    let resource = options.resource
    let loaders = options.loaders//絕對地址數組
    loaders = loaders.map(createLoaderObject)//返回每個loader對象數組
    loaderContext.loaderIndex = 0//當指針用 在某個lader裏可以可以借用index來獲取下一個loader的index
    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) {//讀資源0
        let buffer = loaderContext.readResource(loaderContext.resource)
        iterateNormalLoaders(loaderContext, buffer, finalCallback)//讀完走normal
    }
    function iterateNormalLoaders(loaderContext, args, finalCallback) {//讀normal
        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) {//讀pitch
        if (loaderContext.loaderIndex >= loaderContext.loaders.length) {//沒有pitch了,就拿resource
            loaderContext.loaderIndex--;
            return processResource(loaderContext, finalCallback)
        }
        let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]//當前loader對象
        let pitchfn = currentLoaderObject.pitch
        if (!pitchfn) {
            loaderContext.loaderIndex++//沒有Pitch繼續往下走pitch
            return iteratePitchLoaders(options)
        }
        let args = pitchfn.apply(loaderContext,
            [loaderContext.remainingRequest,
            loaderContext.previousRequest,
            loaderContext.data])
        if (args) {//pitch有返回值跳到前一個normal
            loaderContext.loaderIndex--
            iterateNormalLoaders(loaderContext,args,finalCallback)
        } else {//下一個loader
            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);
    //return source
}
myloader.pitch = function () {
    console.log('1pitch')
}
module.exports = myloader
  • 原理就是做個標誌,當調用時,會把同步標誌改成異步,然後把要傳給下一個loader的方法作爲函數存起來,等待用戶異步完成後調用。
    loaderContext.isSync = true//默認同步模式
    loaderContext.async = () => {//調用此函數,才繼續下一個normal
        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) {//讀normal
        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) {//讀normal
        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)
        }
    }

總結

  • 這個把文件包成一個對象的思路很值得學習,又簡單,又好操作。如果多個文件要依某種順序執行某些函數,就可以套用這種方式。
  • 異步實現就是把下一步的執行用函數包起來,等待用戶調用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章