webpack編譯后的代碼如何在瀏覽器執行


瀏覽器是無法直接使用模塊之間的commonjs或es6,webpack在打包時做了什么處理,才能讓瀏覽器能夠執行呢,往下看吧。

使用commonjs語法

先看下寫的代碼,
app.js

minus.js

webpack.config.js

代碼非常簡單,沒啥可說的,直接上編譯后的代碼來分析,代碼可以直接復制過來在瀏覽器執行調試

  // 一個IIFE, 方法的形參是一個對象,key是頁面的路徑,value是頁面的代碼
;(function (modules) {
    // 緩存已經讀取過的module,避免重復執行
    var installedModules = {}

    // 核心方法。moduleId以頁面的路徑作為屬性
    function __webpack_require__(moduleId) {
        // 如果緩存存有moduleId,代表已經執行過,直接把緩存里的值返回
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports
        }
        // Create a new module (and put it into the cache)
        var module = (installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {},
        })

        // Execute the module function
        modules[moduleId].call(
            module.exports,
            module,
            module.exports,
            __webpack_require__
        )

        // Flag the module as loaded
        module.l = true

        // Return the exports of the module
        return module.exports
    }
    // Load entry module and return exports
    return __webpack_require__((__webpack_require__.s = "./app.js"))
})(
    // IIFE的參數,把所有需要的頁面轉換為對象的key,先讀取入口文件app.js,文件內部需要minus.js,所以再次調用__webpack_require__執行
    {
    "./app.js": function (module, exports, __webpack_require__) {
        var minus = __webpack_require__(
            /*! ./vendor/minus */ "./vendor/minus.js"
        )
        console.log("minus(1, 2) = ", minus(1, 2))
    },

    "./vendor/minus.js": function (module, exports) {
        module.exports = function (a, b) {
            return a - b
        }
    },
})

再來看下es6語法有什么區別

;(function (modules) {
    // webpackBootstrap
    // The module cache
    var installedModules = {}

    // The require function
    function __webpack_require__(moduleId) {
        // Check if module is in cache
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports
        }
        // Create a new module (and put it into the cache)
        var module = (installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {},
        })

        // Execute the module function
        modules[moduleId].call(
            module.exports,
            module,
            module.exports,
            __webpack_require__
        )

        // Flag the module as loaded
        module.l = true

        // Return the exports of the module
        return module.exports
    }

    // expose the modules object (__webpack_modules__)
    // __webpack_require__是一個方法,給這個方法加一個屬性,內容是頁面模塊這個對象
    __webpack_require__.m = modules

    // expose the module cache
    // 同理也是加了一個讀緩存模塊的屬性
    __webpack_require__.c = installedModules

    // define getter function for harmony exports
    // 給這個對象賦值,為什么不直接賦值呢,因為這樣子,這個屬性是不可變的,怎么改使用還是取的get
    __webpack_require__.d = function (exports, name, getter) {
        if (!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                enumerable: true,
                get: getter,
            })
        }
    }

    // define __esModule on exports
    // 給使用es6導出的模塊打上標記,Symbol.toStringTag 
    // 請看https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag
    // 做這一步的原因是 其他頁面導出的時候會判斷有沒有標記,有的話就會換另一種方式讀取,導出看__webpack_require__.n
    __webpack_require__.r = function (exports) {
        if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
            Object.defineProperty(exports, Symbol.toStringTag, {
                value: "Module",
            })
        }
        Object.defineProperty(exports, "__esModule", { value: true })
    }

    // getDefaultExport function for compatibility with non-harmony modules
    // 看導出時的讀取方式是采用es6還是commonjs
    __webpack_require__.n = function (module) {
        var getter =
            module && module.__esModule
                ? function getDefault() {
                      return module["default"]
                  }
                : function getModuleExports() {
                      return module
                  }
        __webpack_require__.d(getter, "a", getter)
        return getter
    }

    // Object.prototype.hasOwnProperty.call
    __webpack_require__.o = function (object, property) {
        return Object.prototype.hasOwnProperty.call(object, property)
    }

    // Load entry module and return exports
    return __webpack_require__((__webpack_require__.s = "./app.js"))
})({
    "./app.js": function (module, __webpack_exports__, __webpack_require__) {
        "use strict"
        __webpack_require__.r(__webpack_exports__)
        // 這里需要注意,使用es6導入的時候,webpack改變了原變量名字,所以這個變量可以使用可以修改,
        // 但是如果變量是對象的情況下,不允許修改變量指向的內存地址

        /* harmony import */ var _vendor_sum__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
            /*! ./vendor/sum */ "./vendor/sum.js"
        )

        console.log(
            "sum(1, 3) = ",
            Object(_vendor_sum__WEBPACK_IMPORTED_MODULE_0__["sum"])(1, 3)
        )
    },

    "./vendor/sum.js": function (
        module,
        __webpack_exports__,
        __webpack_require__
    ) {
        "use strict"
        // 打上標記是es6語法
        __webpack_require__.r(__webpack_exports__)
        // 屬性賦值
        /* harmony export (binding) */ __webpack_require__.d(
            __webpack_exports__,
            "sum",
            function () {
                return sum
            }
        )
        function sum(a, b) {
            return a + b
        }
    },
})

