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,一起學習交流

發佈了57 篇原創文章 · 獲贊 47 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章