美文网首页Front End
[FE] webpack v4.20.2 源码解析(四):生成资

[FE] webpack v4.20.2 源码解析(四):生成资

作者: 何幻 | 来源:发表于2018-10-17 12:25 被阅读4次

1. 简单回顾

这里我们先简单回顾下webpack载入资源的过程,
(1)命令行调用webpack,
即调用了~/.nvm/versions/node/v8.12.0/bin/webpack,原身在这,

~/.nvm/versions/node/v8.12.0/lib/node_modules/webpack/bin/webpack.js

(2)webpack.js用require载入了webpack-cli,

require(path.resolve(
    path.dirname(pkgPath),
    pkg.bin[installedClis[0].binName]
));
~/.nvm/versions/node/v8.12.0/lib/node_modules/webpack-cli/bin/cli.js

(3)cli.js中加载了模块webpack,然后调用它
它会返回一个compiler,然后再调用compiler.run。

const webpack = require("webpack");
compiler = webpack(options);
compiler.run(compilerCallback);

(4)compiler.run的实现,在webpack模块的Compiler.js文件中
compiler.run调用了,this.compile(onCompiled);
compiler方法调用了,this.hooks.make.callAsync

this.hooks.make.callAsync(compilation, err => {
    ...
});

(5)hooks.make的实现在webpack模块的SingleEntryPlugin.js文件中
它调用了compilation.addEntry,实现位于Compilation.js

compiler.hooks.make.tapAsync(
    "SingleEntryPlugin",
    (compilation, callback) => {
        const { entry, name, context } = this;

        const dep = SingleEntryPlugin.createDependency(entry, name);
        compilation.addEntry(context, dep, name, callback);
    }
);

(6)compilation.addEntry调用了this._addModuleChain
用于加载入口文件,及其相关的所有依赖(包括依赖的依赖),
全部加载完了之后,会进入this._addModuleChain的回调。

this._addModuleChain(
    context,
    entry,
    module => {
        this.entries.push(module);
    },
    (err, module) => {
        ...
    }
);

好了,本文从_addModuleChain的回调开始说起。

2. 完成make

_addModuleChain完成之后,模块就都加载完了,
分别调用了各种相应的loader把文件都加载到了内存中。

由于调用链路上,
hooks.make调用了compilation.addEntry,再调用了this._addModuleChain,
因此,this._addModuleChain完成之后,实际上导致hooks.make结束了。

于是,流程走到了Compiler.js 第537行

this.hooks.make.callAsync(compilation, err => {
    log('end: make');
    if (err) return callback(err);

3. compilation.seal

完成make之后,webpack会进行下一个比较重要的环节,就是compilatin.seal
这个过程中,它会生成所有的代码(只是还未写入文件中)。

compilation.seal在这里实现,Compilation.js 第1159行

seal的执行过程中,调用了一堆hooks,例如,

this.hooks.reviveModules.call(this.modules, this.records);
this.hooks.optimizeModuleOrder.call(this.modules);
this.hooks.advancedOptimizeModuleOrder.call(this.modules);
this.hooks.beforeModuleIds.call(this.modules);
this.hooks.moduleIds.call(this.modules);
this.applyModuleIds();
this.hooks.optimizeModuleIds.call(this.modules);
this.hooks.afterOptimizeModuleIds.call(this.modules);

4. createChunkAssets

其中生成代码的环节位于Complation.js 第1270行

this.createChunkAssets();

它的实现位于Compilation.js 第2313行

并在Compilation.js 第2393行,将生成的代码,保存到了compilation.assets对象中。

this.assets[file] = source;

5. 代码是如何生成的

下面我们来跟踪source,看看它是如何生成的,
对代码进行调试后,我们发现,source来源在这里 Compilation.js 第2369行

source = fileManifest.render();

这个render的实现在JavascriptModulesPlugin.js中第64行

result.push({
    render: () =>
        compilation.mainTemplate.render(
            hash,
            chunk,
            moduleTemplates.javascript,
            dependencyTemplates
        ),
    filenameTemplate,
    pathOptions: {
        noChunkHash: !useChunkHash,
        contentHashType: "javascript",
        chunk
    },
    identifier: `chunk${chunk.id}`,
    hash: useChunkHash ? chunk.hash : fullHash
});

6. fileManifest.render调用compilation.mainTemplate.render来生成代码

具体实现在MainTemplate.js 第405行

其中调用了,hooks.render生成代码,

let source = this.hooks.render.call(
    new OriginalSource(
        Template.prefix(buf, " \t") + "\n",
        "webpack/bootstrap"
    ),
    chunk,
    hash,
    moduleTemplate,
    dependencyTemplates
);

注:
hooks.render的实现在MainTemplate.js 第148行
其中又调用了hooks.modules,

而hooks.modules的实现在JavascriptModulesPlugin.js 第86行
其中接着调用了,Template.renderChunkModules。

7. compilation.assets创建完成

compilation.assets创建完成之后,在经历seal中一系列hooks,
就完成了seal操作。

seal完成后,compile就完成了,会导致this.compile的回调被调用。

compilation.seal(err => {
    if (err) return callback(err);

    this.hooks.afterCompile.callAsync(compilation, err => {
        if (err) return callback(err);

        return callback(null, compilation);
    });
});

位于Compiler.js 第547行

8. onCompiled

this.compile是在Compiler.js 第268行被调用的。

this.hooks.beforeRun.callAsync(this, err => {
    if (err) return finalCallback(err);

    this.hooks.run.callAsync(this, err => {
        if (err) return finalCallback(err);

        this.readRecords(err => {
            if (err) return finalCallback(err);

            this.compile(onCompiled);
        });
    });
});

而onCompiled函数中生成了文件。

9. runAsChild

值得注意是,compile方法并不只是由Compiler.js 第268行调用。
还有可能在其他webpack插件中,通过compiler.runAsChild来调用。

例如,extract-text-webpack-plugin,会调用runAsChild,

childCompiler.runAsChild((err, entries, compilation) => {

这会导致包含extract-text-webpack-plugin的webpack项目调试起来非常困难。
因为插件中也进行了this.compile调用。

相关文章

网友评论

    本文标题:[FE] webpack v4.20.2 源码解析(四):生成资

    本文链接:https://www.haomeiwen.com/subject/tnblzftx.html