专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
程序员的那些事  ·  GPU:DeepSeek ... ·  7 小时前  
OSC开源社区  ·  Bun ... ·  昨天  
程序员的那些事  ·  OpenAI ... ·  2 天前  
程序猿  ·  “未来 3 年内,Python 在 AI ... ·  5 天前  
51好读  ›  专栏  ›  SegmentFault思否

Webpack 详解

SegmentFault思否  · 公众号  · 程序员  · 2018-03-20 08:00

正文

webpack是现代前端开发中最火的模块打包工具,只需要通过简单的配置,便可以完成模块的加载和打包。那它是怎么做到通过对一些插件的配置,便可以轻松实现对代码的构建呢?

webpack的配置

  1. const path = require('path');

  2. module.exports = {

  3.  entry: "./app/entry", // string | object | array

  4.  // Webpack打包的入口

  5.  output: {  // 定义webpack如何输出的选项

  6.    path: path.resolve(__dirname, "dist"), // string

  7.    // 所有输出文件的目标路径

  8.    filename: "[chunkhash].js", // string

  9.    // 「入口(entry chunk)」文件命名模版

  10.    publicPath: "/assets/", // string

  11.    // 构建文件的输出目录

  12.    /* 其它高级配置 */

  13.   },

  14.  module: {  // 模块相关配置

  15.    rules: [ // 配置模块loaders,解析规则

  16.      {

  17.        test: /\.jsx?$/,  // RegExp | string

  18.        include: [ // 和test一样,必须匹配选项

  19.          path.resolve(__dirname, "app")

  20.        ],

  21.        exclude: [ // 必不匹配选项(优先级高于test和include)

  22.          path.resolve(__dirname, "app/demo-files")

  23.        ],

  24.        loader: "babel-loader", // 模块上下文解析

  25.        options: { // loader的可选项

  26.          presets : ["es2015"]

  27.        },

  28.      },

  29.  },

  30.  resolve: { //  解析模块的可选项

  31.    modules: [ // 模块的查找目录

  32.      "node_modules",

  33.      path.resolve(__dirname, "app")

  34.    ],

  35.    extensions: [".js", ".json", ".jsx", ".css"], // 用到的文件的扩展

  36.    alias: { // 模块别名列表

  37.      "module": "new-module"

  38.      },

  39.   },

  40.  devtool: "source-map", // enum

  41.  // 为浏览器开发者工具添加元数据增强调试

  42.  plugins: [ // 附加插件列表

  43.    // ...

  44.  ],

  45. }

从上面我们可以看到,webpack配置中需要理解几个核心的概念 Entry Output Loaders Plugins Chunk

  • Entry:指定webpack开始构建的入口模块,从该模块开始构建并计算出直接或间接依赖的模块或者库

  • Output:告诉webpack如何命名输出的文件以及输出的目录

  • Loaders:由于webpack只能处理javascript,所以我们需要对一些非js文件处理成webpack能够处理的模块,比如sass文件

  • Plugins: Loaders 将各类型的文件处理成webpack能够处理的模块, plugins 有着很强的能力。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。但也是最复杂的一个。比如对js文件进行压缩优化的 UglifyJsPlugin 插件

  • Chunk:coding split的产物,我们可以对一些代码打包成一个单独的chunk,比如某些公共模块,去重,更好的利用缓存。或者按需加载某些功能模块,优化加载时间。在webpack3及以前我们都利用 CommonsChunkPlugin 将一些公共代码分割成一个chunk,实现单独加载。在webpack4 中 CommonsChunkPlugin 被废弃,使用 SplitChunksPlugin

webpack详解

读到这里,或许你对webpack有一个大概的了解,那webpack 是怎么运行的呢?我们都知道,webpack是高度复杂抽象的插件集合,理解webpack的运行机制,对于我们日常定位构建错误以及写一些插件处理构建任务有很大的帮助。

不得不说的tapable

webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的 Compiler 和负责创建bundles的 Compilation 都是Tapable的实例。在Tapable1.0之前,也就是webpack3及其以前使用的Tapable,提供了包括:

  • plugin ( name : string , handler : function ) 注册插件到Tapable对象中

  • apply (… pluginInstances : ( AnyPlugin | function )[]) 调用插件的定义,将事件监听器注册到Tapable实例注册表中

  • applyPlugins *( name : string , …) 多种策略细致地控制事件的触发,包括 applyPluginsAsync applyPluginsParallel 等方法实现对事件触发的控制,实现

(1)多个事件连续顺序执行

(2)并行执行

(3)异步执行

(4)一个接一个地执行插件,前面的输出是后一个插件的输入的瀑布流执行顺序

(5)在允许时停止执行插件,即某个插件返回了一个 undefined 的值,即退出执行

我们可以看到,Tapable就像nodejs中 EventEmitter ,提供对事件的注册 on 和触发 emit ,理解它很重要,看个栗子:比如我们来写一个插件

  1. function CustomPlugin() {}

  2. CustomPlugin .prototype.apply = function(compiler) {

  3.  compiler.plugin('emit', pluginFunction);

  4. }

在webpack的生命周期中会适时的执行:

  1. this.apply*("emit",options)

当然上面提到的Tapable都是1.0版本之前的,如果想深入学习,可以查看Tapable和事件流(https://segmentfault.com/a/1190000008060440)。

那1.0的Tapable又是什么样的呢?1.0版本发生了巨大的改变,不再是此前的通过 plugin 注册事件,通过 applyPlugins * 触发事件调用,那1.0的Tapable是什么呢?

暴露出很多的钩子,可以使用它们为插件创建钩子函数

  1. const {

  2.    SyncHook,

  3.    SyncBailHook,

  4.    SyncWaterfallHook,

  5.    SyncLoopHook,

  6.    AsyncParallelHook,

  7.    AsyncParallelBailHook,

  8.    AsyncSeriesHook,

  9.     AsyncSeriesBailHook,

  10.    AsyncSeriesWaterfallHook

  11. } = require("tapable");

我们来看看怎么使用。

  1. class Order {

  2.    constructor() {

  3.        this.hooks = { //hooks

  4.            goods: new SyncHook(['goodsId', 'number']),

  5.            consumer: new AsyncParallelHook(['userId', 'orderId'])

  6.        }

  7.    }

  8.    queryGoods(goodsId, number) {

  9.         this.hooks.goods.call(goodsId, number);

  10.    }

  11.    consumerInfoPromise(userId, orderId) {

  12.        this.hooks.consumer.promise(userId, orderId).then(() => {

  13.            //TODO

  14.        })

  15.    }

  16.    consumerInfoAsync(userId, orderId) {

  17.        this.hooks.consumer.callAsync(userId, orderId, (err, data) => {

  18.            //TODO

  19.        })

  20.    }

  21. }

对于所有的hook的构造函数均接受一个可选的string类型的数组。

  1. const hook = new SyncHook(["arg1", "arg2", "arg3"]);

  1. // 调用tap方法注册一个consument

  2. order.hooks.goods.tap('QueryPlugin', (goodsId, number) => {

  3.    return fetchGoods(goodsId, number);

  4. })

  5. // 再添加一个

  6. order.hooks.goods.tap('LoggerPlugin', (goodsId, number) => {

  7.    logger(goodsId, number);

  8. })

  9. // 调用

  10. order.queryGoods('10000000', 1)

对于一个 SyncHook ,我们通过 tap 来添加消费者,通过 call 来触发钩子的顺序执行。

对于一个非 sync * 类型的钩子,即 async * 类型的钩子,我们还可以通过其它方式注册消费者和调用

  1. // 注册一个sync 钩子

  2. order.hooks.consumer.tap('LoggerPlugin', (userId, orderId) => {

  3.   logger(userId, orderId);

  4. })

  5. order .hooks.consumer.tapAsync('LoginCheckPlugin', (userId, orderId, callback) => {

  6.    LoginCheck(userId, callback);

  7. })

  8. order.hooks.consumer.tapPromise('PayPlugin', (userId, orderId) => {

  9.    return Promise.resolve();

  10. })

  11. // 调用

  12. // 返回Promise

  13. order.consumerInfoPromise('user007', '1024');

  14. //回调函数

  15. order .consumerInfoAsync('user007', '1024')

通过上面的栗子,你可能已经大致了解了 Tapable 的用法,它的用法:

  • 插件注册数量

  • 插件注册的类型(sync, async, promise)

  • 调用的方式(sync, async, promise)

  • 实例钩子的时候参数数量

  • 是否使用了 interception

Tapable详解

对于 Sync * 类型的钩子来说:

  • 注册在该钩子下面的插件的执行顺序都是顺序执行。

  • 只能使用 tap 注册,不能使用 tapPromise tapAsync 注册

  1. // 所有的钩子都继承于Hook

  2. class Sync* extends Hook {

  3.    tapAsync() { // Sync*类型的钩子不支持tapAsync

  4.        throw new Error("tapAsync is not supported on a Sync*");

  5.    }

  6.    tapPromise() {// Sync*类型的钩子不支持tapPromise

  7.         throw new Error("tapPromise is not supported on a Sync*");

  8.    }

  9.    compile(options) { // 编译代码来按照一定的策略执行Plugin

  10.        factory.setup(this, options);

  11.        return factory.create(options);

  12.    }

  13. }

对于 Async * 类型钩子:支持 tap tapPromise tapAsync 注册。

  1. class AsyncParallelHook extends Hook {

  2.    constructor(args) {

  3.        super(args);

  4.        this.call = this._call = undefined;

  5.    }

  6.    compile(options) {

  7.        factory.setup(this, options);

  8.        return factory.create(options);

  9.    }

  10. }

  1. class Hook {

  2.    constructor(args) {

  3.        if(!Array.isArray(args)) args = [];

  4.        this._args = args; // 实例钩子的时候的string类型的数组

  5.        this. taps = []; // 消费者

  6.        this.interceptors = []; // interceptors

  7.        this.call = this._call =  // 以sync类型方式来调用钩子

  8.        this._createCompileDelegate("call", "sync");

  9.        this.promise =

  10.        this._promise = // 以promise方式

  11.        this._createCompileDelegate("promise", "promise");

  12.        this.callAsync =

  13.        this._callAsync = // 以async类型方式来调用

  14.        this._createCompileDelegate("callAsync", "async" );

  15.        this._x = undefined; //

  16.    }

  17.    _createCall(type) {

  18.        return this.compile({

  19.            taps: this.taps,

  20.            interceptors: this.interceptors,

  21.            args: this._args,

  22.            type: type

  23.        });

  24.    }

  25.    _createCompileDelegate(name, type) {

  26.        const lazyCompileHook = (...args) => {

  27.            this[name] = this._createCall(type);

  28.            return this[name](...args);

  29.        };

  30.        return lazyCompileHook;

  31.    }

  32.    // 调用tap 类型注册

  33.    tap(options, fn) {

  34.        // ...

  35.        options = Object.assign({ type: "sync", fn: fn }, options);

  36.        // ...

  37.        this. _insert(options);  // 添加到 this.taps中

  38.    }

  39.    // 注册 async类型的钩子

  40.    tapAsync(options, fn) {

  41.        // ...

  42.        options = Object.assign({ type: "async", fn: fn }, options);

  43.        // ...

  44.        this._insert(options); // 添加到 this.taps中

  45.    }

  46.    注册 promise类型钩子

  47.    tapPromise(options, fn) {

  48.        // ...

  49.        options = Object.assign({ type: "promise", fn: fn }, options);

  50.        // ...

  51.        this._insert(options); // 添加到 this.taps中

  52.    }

  53. }

每次都是调用 tap tapSync tapPromise 注册不同类型的插件钩子,通过调用 call callAsync promise 方式调用。其实调用的时候为了按照一定的执行策略执行,调用 compile 方法快速编译出一个方法来执行这些插件。

  1. const factory = new Sync*CodeFactory();

  2. class Sync* extends Hook {

  3.    // ...

  4.    compile(options) { // 编译代码来按照一定的策略执行Plugin

  5.        factory .setup(this, options);

  6.        return factory.create(options);

  7.    }

  8. }

  9. class Sync*CodeFactory extends HookCodeFactory {

  10.    content({ onError, onResult, onDone, rethrowIfPossible }) {

  11.        return this.callTapsSeries({

  12.            onError: (i, err) => onError(err),

  13.            onDone,

  14.            rethrowIfPossible

  15.        });

  16.    }

  17. }

compile 中调用 HookCodeFactory #create 方法编译生成执行代码。

  1. class HookCodeFactory {

  2.    constructor(config) {

  3.        this.config = config;

  4.        this.options = undefined;

  5.    }

  6.    create(options) {

  7.        this.init(options);

  8.        switch(this.options.type) {

  9.            case "sync":  // 编译生成sync, 结果直接返回

  10.                 return new Function(this.args(),

  11.                "\"use strict\";\n" + this.header() + this.content({

  12.                    // ...

  13.                    onResult: result => `return ${result};\n`,

  14.                    // ...

  15.                }));

  16.            case "async": // async类型, 异步执行,最后将调用插件执行结果来调用callback,

  17.                return new Function(this.args({

  18.                    after: "_callback"

  19.                }), "\"use strict\";\n" + this.header() + this.content({

  20.                    // ...

  21.                    onResult: result => `_callback(null, ${result});\n`,

  22.                    onDone: () => "_callback();\n"

  23.                }));

  24.            case "promise": // 返回promise类型,将结果放在resolve中

  25.                // ...

  26.                code += "return new Promise((_resolve, _reject) => {\n";

  27.                code += "var _sync = true;\n";

  28.                code += this.header();

  29.                code += this.content({

  30.                    // ...

  31.                    onResult: result => `_resolve(${result});\n`,

  32.                    onDone: () => "_resolve();\n"

  33.                });

  34.                // ...

  35.                return new Function(this.args(), code);

  36.        }

  37.    }

  38.    // callTap 就是执行一些插件,并将结果返回

  39.    callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {

  40.        let code = "";

  41.        let hasTapCached = false;

  42.        // ...

  43.        code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;

  44.        const tap = this.options.taps[tapIndex];

  45.        switch(tap.type) {

  46.            case "sync":

  47.                // ...

  48.                if(onResult) {

  49.                    code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({

  50.                        before: tap.context ? "_context" : undefined

  51.                     })});\n`;

  52.                } else {

  53.                    code += `_fn${tapIndex}(${this.args({

  54.                        before: tap.context ? "_context" : undefined

  55.                    })});\n`;

  56.                }

  57.                if(onResult) { // 结果透传

  58.                    code += onResult(`_result${tapIndex}`);

  59.                }

  60.                if(onDone) { // 通知插件执行完毕,可以执行下一个插件

  61.                    code += onDone ();

  62.                }

  63.                break;

  64.            case "async": //异步执行,插件运行完后再将结果通过执行callback透传

  65.                let cbCode = "";

  66.                if(onResult)

  67.                    cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;

  68.                else

  69.                    cbCode += `_err${tapIndex} => {\n`;

  70.                cbCode += `if(_err${tapIndex}) {\n`;

  71.                cbCode += onError(`_err${tapIndex }`);

  72.                cbCode += "} else {\n";

  73.                if(onResult) {

  74.                    cbCode += onResult(`_result${tapIndex}`);

  75.                }

  76.                cbCode += "}\n";

  77.                cbCode += "}";

  78.                code += `_fn${tapIndex}(${this.args({

  79.                    before: tap.context ? "_context" : undefined,

  80.                    after: cbCode //cbCode将结果透传

  81.                })});\n`;

  82.                break;

  83.            case "promise": // _fn${tapIndex} 就是第tapIndex 个插件,它必须是个Promise类型的插件

  84.                code += `var _hasResult${tapIndex} = false;\n`;

  85.                code += `_fn${tapIndex}(${this.args({

  86.                    before: tap.context ? "_context" : undefined

  87.                })}).then(_result${tapIndex} => {\n`;

  88.                code += `_hasResult${tapIndex} = true;\n`;

  89.                if(onResult) {

  90.                    code += onResult(`_result${tapIndex}`);

  91.                }

  92.            // ...

  93.                break;

  94.        }

  95.        return code;

  96.    }

  97.    // 按照插件的注册顺序,按照顺序递归调用执行插件

  98.    callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) {

  99.        // ...

  100.        const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");

  101.        const next = i => {

  102.            // ...

  103.             const done = () => next(i + 1);

  104.            // ...

  105.            return this.callTap(i, {

  106.                // ...

  107.                onResult: onResult && ((result) => {

  108.                    return onResult(i, result, done, doneBreak);

  109.                }),

  110.                // ...

  111.            });

  112.        };

  113.        return next(0);

  114.    }

  115.    callTapsLooping({ onError, onDone, rethrowIfPossible }) {

  116.        const syncOnly = this.options.taps.every(t => t.type === "sync");

  117.        let code = "";

  118.        if(!syncOnly) {

  119.            code += "var _looper = () => {\n";

  120.            code += "var _loopAsync = false;\n";

  121.        }

  122.        code += "var _loop;\n";

  123.        code += "do {\n";

  124.        code += "_loop = false;\n";

  125.         // ...

  126.        code += this.callTapsSeries({

  127.            // ...

  128.            onResult: (i, result, next, doneBreak) => { // 一旦某个插件返回不为undefined,  即一只调用某个插件执行,如果为undefined,开始调用下一个

  129.                let code = "";

  130.                code += `if(${result} !== undefined) {\n`;

  131.                code += "_loop = true;\n";

  132.                if(!syncOnly)

  133.                    code += "if(_loopAsync) _looper();\n";

  134.                code += doneBreak(true);

  135.                code += `} else {\n`;

  136.                code += next();

  137.                code += `}\n`;

  138.                return code;

  139.            },

  140.            // ...

  141.        })

  142.        code += "} while(_loop);\n";

  143.        // ...

  144.        return code;

  145.    }

  146.    // 并行调用插件执行

  147.    callTapsParallel({ onError, onResult, onDone, rethrowIfPossible, onTap = (i, run) => run() }) {

  148.         // ...

  149.        // 遍历注册都所有插件,并调用

  150.        for(let i = 0; i < this.options.taps.length; i++) {

  151.            // ...

  152.            code += "if(_counter <= 0) break;\n";

  153.            code += onTap(i, () => this.callTap(i, {

  154.                // ...

  155.                onResult: onResult && ((result) => {

  156.                    let code = "";

  157.                    code += "if(_counter > 0) {\n";

  158.                    code += onResult(i, result, done, doneBreak);

  159.                    code += "}\n";

  160.                    return code;

  161.                }),

  162.                // ...

  163.            }), done, doneBreak);

  164.        }

  165.        // ...

  166.        return code;

  167.    }

  168. }

HookCodeFactory #create 中调用到 content 方法,此方法将按照此钩子的执行策略,调用不同的方法来执行编译 生成最终的代码。

SyncHook中调用 callTapsSeries 编译生成最终执行插件的函数, callTapsSeries 做的就是将插件列表中插件按照注册顺序遍历执行。

  1. class SyncHookCodeFactory extends HookCodeFactory {

  2.    content({ onError , onResult, onDone, rethrowIfPossible }) {

  3.        return this.callTapsSeries({

  4.            onError: (i, err) => onError(err),

  5.            onDone,

  6.            rethrowIfPossible

  7.        });

  8.    }

  9. }

SyncBailHook中当一旦某个返回值结果不为 undefined 便结束执行列表中的插件。

  1. class SyncBailHookCodeFactory extends HookCodeFactory {

  2.    content({ onError, onResult, onDone, rethrowIfPossible }) {

  3.        return this.callTapsSeries({

  4.            // ...

  5.            onResult : (i, result, next) => `if(${result} !== undefined) {\n${onResult(result)};\n} else {\n${next()}}\n`,

  6.            // ...

  7.        });

  8.    }

  9. }

SyncWaterfallHook中上一个插件执行结果当作下一个插件的入参。

  1. class SyncWaterfallHookCodeFactory extends HookCodeFactory {

  2.    content({ onError, onResult, onDone, rethrowIfPossible }) {

  3.        return this.callTapsSeries({

  4.            // ...

  5.            onResult: (i, result, next) => {

  6.                let code = "";

  7.                code += `if(${result} !== undefined) {\n`;

  8.                code += `${this._args[0]} = ${result};\n`;

  9.                code += `}\n`;

  10.                code += next();

  11.                return code;

  12.            },

  13.            onDone: () => onResult( this._args[0]),

  14.        });

  15.    }

  16. }

  • AsyncParallelHook调用 callTapsParallel 并行执行插件

  1. class AsyncParallelHookCodeFactory extends HookCodeFactory {

  2.    content({ onError, onDone }) {

  3.        return this.callTapsParallel({

  4.            onError: (i, err, done, doneBreak) => onError(err) + doneBreak(true),

  5.            onDone

  6.        });

  7.    }

  8. }

webpack流程篇

本文关于webpack 的流程讲解是基于webpack4的。

webpack 入口文件

从webpack项目的package.json文件中我们找到了入口执行函数,在函数中引入webpack,那么入口将会是 lib / webpack . js ,而如果在shell中执行,那么将会走到 . /bin/ webpack . js ,我们就以 lib / webpack . js 为入口开始吧!

  1. {

  2.  "name": "webpack",

  3.  "version": "4.1.1",

  4.  ...

  5.  "main": "lib/webpack.js",

  6.  "web": "lib/webpack.web.js",

  7.  "bin": "./bin/webpack.js",

  8.  ...

  9.  }

webpack入口
  1. const webpack = (options, callback) => {

  2.    // ...

  3.    // 验证options正确性

  4.    // 预处理options

  5.    options = new WebpackOptionsDefaulter().process(options); // webpack4的默认配置

  6.    compiler = new Compiler(options.context); // 实例Compiler

  7.    // ...

  8.    // 若options.watch === true && callback 则开启watch线程

  9.    compiler.watch(watchOptions, callback);

  10.    compiler.run(callback);

  11.    return compiler;

  12. };

webpack 的入口文件其实就实例了 Compiler 并调用了 run 方法开启了编译,webpack的编译都按照下面的钩子调用顺序执行:

  • before-run 清除缓存

  • run 注册缓存数据钩子

  • before-compile

  • compile 开始编译

  • make 从入口分析依赖以及间接依赖模块,创建模块对象

  • build-module 模块构建

  • seal 构建结果封装, 不可再更改

  • after-compile 完成构建,缓存数据

  • emit 输出到dist目录

编译&构建流程

webpack中负责构建和编译都是 Compilation

  1. class Compilation extends Tapable {

  2.    constructor(compiler) {

  3.        super();

  4.        this.hooks = {

  5.            // hooks

  6.        };

  7.        // ...

  8.        this.compiler = compiler;

  9.        // ...

  10.        // template

  11.        this.mainTemplate = new MainTemplate(this.outputOptions );

  12.        this.chunkTemplate = new ChunkTemplate(this.outputOptions);

  13.        this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(

  14.            this.outputOptions

  15.        );

  16.        this.runtimeTemplate = new RuntimeTemplate(

  17.            this.outputOptions,

  18.            this.requestShortener

  19.        );

  20.        this.moduleTemplates = {

  21.            javascript: new ModuleTemplate(this.runtimeTemplate),

  22.            webassembly: new ModuleTemplate(this.runtimeTemplate)

  23.        };

  24.        // 构建生成的资源

  25.        this.chunks = [];

  26.        this.chunkGroups = [];

  27.        this.modules = [];

  28.        this.additionalChunkAssets = [];

  29.        this.assets = {};

  30.        this.children = [];

  31.        // ...

  32.    }

  33.    //

  34.    buildModule(module, optional, origin, dependencies, thisCallback) {

  35.        // ...

  36.        // 调用module.build方法进行编译代码,build中 其实是利用acorn编译生成AST

  37.        this.hooks.buildModule.call(module);

  38.        module.build(/**param*/);

  39.    }

  40.    // 将模块添加到列表中,并编译模块

  41.    _addModuleChain(context, dependency, onModule, callback) {

  42.            // ...

  43.            // moduleFactory.create创建模块,这里会先利用loader处理文件,然后生成模块对象

  44.            moduleFactory.create(

  45.                {

  46.                    contextInfo: {

  47.                        issuer: "",

  48.                        compiler: this.compiler.name

  49.                    },

  50.                    context: context,

  51.                    dependencies: [dependency]

  52.                },

  53.                (err, module) => {

  54.                    const addModuleResult = this.addModule(module);

  55.                    module = addModuleResult.module;

  56.                    onModule(module);

  57.                    dependency.module = module;

  58.                    // ...

  59.                    // 调用buildModule编译模块

  60.                    this.buildModule(module, false, null, null, err => {});

  61.                }

  62.        });

  63.    }

  64.    // 添加入口模块,开始编译&构建

  65.    addEntry(context, entry, name, callback) {

  66.        // ...

  67.        this._addModuleChain( // 调用_addModuleChain添加模块

  68.            context,

  69.            entry,

  70.            module => {

  71.                this.entries.push(module);

  72.            },

  73.            // ...

  74.         );

  75.    }

  76.    seal(callback) {

  77.        this.hooks.seal.call();

  78.        // ...

  79.        const chunk = this.addChunk(name);

  80.        const entrypoint = new Entrypoint(name);

  81.        entrypoint.setRuntimeChunk(chunk);

  82.        entrypoint.addOrigin(null, name, preparedEntrypoint.request);

  83.        this.namedChunkGroups.set(name, entrypoint);

  84.        this.entrypoints.set(name, entrypoint);

  85.        this.chunkGroups.push(entrypoint);

  86.        GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);

  87.        GraphHelpers.connectChunkAndModule(chunk, module);

  88.        chunk.entryModule = module;

  89.        chunk







请到「今天看啥」查看全文