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

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

{ './src/index.js':
   { dependencies: { './expo.js': './src\\expo.js' },
     code:
      '"use strict";\n\nvar _expo = require("./expo.js");\n\n(0, _expo.add)(1, 2);\nconsole.log("hello webpack"); //表达式' },
  './src\\expo.js':
   { dependencies: {},
     code:
      '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports.minus = exports.add = void 0;\n\nvar add = function add(a, b) {\n  return a + b;\n};\n\nexports.add = add;\n\nvar minus = function minus(a, b) {
\n  return a - b;\n};\n\nexports.minus = minus;' } }

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

  • 生成bundle文件main.js的路径filePath
  • bundle文件的内容, 注意第一段生成的代码var _expo = require("./expo.js")。参数里其实是相对路径,我们需要把它处理成项目路径,才能正常运行,localRequire函数的作用在于此,当做参数传入自执行函数
     (function(require,exports,code){
                 eval(code)
                })(localRequire,exports,graph[module].code)
    
  • 第二个参数exports是一个空对象传入,装载导入的方法/对象,第三个参数是es6转换后的代码,通过eval去执行,遇到require和exports,会在参数里找

输出文件的代码

//接收参数对象,生成自执行函数
    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 = []
}

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

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

如果你热爱编程,或者遇到了什么问题,欢迎加入群663077768,一起学习交流

发布了49 篇原创文章 · 获赞 47 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/z2516305651/article/details/103646874