如何使用分包懶加載

在某些特定環境比如點擊某個按鈕才加載,webpack會創建一個script標簽來加載內容(vue-cli 有利用prefetch做優化)同時生成一個promise
把resolve和reject放進installedChunks數組里,當頁面加載好后會從數組取出resolve執行(利用webpackJsonp重寫push方法)

// 加載模塊
 __webpack_require__.e = function requireEnsure(chunkId) {
        var promises = []

        // JSONP chunk loading for javascript

        var installedChunkData = installedChunks[chunkId]
        if (installedChunkData !== 0) {
            // 0 means "already installed".

            // a Promise means "currently loading".
            if (installedChunkData) {
                promises.push(installedChunkData[2])
            } else {
                // setup Promise in chunk cache
                var promise = new Promise(function (resolve, reject) {
                    installedChunkData = installedChunks[chunkId] = [
                        resolve,
                        reject,
                    ]
                })
                promises.push((installedChunkData[2] = promise))

                // start chunk loading
                var script = document.createElement("script")
                var onScriptComplete

                script.charset = "utf-8"
                script.timeout = 120
                if (__webpack_require__.nc) {
                    script.setAttribute("nonce", __webpack_require__.nc)
                }
                script.src = jsonpScriptSrc(chunkId)

                // create error before stack unwound to get useful stacktrace later
                var error = new Error()
                onScriptComplete = function (event) {
                    // avoid mem leaks in IE.
                    script.onerror = script.onload = null
                    clearTimeout(timeout)
                    var chunk = installedChunks[chunkId]
                    if (chunk !== 0) {
                        if (chunk) {
                            var errorType =
                                event &&
                                (event.type === "load" ? "missing" : event.type)
                            var realSrc =
                                event && event.target && event.target.src
                            error.message =
                                "Loading chunk " +
                                chunkId +
                                " failed.\n(" +
                                errorType +
                                ": " +
                                realSrc +
                                ")"
                            error.name = "ChunkLoadError"
                            error.type = errorType
                            error.request = realSrc
                            chunk[1](error)
                        }
                        installedChunks[chunkId] = undefined
                    }
                }
                var timeout = setTimeout(function () {
                    onScriptComplete({ type: "timeout", target: script })
                }, 120000)
                script.onerror = script.onload = onScriptComplete
                document.head.appendChild(script)
            }
        }
        return Promise.all(promises)
    }

// 設置webpackJsonp
var jsonpArray = (window["webpackJsonp"] = window["webpackJsonp"] || [])
    var oldJsonpFunction = jsonpArray.push.bind(jsonpArray)
    jsonpArray.push = webpackJsonpCallback
    jsonpArray = jsonpArray.slice()
    for (var i = 0; i < jsonpArray.length; i++)
        webpackJsonpCallback(jsonpArray[i])
    var parentJsonpFunction = oldJsonpFunction

// 異步加載的模塊,加載好后,執行webpackJsonp的push方法
;(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
    [0],
    {
        /***/ "./vendor/sum.js": /***/ function (
            module,
            __webpack_exports__,
            __webpack_require__
        ) {
            "use strict"
            __webpack_require__.r(__webpack_exports__)
            /* harmony export (binding) */ __webpack_require__.d(
                __webpack_exports__,
                "sum",
                function () {
                    return sum
                }
            )
            function sum(a, b) {
                return a + b
            }

            /***/
        },
    },
])


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM