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
            }

            /***/
        },
    },
])
posted @ 2020-05-11 15:49  爱吃巧克力的狗  阅读(1674)  评论(0编辑  收藏  举报