美文网首页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