動態

詳情 返回 返回

根據webpack打包規則,實現簡單的打包手寫實現 - 動態 詳情

首先導入導出分為幾類

  • CommonJS導入CommonJS
  • EsModule導入CommonJS
  • EsModule導入EsModule

根據實現規則,簡單代碼樣例

// index.js

// import log,{age} from "./log.js"
// // let log = require('./log.js')
// console.log('index.js內容')
// console.log("log", log,age)


// log.js
// CommonJS導出
module.exports ="logger"
// export default "jack"
// export const age = 18;

手寫模擬實現代碼
模塊中的代碼經過 webpack 打包之後,就被放在了一個自執行函數中,這個自調用函數接收一個參數,它的值是一個對象,這個對象的鍵就是相對於當前項目來説, 被打包文件的路徑(在這裏被賦值給了moduleId),它的值就是我們被打包模塊中的源代碼,這個函數定義的時候接收了兩個參數 module exports

在單模塊打包中,且這個模塊中沒有其它的導包操作為例

  1. 將所有的內容都放置於一個自調用函數(IIFE)中,然後將被打包模塊相關信息進行傳參
  2. 相關信息就是一個對象,格式就是 moduleId:組裝後的函數( 函數體就是打包前的源碼 )
  3. 自調用函數體內首先定義一個空對象用於存儲緩存
  4. 自定義一個 webpack_require 函數,它接收一個 ModuleId
  5. 這個 moduleId 是在自調用函數體的最後一行調用時傳入的

自調用函數邏輯:

  • 判斷當前 ModuleID 對應的模塊是否存在於緩存中
  • 如果緩存中不存在的情況下,就自定義的一個 module 存放一個對象
  • 同時還將這個 對象存放在了 installModules[moduleId] 裏
  • 這個對象有三個屬性: i (存放當前被加載模塊的 moduleId), l(用於標記當前模塊是否已加載, true ) , exports={} (一個容器,在將來調用自己的 require 方法時去存放我們被打包模塊中的內容)
  • 通過 Modules[moduleId] 找到最初組裝的那個函數以 call 的方式來調用
  • 首先修改了this指向,然後傳入了 module 和 module.exports , 最後還有
    一個 webpack_require ,為了將來應對被打包模塊中還有其它的模塊導入
(function (modules) {
    // 緩存被加載過的模塊
    let installedModules = {};

    // 定義一個__webpak_require__方法來替換 import require的加載操作
    function __webpack_require__(moduleId) {
        // 緩存優先,判斷當前緩存中是否存在要被加載的模塊,如果存在就直接返回
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports
        }

        // 如果當前緩存中不存在,就需要自己定義,執行被導入的模塊內容加載
        let module = installedModules[moduleId] = {
            i: moduleId,  // 存放當前被加載模塊的 moduleId
            l: false,  // 用於標記當前模塊是否已加載, true
            exports: {}  // 一個容器,在將來調用自己的 require 方法時去存放我們被打包模塊中的內容
        }

        // 調用當前moduleId對應的函數,完成內容的加載,__webpack_require__c參數存在是為了解決遞歸調用的問題
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)

        // 當上述方法調用結束之後,修改 l 的值,標識當前模塊內容已經加載完成了
        module.l = true

        // 加載工作完成之後,將拿回來的內容返回至調用位置
        return module.exports
    }

    // 定義m屬性來保存modules
    __webpack_require__.m = modules

    // 定義c屬性用於保存cache
    __webpack_require__.c = installedModules

    // 定義o方法就是接收一個對象,和一個屬性名,然後返回一個布爾值,判斷這個對象的身上是否存在這個屬性
    __webpack_require__.o = function (object, property) {
        return Object.prototype.hasOwnProperty(object, property)
    }

    // 定義d方法用於在對象身上添加制定屬性,並且給該屬性提供一個getter
    __webpack_require__.d = function (exports, name, getter) {
        if (!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                enumerable: true,
                get: getter
            });
        }
    }

    // 定義r方法用於標識當前模塊EsModule
    __webpack_require__.r = function (exports) {
        if (typeof Symbol !== undefined && Symbol.toStringTag) {
            Object.defineProperty(exports, Symbol.toStringTag, {
                value: "module"
            })
        }
        Object.defineProperty(exports, "__esModule", {
            value: true
        })
    }

    // 定義n方法用於設置具體的getter,依據不同的規範下的 module來返回一個相應的 getter 
    __webpack_require__.n = function (module) {
        let getter = module && module.__esModule ? function getDefault() {
            return module['default']
        } : function getModuleExports() {
            return module
        }
        __webpack_require__.d(getter, 'a', getter)
        return getter
    }

    // 定義p屬性,用於保存資源訪問路徑
    __webpack_require__.p = ""

    // 調用__webpack_require__方法,執行模塊導入和加載操作
    return __webpack_require__(__webpack_require__.s = "./src/index.js")

})(
    // CommonJS導入CommonJS
    //     {
    //     "./src/index.js": (function (module, exports, __webpack_require__) {
    //         let log = __webpack_require__( /*! ./log.js */ "./src/log.js")
    //         console.log('index.js內容')
    //         console.log("log", log)
    //     }),
    //     "./src/log.js": (function (module, exports) {
    //         // CommonJS導出
    //         module.exports = "logger"
    //     })
    // }

    // EsModule導入CommonJS
    {
        "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
            "use strict";
            __webpack_require__.r(__webpack_exports__);
            var _log_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./log.js */ "./src/log.js");
            var _log_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/ __webpack_require__.n(_log_js__WEBPACK_IMPORTED_MODULE_0__);
            console.log('index.js內容')
            console.log("log", _log_js__WEBPACK_IMPORTED_MODULE_0___default.a)
        }),
        "./src/log.js": (function (module, exports) {
            module.exports = "logger"
        })
    }

    // EsModule導入EsModule
    // {
    //     "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
    //         "use strict";
    //         __webpack_require__.r(__webpack_exports__);
    //         var _log_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./log.js */ "./src/log.js");
    //         console.log('index.js內容')
    //         console.log("log", _log_js__WEBPACK_IMPORTED_MODULE_0__["default"], _log_js__WEBPACK_IMPORTED_MODULE_0__["age"])
    //     }),
    //     "./src/log.js": (function (module, __webpack_exports__, __webpack_require__) {
    //         "use strict";
    //         __webpack_require__.r(__webpack_exports__);
    //         __webpack_require__.d(__webpack_exports__, "age", function () {
    //             return age;
    //         });
    //         __webpack_exports__["default"] = ("jack");
    //         const age = 18;
    //     })
    // }
)

