webpack原理解析(一)實現一個簡單的webpack

背景

Webpack 是當下最熱門的前端資源模塊化管理和打包工具。它能夠將許多鬆散的模塊按照依賴和規則打包成符合生產環境部署的前端資源。還能夠將按需加載的模塊進行代碼分隔,等到實際須要的時候再異步加載。webpack如此強大,其內部機制究竟是怎樣的,今天咱們來一探究竟。前端

準備工做

新建一個目錄,執行npm init -y 初始化一個package.json文件node

新建src下index.js,做爲入口文件,export.js做爲依賴的模塊webpack

新建webpack.config.js,這裏就按照webpack4的基礎配置寫一下git

新建bundle.js做爲入口進行webpack構建 建立lib下webpack.jsgithub

根據配置讀取入口文件

bundle.js:web

//讀取到webpackd 的配置文件
const options = require("./webpack.config.js")
const Webpack = require("./lib/webpack.js")
new Webpack(options).run()
複製代碼

webpack.js 這裏寫了一個Webpack類,bundle.js實例化這個類,將參數(引入的基礎配置的內容)傳進來並執行run方法npm

module.exports = class Webpack {
    constructor(options){
        console.log(options)
    }
    run(){
        console.log("hello")
    }
}
複製代碼

此時執行node bundle.js : json

image
已經能夠根據基礎配置讀取到入口模塊了

分析出依賴模塊

我這裏寫的index.js是這樣:數組

import { sayHi } from './export.js'
sayHi("hfj")
console.log("hello webpack index")
複製代碼

此時須要藉助node的核心模塊-- filesystem來讀到內容瀏覽器

const conts = fs.readFileSync(entryFile,'utf-8')
複製代碼

接下來藉助@babel/parser(記得本身安裝和引入。。)把內容抽象成語法樹

const ast = parser.parse(conts, {
            sourceType: "module"
          });
複製代碼

打印下ast.program.body

image
看type,哪裏是依賴模塊,哪裏是表達式,已經很明顯了,接下來經過@babel/traverse來提取依賴模塊:

const dependencies = {}
          traverse(ast,{
            ImportDeclaration({node}){
                const newPath = "./" + path.join(
                    path.dirname(entryFile),
                    node.source.value
                    )
                dependencies[node.source.value] = newPath
                console.log(dependencies)
            }
        })
複製代碼

注意下,這裏作了一個處理,將依賴模塊以對象的形式放到了dependencies裏。

編譯內容

接下來,經過@babel/core將ast處理成內容

const {code} = transformFromAst(ast,null,{
            presets: ["@babel/preset-env"]
        })
        console.log(code)
複製代碼

打印的code以下:

image
內容成功拿到了!

遍歷依賴模塊

以上是對入口文件進行的編譯,接下來還要看入口模塊的依賴模塊有哪些,分別按照剛纔的方法進行內容分析,最後作一個彙總(根據dependence來判斷的是否還有依賴模塊):

run(){
       const info = this.analysis(this.entry)
        this.modulesArr.push(info)
        for(let i=0;i<this.modulesArr.length;i++) {
            const item = this.modulesArr[i]
            const { dependencies } = item;
            if(dependencies) {
                for(let j in dependencies){
                    this.modulesArr.push(this.analysis(dependencies[j]))
                }
            }
        }
        // console.log(this.modules)
        //數組結構轉換
        const obj = {}
        this.modulesArr.forEach((item) => {
            obj[item.entryFile] = {
                dependencies:item.dependencies,
                code:item.code
            }
        })
    }
複製代碼

最終是將modulesArr轉化成了對象,方便後續處理

輸出瀏覽器可執行的js代碼

此處劃重點! webpack最終是將模塊化的js打包成一個chunk,打包後的js到dist(webpack基礎配置裏設置的output)

首先用了fs.writeFileSync將生成的bundle寫入filePath(filePath以前有在this.output保存) 接下來就是處理obj(上一步驟中的彙總後的模塊)了:

核心是用eval()來執行obj中的code,同時須要處理requrie和export require和export經過形參傳入,以後執行本身寫的reqire和export require處理了模塊路徑,exports是一個對象,看源碼--

const bundle = `(function(graph){
            function require(moduleId){
                function localRequire(relativePath){
                   return require(graph[moduleId].dependencies[relativePath]) 
                }
                var exports = {};
                (function(require,exports,code){
                    eval(code)
                })(localRequire,exports,graph[moduleId].code)
                return exports;
            }
            require('${this.entry}')
        })(${newCode})`
複製代碼

執行node bundle.js,在dist文件夾下輸出以下js:

(function(graph){
            function require(moduleId){
                function localRequire(relativePath){
                   return require(graph[moduleId].dependencies[relativePath]) 
                }
                var exports = {};
                (function(require,exports,code){
                    eval(code)
                })(localRequire,exports,graph[moduleId].code)
                return exports;
            }
            require('./src/index.js')
        })({"./src/index.js":{"dependencies":{"./export.js":"./src\\export.js"},"code":"\"use strict\";\n\nvar _export = require(\"./export.js\");\n\n(0, _export.sayHi)(\"hfj\");\nconsole.log(\"hello webpack index\");"},"./src\\export.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.sayHi = void 0;\n\nvar sayHi = function sayHi(params) {\n console.log(\"hello ~\" + params);\n};\n\nexports.sayHi = sayHi;"}})
複製代碼

在瀏覽器執行:輸出:

image

完成~

總結

webpack經過入口文件逐層遍歷到模塊依賴,進行代碼分析、代碼轉換,最終生成可在瀏覽器運行的打包後的代碼

本質上,webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(module bundler)。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關係圖(dependency graph),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個 bundle。

以上實現了一個簡易版本的webpack

下一篇:webpack原理解析(二)loader和plugin機制

若有興趣,歡迎進一步交流探討~

附github地址:github.com/manli-tongh…

參考:Webpack官網

關注咱們

相關文章
相關標籤/搜索