webpack 打包後文件分析

webpack 用於編譯 javascript 模塊, 能夠把文件格式編譯成咱們想要的靜態文件格式, 可是處理的過程並非所有由 webpack 自己完成, webpack 只是提供了一個打包機制, 對於各種文件的打包處理須要使用相對應的 預處理模塊 loader 來處理, 做爲一種機制 webpack 會幫助各類 loader 提供識別入口目錄、入口文件、 輸出目錄, 輸出文件。javascript

首先咱們試着打包一個只包含 console.log('hello world')js 文件。java

初始化文件和安裝 webpack 環境

#  新建 demo 目錄
mkdir webpack-demo cd webpack-demo

# 初始化目錄
npm init -y

# 本地安裝 webpack 工具
npm install webpack webpacl-cli  --save-dev

# webpack 默認的入口文件是 .src/index.js 建立 src 目錄和 index.js 文件
mkdir src

echo 「console.log('hello world')」 > src/index.js

# 執行 webpack 命令 須要查看打包後文件, 這裏使用 development 模式 
npx webpack --mode development

複製代碼

簡化打包後文件

因爲打包後的文件比較繁瑣, 這裏咱們簡化一下打包後的文件webpack

(function(modules) {
    var installedModules = {}
    function __webpack_require__(moduleIid) {

    }
    return __webpack_require__(__webpack_require__.s = "./src/index.js")
})({
    "./src/index.js": (function(module, exports) {
                            eval("console.log('test webpack entry')");
                    })
})
複製代碼

打包後的文件含有大量的註釋和 webpack 自己的變量, 爲了方便分析能夠把這些註釋和 相似 __webpack_require__.s 的複製語句所有刪掉web

從上面的代碼能夠看到,npm

  1. 通過 webpack 打包後的代碼經過一個 IIFE 自執行函數, 這個函數接收一個對象參數, 這個對象的 key 爲入口文件的目錄, value 是一個執行入口文件裏面代碼的函數
  2. 這個對象做爲參數 modules 傳遞給 IIFE 函數
  3. IIFE 函數裏面聲明瞭一個變量 installedModules 用來存放緩存, 一個函數 __webpack_require__, 用來轉化入口文件裏面的代碼
  4. IIFE 最終把 modules 裏面的的 key 傳遞給 __webpack_require__ 函數並返回。

咱們進一步看 __webpack_require__ 函數都作了什麼。緩存

__webpack_require__ 分析

function (modules) {
    var installedModules = {}

    function __webpack_require__ (moduleId) {
        if(installedModules[moduleId]) {
            return installedModules[moduleId].exports
        }

        var module = installedModules[moduleId] = {
            i: moduleId,
            l:false,
            exports: {}
        }

        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)

        module.l = true

        return module.exports
    }
}

複製代碼
  1. __webpack_require__ 函數接收 ./src/index.js
  2. 首先檢查緩存 installedModules 中是否包含 key./src/index.js 的對象, 若是存在直接返回這個對象中的 exports
  3. 當緩存中不存在入口模塊的時候, 在緩存中生成一個對象並放到緩存中, 這個對象包括三個值: i l exports
  4. 使用 modules[moduleId].call 調用 IIFE 參數的 value 函數, 並把 value 對應的函數中的 this 指向賦值給了 module.exports, 後面的 call 方法的後面三個參數爲 value 對應函數的參數
  5. 最後返回了 module.exports, 這裏的 module.exports 在第四步的時候已賦值爲 IIFE 參數對象中的 value 對應的函數。

因此能夠看出來。 函數 __webpack_require__ 實際返回的就是 IIFE 參數對象中的 value 對應的函數, 也就是 eval("\nconsole.log('test webpack entry')\n\n\n//# sourceURL=webpack:///./src/index.js?")bash

當咱們運行 webpack 打包後的文件的時候執行的是 "console.log('test webpack entry')"函數

eval() 函數可計算某個字符串,並執行其中的的 JavaScript 代碼。工具

入口文件引用其餘模塊時的打包過程

上面講的打包過程入口文件中並無引用其餘的代碼模塊, 當入口文件中引用其餘的模塊的時候, webpack 的打包過程也和上述過程類似。ui

./src/ 下新建 main.js

module.exports = () => {
    console.log('main module')
}
複製代碼

./src/index.js 中引入 main.js

const main = reuiqre('./main.js')
console.log('webpack index entry')
main()
複製代碼

運行 npx webpack 打包後的文件

IIFE 參數的變化

(function (mudoles) {

})({
    './src/index.js': (function(module,exports, __webpack_require__) {
        eval("const main = __webpack_require__(/*! ./main.js */ \"./src/main.js\")\r\nconsole.log('test webpack entry')\r\n\n\n//# sourceURL=webpack:///./src/index.js?");
    }), 
    './src/main.js': (function(module, exports) {
        eval("console.log('main module')\n\n//# sourceURL=webpack:///./src/main.js?");
    })
})
複製代碼

若是入口文件中引用了其餘模塊的文件,將會把這些模塊添加到 IIFE 的參數對象中, key 爲模塊的路徑, value 執行該模塊代碼的函數。

IIFE 函數執行邏輯的變化

function (modules) {
    var installedModules = {}

    function __webpack_require__ (moduleId) {
        if (installedModules[moduleId]) {
            return installedMOdules[mudoleId].exports
        }

        var module = installedModuled[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        }
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)

        return module.exports
    }

    return __webpack_require__(__webpack_require__.s = "./src/index.js")
}

複製代碼

上面的代碼中依舊返回了以 ./src/index.js 爲參數的函數。 可是函數裏面的邏輯發生了改變。

  1. 聲明一個 installedModules 變量存放緩存
  2. ./src/index.js 傳入 __webpack_require__ 函數
  3. ./src/index.js 不在緩存中, 往下執行
  4. 聲明一個 module 並在緩存中存放一以 ./src/index.jskey 的對象
  5. 調用 modules[moduleId] 函數,並指明 做用域和參數 也就是
    function(module,exports, __webpack_require__) {
            eval("const main = __webpack_require__(/*! ./main.js */ \"./src/main.js\")\r\nconsole.log('test webpack entry')\r\n\n\n//# sourceURL=webpack:///./src/index.js?")}
    複製代碼
  6. 返回 module.exports, 因爲在第五步調用 modules['./src/index.js'] 函數的時候, 已經把 module.exports 做爲了函數的 this 做用域, 因此這時 module.exports 實際就是 modules['./src/index.js'] 執行的函數。
  7. 在上面的函數中, eval 代碼中使用了一個函數 __webpack_require__, 這個函數就是在第五步 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__) 的最後一個參數 __webpack_require__, 這時繼續調用 __webpack__require__ 函數並傳入 ./src/main.js
  8. ./src/main.js 傳入 __webpack_require__ 中, 依舊不在緩存, 再次聲明一個變量 module 並在緩存中新增一個 key./src/main.js 的對象。
  9. modules[moduleId].call 這時調用 IIFE 參數對象中 key./src/main.js 的函數:
    (function(module, exports) {
            eval("console.log('main module')\n\n//# sourceURL=webpack:///./src/main.js?");
        })
    複製代碼
  10. 返回 module.export, 同第6步類似這時的 module.exports 就是 modules[./src/main.js] 對應的函數。
  11. 返回的最終結果就是(去除了 eval 和註釋):
const main = console.log('main module')
    console.log(test webpack entry)
複製代碼
相關文章
相關標籤/搜索