通過編譯生成的index.html,引入手寫built.js文件,控制枱可以正常打印

有一個特殊的t方法,實現如下

    // weback原生註釋
    // create a fake namespace object
    // mode & 1: value is a module id, require it
    // mode & 2: merge all properties of value into the ns
    // mode & 4: return value when already ns object
    // mode & 8|1: behave like require
    // 定義t方法,用於加載制定value的模塊內容,之後對內容進行處理再返回.
    // 接收二個參數,一個是 value 一般用於表示被加載的模塊id ,第二個值 mode 是一個二進制的數值
    __webpack_require__.t = function (value, mode) {
        // t 方法內部做的第一件事情就是調用自定義的 require 方法加載value(value一般就是模塊id) 對應的模塊導出,重新賦值給 value 
        // 當獲取到了這個 value 值之後餘下的 8 4 ns 2 都是對當前的內容進行加工處理,然後返回使用
        if (mode & 1) {
            value = __webpack_require__(value)
        }
        // 當mode & 8 成立是直接將 value 返回 ( 1和8同時成立,相當於是commonJS )
        if (mode & 8) {
            return value
        }
        // 當 mode & 4 成立時直接將 value 返回(esModule)
        if (mode & 4 && typeof vlaue === 'object' && value && value.__esModule) {
            return value
        }

        // 如果8和4都沒有成立則需要自定義ns來通過default屬性返回內容
        let ns = Object.create(null)
        __webpack_require__.r(ns) // ns通過r方法後會多一個Symbol.toStringTag和__esModule
        // 如果拿到的 value 是一個可以直接使用的內容,例如是一個字符串,將它掛載到 ns 的 default 屬性上
        Object.defineProperty(ns, 'default', {
            enumerable: true,
            value: value
        })
        // 例如返回的是一個對象,進行遍歷,掛載相應key的屬性,還有getter
        if (mode & 2 && typeof value !== 'string') {
            for (var key in value) {
                __webpack_require__.d(ns, key, function () {
                    // 提供一個getter
                    return value[key]
                }.bind(null, key))
            }
        }
        return ns
    }

測試代碼

    // t方法測試
    {
        "./src/index.js": (function (module, exports, __webpack_require__) {
            let log = __webpack_require__.t( /*! ./log.js */ "./src/log.js", 0b0111) // 通過第二個值不同設置可以測試不同情況
            console.log('index.js內容')
            console.log("log", log)
        }),
        "./src/log.js": (function (module, exports) {
            // CommonJS導出
            module.exports = "logger"
        })
    }
user avatar haoqidewukong 頭像 smalike 頭像 dunizb 頭像 febobo 頭像 ccVue 頭像 libubai 頭像 tanggoahead 頭像 baidujiagoushi 頭像 ruanjiankaifa_xiaofanya 頭像 zego 頭像 yayujs 頭像 kinglisky 頭像
點贊 22 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.