【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)
        }
    }

总结

  • 这个把文件包成一个对象的思路很值得学习,又简单,又好操作。如果多个文件要依某种顺序执行某些函数,就可以套用这种方式。
  • 异步实现就是把下一步的执行用函数包起来,等待用户调用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章