jyzwf / blog

在Issues里记录技术得点滴
17 stars 3 forks source link

webpack打包后代码浅析1 #43

Open jyzwf opened 6 years ago

jyzwf commented 6 years ago

这里只是简单的窥探 webpack 的打包过程,能力有限,欢迎交流

webpack 构建流程

  1. 解析webpack配置参数,合并从shell传入和 webpack.config.js 文件里配置的参数,生产最后的配置结果
  2. 注册所有配置的插件,好让插件监听 webpack 构建生命周期的事件节点,以做出对应的反应
  3. 从配置的 entry 入口文件开始解析文件构建AST语法树,找出每个文件所依赖的文件,递归下去
  4. 在解析文件递归的过程中根据文件类型和 loader配置 找出合适的loader用来对 文件进行转换
  5. 递归完后得到每个文件的最终结果,根据 entry 配置生成代码块 chunk
  6. 输出所有 chunk 到文件系统

webpack.config.js:

const path = require('path')

module.exports = {
    entry: './main.js',
    output: {
        filename: 'bundlejs',
        path: path.resolve(__dirname, './dist')
    }
}

main.js:

const show = require('./show')
show('webpack hello 77')

show.js:

function show(content) {
    window.document.getElementById('app').innerHTML = content
}

module.exports = show

最后打包到 bundle.js,现在我们就看看他是长什么样子?

(function(modules){
    // xxx
})([
    // main.js
    (function(module,exports,__webpack_require__){
        const show = __webpack_require__(1)

        show('webpack hello 77')
    }),

    // show.js
    (function(module,exports){
        function show(content) {
            window.document.getElementById('app').innerHTML = content
        }

        module.exports = show
    })
])

我们可以看到,它是一个立即执行函数,该函数的参数是一个数组,并把 entry 所对应的文件作为该数组的第一个元素,那么是不是这是一个约定呢?(我们后面再谈),然后将 main 依赖的 show 作为后续元素,而且我们注意到,它不像 main 一样有 __webpack_require__ 这个参数,这个又是不是是一个约定呢?

好,现在我们来看看立即执行函数里面发生了什么

    // 用于模块的缓存
    var installedModules = {}

    function __webpack_require__(moduleId) {    
        // 先检查该 id 对应的模块是否存在于缓存中
        if (installedModules[moduldId]) {
            // 我们看见,如果有,就直接暴露该模块的 `exports` 对象
            return installedModules[moduleId].exports;
        }

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

        // 执行该模块,并把this 指向该模块的exports对象,
        // 最后把该模块、该模块的 `exports` 方法,`__webpack_require__`方法传给该模块函数
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)

        // 这里我们知道,模块的 `l` 属性是用来说明该模块是否已经被加载
        module.l = true

        // 返回该模块暴露的接口
        return module.exports;
    }

    // 暴露 modules 对象
    __webpack_require__.m = modules

    // 暴露模块的缓存
    __webpack_require__.c = installedModules

    // 为 `harmony exports` 定义 getter 方法
    __webpack_require__.d = function (exports, name, getter) {
        if (!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                configurable: false,
                enumerable: true,
                get: getter
            })
        }
    }

    // 获取默认的函数为了兼容性
    __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
    }

    // 调用对象的 `hasOwnProperty`
    __webpack_require__.o = function (object, property) {
        return Object.prototype.hasOwnProperty.call(object, property)
    }

    // Webpack 配置中的 publicPath,用于加载被分割出去的异步代码
    __webpack_require__.p = ""

    // 加载传进来的modules[0],并返回 `exports`
    return __webpack_require__(__webpack_require__.s = 0)

如上,我们看见,先是定义一个 __webpack_require__,然后在上面添加一些方法,最后开始模块的加载,我们来讲解加载的流程, 先是执行 数组的第一个元素,main,然后里面遇到 __webpack_require__(1),自动将 要加载的数值加一,然后去加载 show.js,并将 exports 返回,供 main 使用,这里我们看见模块加载和 SeaJs一样,是延迟加载的,show 里面的函数,就是 SeaJsdefine函数的factory 参数,详见我写的 seajs 阅读感悟

至此,我们简单的分析了其中的加载原理,但这仅仅是两个模块的情况,那么更复杂的呢?还有里面的 __webpack_require__的一些方法在这里面都没有体现作用,它们又是干嘛的呢?

我们继续 ^_^