1

前言

本文主要介绍webpack打包原理。webpack这个项目还是比较复杂的,现在已经到了webpack5的版本了。但是webpack打包原理并不是很复杂。本文的所有内容基于webpack0.1版本,github上能够找到的webapck的第一个版本;

基础概念

  • chunk: webpack打包生成的文件是chunk
  • module: 模块,可以是common.js模块,也可以是es6模块,还可以是amd的模块
  • chunkId: chunkId的标识
  • moduleId: 模块的标识
  • require.ensure: commonjs规范中按需加载代码的方法,webpack实现了这个方法;其他按需加载的方法包括amd, es6中的import方法;
    require.ensure(dependencies, callback)

参考文章: https://github.com/webpack/do...
这里我只解释下require.ensure函数的第一个参数,在执行第二个参数(回调函数)之前,第一个参数的依赖都已经被加载,但是加载后不会被执行。这个和amd规范不同,amd规范依赖加载后就执行了;

一个例子

下文所有内容基于webpack 0.1版本,如果你采用其他版本,打包出来的结果大同小异,不影响对webpack打包原理的理解

目录结构:

clipboard.png

代码
webpack.config.js

var path = require("path");
module.exports = {
    mode: 'development',
    entry: path.resolve(__dirname, './index.js'),
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }

}

index.js

var a = require("./src/a");
var b = require("./src/b");
require.ensure([], function(require) {
    require("./src/b").xyz();
    var d = require("./src/d");
});

a.js

//module a

b.js

//module b

c.js

//module c

d.js

//module d

打包后的代码:
main.js


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

    // object to store loaded and loading chunks
    // "0" means "already loaded"
    // Array means "loading", array contains callbacks
    var installedChunks = { 0: 0 };

    // The require function
    function 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] = {
            exports: {},
            id: moduleId,
            loaded: false
        };

        // Execute the module function
        modules[moduleId].call(null, module, module.exports, require);

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

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

    // This file contains only the entry chunk.
    // The chunk loading function for additional chunks
    require.e = function requireEnsure(chunkId, callback) {
        // "0" is the signal for "already loaded"
        if (installedChunks[chunkId] === 0)
            return callback.call(null, require);

        // an array means "currently loading".
        if (installedChunks[chunkId] !== undefined) {
            installedChunks[chunkId].push(callback);
        } else {
            // start chunk loading
            installedChunks[chunkId] = [callback];
            var head = document.getElementsByTagName('head')[0];
            var script = document.createElement('script');
            script.type = 'text/javascript';
            script.charset = 'utf-8';
            script.src = modules.c + "" + chunkId + ".js";
            head.appendChild(script);
        }
    };

    // expose the modules object (__webpack_modules__)
    require.modules = modules;

    // expose the module cache
    require.cache = installedModules;

    // install a JSONP callback for chunk loading
    window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
        // add "moreModules" to the modules object,
        // then flag all "chunkIds" as loaded and fire callback
        var moduleId, chunkId, callbacks = [];
        while (chunkIds.length) {
            chunkId = chunkIds.shift();
            if (installedChunks[chunkId])
                callbacks.push.apply(callbacks, installedChunks[chunkId]);
            installedChunks[chunkId] = 0;
        }
        for (moduleId in moreModules) {
            modules[moduleId] = moreModules[moduleId];
        }
        while (callbacks.length)
            callbacks.shift().call(null, require);
    };

    // Load entry module and return exports
    return require(0);
})
    /************************************************************************/
    ({
        // __webpack_public_path__
        c: "",

        0:function (module, exports, require) {

                var a = require(1);
                var b = require(2);
                require.e/*nsure*/(1, function (require) {
                    require(2).xyz();
                    var d = require(3);
                });

            },

        1:
            function (module, exports, require) {

                // module a

            },

        2: function (module, exports, require) {

                // module b

        }
    })

1.js


   webpackJsonp([1],
{

/***/ 3:
/***/ function(module, exports, require) {

    exports.lzy = 123;
    // module d

/***/ }

}
)

两个文件一共130多行代码,看懂这些代码基本就明白webpack打包原理了,下面对打包后的文件进行分析:打包后生成了2个chunk, 1.js和main.js,其中1.js是异步加载的chunk;

chunk分析

main.js是一个自执行函数,自执行函数的参数是一个对象;对象的key是moduleId, 对象的值是一个函数,这个函数对应了一个模块;webpack将所有的模块打包成一个函数,可以看到moduleId为0,1,2依次对应index.js, a.js, b.js;那么d.js对应的模块呢,别急,请看异步1.js chunk,包括了d.js,说明d.js是异步加载的;

既然webpack将每一个文件打包成一个模块,我们对几个关键的函数进行分析require(模块加载), 模块异步加载require.e, 加载成功后执行的回调函数webpackJsonp

require方法

很简单,没有几行代码,应该可以看懂,传入moduleId, 根据moduleId查找对应的模块,并调用

require.e方法

这个方法可以说一下,可以看到通过插入script,异步加载文件,如何避免重复加载呢,比如说d.js已经引入了,再次require.ensure如何避免重复加载呢?带着问题就明白了
installedChunks[chunkId]有三种状态:

  1. installedChunks[chunkId] == 0; 说明脚本已经加载完成,webpackJsonp方法中有将installedChunks[chunkId]置0的操作
  2. installedChunks[chunkId] !== undefined,说明脚本正在加载,还没执行,这个时候在数组中添加回调函数;
  3. installedChunks[chunkId] === undefined,说明第一次加载,需要创建一个script引入脚本,即为异步chunk
webpackJsonp

脚本加载成功后,执行的回调函数;可以看出,主要逻辑是执行回调函数,为啥这样命名:webpackJsnop,我想跟jsnop的回调函数真的很像啊,webpackJsonp中执行require.ensure的回调函数,jsonp的回调函数接收服务端返回的数据

webpack打包原理说完了,如果你还是不理解,从头跑一遍上面的代码吧;


lizeyuan
25 声望1 粉丝