webpack源码分析:打包规范

1,792 阅读3分钟

1. webpack简介

      webpack是当下最热门的前端资源模块化管理和打包工具。简单来说,通过指定入口文件,按照依赖和规则,将许多松散的模块打包成符合环境部署的前端资源。

     通过加载器(loader)、插件(plugin),webpack可以将任何形式的资源,比如 CommonJs 模块、AMD 模块、ES6 模块、CSS、图片、JSON、Coffeescript、LESS 等,转译成模块进行解析。

     

      面对工程中成百上千个模块,webpack究竟是如何将它们有序地组织在一起,并按照我们预想的顺序运行在浏览器上的呢?

接下来让我们带着这些问题来开始。首先模块之间是有依赖的,webpack是怎么有序组织起来的呢?

2. webpack如何处理模块依赖

      这里我就不赘叙了,直接上干货图理解(笔者呕心沥血花了近一周时间,边看源码边整理出来的,该源码版本为:webpack v5.21.1, webpack v4.5.0。后面版本迭代,部分函数名可能会有变动,但主流程思路和逻辑应该是不变的)。简单来说,webpack是利用compiler来控制整个编译流程,Compilation来专门编译构建,最后转交给compiler将文件打包输出到指定目录。

其中处理模块依赖的,主要是由Compilation addModuleTree触发,调用模块工厂ModuleFactory来完成解析。接下来,我们着重来看下它的编译打包部分。

3. 不同模块方法的打包及源码解析

首先我们打包一个amd规范的文件示例来看下。(所有示例代码均可在这里找到

准备一个最简单的js文件

// src/index.js
const logA = () =>console.log('hello webpack');export default logA;

修改webpack配置文件的入口为src/index.js

// webpack.config.js
...
entry: "./src/index.js",
...

修改webpack配置文件的打包方式为amd,

// webpack.prod.config.js  output: {    path: path.resolve(__dirname, "../dist"),    filename: 'amd.js',    library: "myDemo",    libraryTarget: 'amd'  },

// 打包生成的文件: 打包内容,最外层封装不同define("myDemo", [], (() => (() => {
    "use strict";
    var e = {
        352 : (e, r, o) = >{
            o.r(r),
            o.d(r, {
            default:
                () = >t
            });
            const t = function() {
                return console.log("hello webpack")
            }
        }
    },
    r = {};
    function o(t) {
        if (r[t]) return r[t].exports;
        var n = r[t] = {
            exports: {}
        };
        return e[t](n, n.exports, o),
        n.exports
    }
    return o.d = (e, r) =>{
        for (var t in r) o.o(r, t) && !o.o(e, t) && Object.defineProperty(e, t, {
            enumerable: !0,
            get: r[t]
        })
    },
    o.o = (e, r) => Object.prototype.hasOwnProperty.call(e, r),
    o.r = e => {
        "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
            value: "Module"
        }),
        Object.defineProperty(e, "__esModule", {
            value: !0
        })
    },
    o(352)
})()));

我们可以看到,设置不同的打包规范,最终webpack打包出来的文件模式都很固定模式。

再对照前面 工作机制的梳理图,compilation.seal调用后(也就是模块构建完成后),会调用create ChunkAssets(实际核心是调用template)将这些模块重组成js代码。

createChunkAssets(callback) {
   //...
   asyncLib.forEach(            this.chunks,            (chunk, callback) => {
               let manifest = this.getRenderManifest({...}) // 获取渲染列表               //...               asyncLib.forEach(                    manifest,                    (fileManifest, callback) => {
                       //...

                       let source = fileManifest.render(); // 开始渲染                       //...
                       this.emitAsset(file, source, assetInfo);  // 打包文件并输出到指定目录                       //...
                     
                    },
               callback);
             }, 
             callback);}

调用了fileManifest的render函数,其实就是mainTemplate。mainTemplate转交给template去处理,即调用Template.renderChunkModules,并返回了一个ConcatSource的资源。其中有固定的模板,也有调用的模块。

static renderChunkModules(renderContext, modules, renderModule, prefix = "") {
   // 1. 初始化source
    var source = new ConcatSource();

   // 2. 处理模块依赖更新allModules source,并渲染模块    const allModules = modules.map(module => {          return {                id: chunkGraph.getModuleId(module),                source: renderModule(module) || "false"            };     });
    // 3. 模板渲染source,这里简化显示
    source.add("{\n");    for (let i = 0; i < allModules.length; i++) {        const module = allModules[i];        if (i !== 0) {            source.add(",\n");        }        source.add(`\n/***/ ${JSON.stringify(module.id)}:\n`);        source.add(module.source);    }    source.add(`\n\n${prefix}}`);    return source; }

模块工厂(ModuleFactory)给module配备了generator, 生成替换代码。在generate阶段的时候,会触发请求RuntimeTemplate,用于替换运行时的代码。详情请参考该具体编译流程(原链接在这里

最后通过emitAssets输出到指定目录中文件中去。

4. 总结

嗯,其实webpack做的事情很多,源码也很绕,阅读得花费些功夫...初步了解大致工作流程后,下一阶段着重看下loader及plugin的调用处理模块之间依赖的部分。待续。。。(我先去喝杯茶,喘口气儿)

文中所提的代码规范示例仓库:github.com/JeanZhao/we…