webpack打包原理分析和实现(三)

2022-09-29 08:33:53 浏览数 (1)

  • webpack打包原理分析和实现(一)
  • webpack打包原理分析和实现(二)
  • webpack打包原理分析和实现(三)

上一篇,获得了modules的对象,打印:

代码语言:javascript复制
{ './src/index.js':
   { dependencies: { './expo.js': './src\expo.js' },
     code:
      '"use strict";nnvar _expo = require("./expo.js");nn(0, _expo.add)(1, 2);nconsole.log("hello webpack"); //表达式' },
  './src\expo.js':
   { dependencies: {},
     code:
      '"use strict";nnObject.defineProperty(exports, "__esModule", {n  value: truen});nexports.minus = exports.add = void 0;nnvar add = function add(a, b) {n  return a   b;n};nnexports.add = add;nnvar minus = function minus(a, b) {
n  return a - b;n};nnexports.minus = minus;' } }

代码生成了,但是里面有require函数,exports浏览器是不认识的,因此接下来需要实现require和exports

具体步骤:

生成bundle文件main.js的路径filePath

bundle文件的内容, 注意第一段生成的代码var _expo = require("./expo.js")。参数里其实是相对路径,我们需要把它处理成项目路径,才能正常运行,localRequire函数的作用在于此,当做参数传入自执行函数

代码语言:javascript复制
 (function(require,exports,code){
             eval(code)
            })(localRequire,exports,graph[module].code)

第二个参数exports是一个空对象传入,装载导入的方法/对象,第三个参数是es6转换后的代码,通过eval去执行,遇到require和exports,会在参数里找

输出文件的代码

代码语言:javascript复制
//接收参数对象,生成自执行函数
    savefile(code){
        //! 生成bundle.js => ./dist/main.js
        const filePath=path.join(this.output.path,this.output.filename)
        console.log(filePath)
        //对象序列化, //如果不序列化,参数是对象=>
        // (function(){
        // })([object Object])
        //处理参数对象
        const newModules=JSON.stringify(code)
        //创建bundle.js  自执行函数
        const bundle=`(function(graph){
            //执行参数中代码,需要实现require函数,实现exports
            function require(module){
                function localRequire(relativePath){
                   return require(graph[module].dependencies[relativePath])//递归解析
                }
                var exports={};//要加分号,不然会连接面的(),把内容加到exports
                //require做进一步路径解析
                (function(require,exports,code){
                 eval(code)
                })(localRequire,exports,graph[module].code)
                return exports
            }
            require('${this.entry}')
        })(${newModules})`
        fs.writeFileSync(filePath,bundle,'utf-8')
    }

webpack.js完整代码

const fs = require(‘fs’)//node的核心模块fs

const parser = require(’@babel/parser’)//@babel/parser//分

const traverse = require(’@babel/traverse’).default// 处理得到的信息

const path = require(‘path’)

const babel = require(’@babel/core’)

// 析依赖内容

module.exports = class webpack {

constructor(options) {

console.log(options)

const {entry, output} = options

this.entry = entry

this.output = output

//存所有模块信息

this.modules = []

}

代码语言:javascript复制
run() {//入口函数
    const entryModule = this.moduleAnalyser(this.entry)
    console.log(entryModule)

    //!处理其他模块,做一个信息汇总
    this.modules.push(entryModule);
    for (let i = 0; i < this.modules.length; i  ) {
        const item = this.modules[i]
        const {dependencies} = item
        if (dependencies) {
            for (let j in dependencies) {
                this.modules.push(this.moduleAnalyser(dependencies[j]))//数组递归

            }
        }
    }
    console.log(this.modules)
    //! 数组处理成对象
    const obj = {}
    this.modules.forEach((item) => {
        obj[item.entryFile] = {
            dependencies: item.dependencies,
            code: item.code
        }
    })
    console.log('haha',obj)//已完成分析入口依赖
    this.savefile(obj)
}

moduleAnalyser(entryFile) {
    //! 分析入口模块的内容
    const content = fs.readFileSync(entryFile, 'utf-8')
    console.log(content)

    //!分析出哪些是依赖?以及依赖的路径
    const ast = parser.parse(content, {
        sourceType: 'module'
    })
    const dependencies = {}
    traverse(ast, {
        //提取哪个字段就用哪个函数
        ImportDeclaration({node}) {
            console.log(node.source.value)
            // path.dirname(entryFile)
            // console.log(path.dirname(entryFile))
            //路径拼接
            const newPathName = "./"   path.join(path.dirname(entryFile), node.source.value)
            console.log(newPathName)
            dependencies[node.source.value] = newPathName
            console.log(dependencies)
        }
    })
    console.log(ast.program.body)
    //! 处理内容,转换ast
    const {code} = babel.transformFromAst(ast, null, {
        presets: ['@babel/preset-env']
    })
    console.log(code)
    return {
        entryFile,
        dependencies,//如果没有值,说明没有依赖
        code
    }
}
//接收参数对象,生成自执行函数
savefile(code){
    //! 生成bundle.js => ./dist/main.js
    const filePath=path.join(this.output.path,this.output.filename)
    console.log(filePath)
    //对象序列化, //如果不序列化,参数是对象=>
    // (function(){
    // })([object Object])
    //处理参数对象
    const newModules=JSON.stringify(code)
    //创建bundle.js  自执行函数
    const bundle=`(function(graph){
        //执行参数中代码,需要实现require函数,实现exports
        function require(module){
            function localRequire(relativePath){
               return require(graph[module].dependencies[relativePath])//递归解析
            }
            var exports={};//要加分号,不然会连接面的(),把内容加到exports
            //require做进一步路径解析
            (function(require,exports,code){
             eval(code)
            })(localRequire,exports,graph[module].code)
            return exports
        }
        require('${this.entry}')
    })(${newModules})`
    fs.writeFileSync(filePath,bundle,'utf-8')
}

}

花了几个小时,把流程梳理了一遍,给个赞吧,完整代码

0 人点赞