浅析webpack源码之Compiler.js模块(八)

栏目: JavaScript · 发布时间: 7年前

内容简介:最终返回了compiler下面暴露了一些插件再通俗一点的解释:

webpack.js小尾巴

const webpack = (options, callback) => {
    //... 
    if (callback) {
        if (typeof callback !== "function") {
            throw new Error("Invalid argument: callback");
        }
        if (
            options.watch === true ||
            (Array.isArray(options) && options.some(o => o.watch))
        ) {
            const watchOptions = Array.isArray(options)
                ? options.map(o => o.watchOptions || {})
                : options.watchOptions || {};
            return compiler.watch(watchOptions, callback);
        }
        compiler.run(callback);
    }
    return compiler;
}

最终返回了compiler

exports.version = version;

// ...属性挂载,把引入的函数模块全部暴露出来
webpack.WebpackOptionsDefaulter = WebpackOptionsDefaulter;
webpack.WebpackOptionsApply = WebpackOptionsApply;
webpack.Compiler = Compiler;
webpack.MultiCompiler = MultiCompiler;
webpack.NodeEnvironmentPlugin = NodeEnvironmentPlugin;
// @ts-ignore Global @this directive is not supported
webpack.validate = validateSchema.bind(this, webpackOptionsSchema);
webpack.validateSchema = validateSchema;
webpack.WebpackOptionsValidationError = WebpackOptionsValidationError;

下面暴露了一些插件

const exportPlugins = (obj, mappings) => {
    for (const name of Object.keys(mappings)) {
        Object.defineProperty(obj, name, {
            configurable: false,
            enumerable: true,
            get: mappings[name]
        });
    }
};

exportPlugins(exports, {
    AutomaticPrefetchPlugin: () => require("./AutomaticPrefetchPlugin"),
    BannerPlugin: () => require("./BannerPlugin"),
    CachePlugin: () => require("./CachePlugin")}
)

再通俗一点的解释:

比如当你api.AutomaticPrefetchPlugin你能

调用AutomaticPrefetchPlugin文件下的方法

这个和上面的不同在于上面的是挂在webpack函数对象上的

Compiler.js正题

要想理解Compiler.js

必须要理解tapable

再写一遍 git地址

我们先简单的理解它为一个通过tap 注册插件

call是run插件的事件流,里面还有一些异步的操作

Compiler整理如下

class Compiler extends Tapable {
    constructor(context) {
        super();
        this.hooks = {
            //罗列一些出现频率比较高的
            shouldEmit: new SyncBailHook(["compilation"]),
            done: new AsyncSeriesHook(["stats"]),
            beforeRun: new AsyncSeriesHook(["compiler"]),
            run: new AsyncSeriesHook(["compiler"]),
            emit: new AsyncSeriesHook(["compilation"]),
            afterEmit: new AsyncSeriesHook(["compilation"]),
            thisCompilation: new SyncHook(["compilation", "params"]),
            compilation: new SyncHook(["compilation", "params"]),
            beforeCompile: new AsyncSeriesHook(["params"]),
            compile: new SyncHook(["params"]),
            make: new AsyncParallelHook(["compilation"]),
            afterCompile: new AsyncSeriesHook(["compilation"]),
            watchRun: new AsyncSeriesHook(["compiler"]),
            //...
            
        }
        // 添加事件流
        this._pluginCompat.tap("Compiler", options => {
            switch (options.name) {
                case "additional-pass":
                case "before-run":
                case "run":
                case "emit":
                case "after-emit":
                case "before-compile":
                case "make":
                case "after-compile":
                case "watch-run":
                    options.async = true;
                    break;
            }
        });
        
    }
    watch(){
           //...
    }
    
    run(callback) {
        //... 
    }
    // 清除输入文件系统
    purgeInputFileSystem() {
        if (this.inputFileSystem && this.inputFileSystem.purge) {
            this.inputFileSystem.purge();
        }
    }
    emitAssets(compilation, callback) {
        //...
    }
    createChildCompiler(
        compilation,
        compilerName,
        compilerIndex,
        outputOptions,
        plugins
    ) {
        //...
    
        
    }
    //...
    compile(callback){
        //...
    }    
}

和tapable用法一个模子里刻出来的,我们仔细的研究run函数

compiler.js是webpack 的核心

run(callback) {
   //如果正在running,返回报错处理
    if (this.running) return callback(new ConcurrentCompilationError());
    
    //running调用finalCallback 
    const finalCallback = (err, stats) => {
        this.running = false;

        if (callback !== undefined) return callback(err, stats);
    };
    //记录初始化running时间
    const startTime = Date.now();
    //设置running标志,防止多次running
    this.running = true;
    
    //正在编译
    const onCompiled = (err, compilation) => {
        //如果报错,编译结束
        if (err) return finalCallback(err);

        //如果没有编译完成
        if (this.hooks.shouldEmit.call(compilation) === false) {
             // Stats模块有1400行,
             // compiler.js是webpack 的核心,new Stats(compilation)是compiler.js的核心
            const stats = new Stats(compilation);
             //    stats对象挂载startTime,endTime 
            stats.startTime = startTime;
            stats.endTime = Date.now();
            // 异步调用完成事件流,running结束
            this.hooks.done.callAsync(stats, err => {
                if (err) return finalCallback(err);
                return finalCallback(null, stats);
            });
            return;
        }
        // 调用emitAsset方法,emitAsset主要负责写入文件输出文件,不影响我们先看编译
        this.emitAssets(compilation, err => {
            // 类似同上, 如果报错,编译结束
            if (err) return finalCallback(err);
            // 如果需要额外的编译,上次的没编译完成吗:cold_sweat:
            if (compilation.hooks.needAdditionalPass.call()) {
                compilation.needAdditionalPass = true;
                //再来一遍new Stats
                const stats = new Stats(compilation);
                stats.startTime = startTime;
                stats.endTime = Date.now(); 
                //继续异步调用时间流
                this.hooks.done.callAsync(stats, err => {
                    if (err) return finalCallback(err);
                    //  这次多了一个时间流,调用额外编译,告知编译终于编完了
                    this.hooks.additionalPass.callAsync(err => {
                        if (err) return finalCallback(err);
                        //调用compile,把onCompiled的返回值传入compile函数,onCompiled的返回值也就是new Stats的对象
                        this.compile(onCompiled);
                    });
                });
                return;
            }
            // 如果都不走上面的分支,即编译完成,不需要额外的编译
            // 调用emitRecords方法,主要用来记录输出的
            this.emitRecords(err => {
                if (err) return finalCallback(err);
                // 同样new Stats
                const stats = new Stats(compilation);
                stats.startTime = startTime;
                stats.endTime = Date.now();
                // 异步调用完成事件
                this.hooks.done.callAsync(stats, err => {
                    if (err) return finalCallback(err);
                    return finalCallback(null, stats);
                });
            });
            //最终总结,无论走那个分支都是 new Stats(compilation) 返回stats的回调函数,按照目前的流程走的是最后一个分支,调用 this.emitRecords
        });
    };
    
    // 调用beforeRun钩子
    this.hooks.beforeRun.callAsync(this, err => {
        if (err) return finalCallback(err);
        // 调用run钩子
        this.hooks.run.callAsync(this, err => {
            if (err) return finalCallback(err);
            //读文件记录
            this.readRecords(err => {
                if (err) return finalCallback(err);
                //把onCompiled函数传入,调用compile
                this.compile(onCompiled);
            });
        });
    });
}

new Stats(compilation)是compiler.js的核心

compilation和Stats分别对应两个模块

compilation.js 2500行,Stats.js 1400行

接下来我们看一下compile函数

newCompilationParams() {
        const params = {
            // 普通模块工厂
            normalModuleFactory: this.createNormalModuleFactory(),
            // 上下文模块工厂
            contextModuleFactory: this.createContextModuleFactory(),
            // 编译依赖
            compilationDependencies: new Set()
        };
        return params;
    }


compile(callback) {
    // params值如下
    const params = this.newCompilationParams();
    // 异步调用beforeCompile钩子
    this.hooks.beforeCompile.callAsync(params, err => {
        if (err) return callback(err);
        // 调用compile钩子
        this.hooks.compile.call(params);
        // 终于出现了compilation,之前一直没有讲了compilation是什么,newCompilation我们看如下函数
        const compilation = this.newCompilation(params);

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

            compilation.finish();

            compilation.seal(err => {
                if (err) return callback(err);
                // 异步调用afterCompile,返回回调函数
                this.hooks.afterCompile.callAsync(compilation, err => {
                    if (err) return callback(err);

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

newCompilation(params) {
   // compilation 是 this.createCompilation(),继续往下
    const compilation = this.createCompilation();
    //给compilation对象挂载属性
    compilation.fileTimestamps = this.fileTimestamps;
    compilation.contextTimestamps = this.contextTimestamps;
    compilation.name = this.name;
    compilation.records = this.records;
    compilation.compilationDependencies = params.compilationDependencies;
    //告知钩子调用完毕
    this.hooks.thisCompilation.call(compilation, params);
    this.hooks.compilation.call(compilation, params);
    return compilation;
}

createCompilation() {
   // 原来compilation 是 newCompilation而来,Compilation一共2500行,堪称整个compiler.js的核心
    return new Compilation(this);
}

params如下

{ normalModuleFactory:
   NormalModuleFactory {
     _pluginCompat:
      SyncBailHook {
      // key是tapable 方法名
        _args: [Array],
        taps: [Array],
        interceptors: [],
        call: [Function: lazyCompileHook],
        promise: [Function: lazyCompileHook],
        callAsync: [Function: lazyCompileHook],
        _x: undefined },
     hooks:
      { resolver: [SyncWaterfallHook],
        factory: [SyncWaterfallHook],
        beforeResolve: [AsyncSeriesWaterfallHook],
        afterResolve: [AsyncSeriesWaterfallHook],
        createModule: [SyncBailHook],
        module: [SyncWaterfallHook],
        createParser: [HookMap],
        parser: [HookMap],
        createGenerator: [HookMap],
        generator: [HookMap] },
     resolverFactory:
      ResolverFactory {
        _pluginCompat: [SyncBailHook],
        hooks: [Object],
        cache1: [WeakMap],
        cache2: Map {} },
     ruleSet: RuleSet { references: {}, rules: [Array] },
     cachePredicate: [Function: bound Boolean],
     //文件路径
     context: '/Users/orion/Desktop/react-beauty-highcharts',
     parserCache: {},
     generatorCache: {} },
  contextModuleFactory:
   ContextModuleFactory {
     _pluginCompat:
      SyncBailHook {
        _args: [Array],
        taps: [Array],
        interceptors: [],
        call: [Function: lazyCompileHook],
        promise: [Function: lazyCompileHook],
        callAsync: [Function: lazyCompileHook],
        _x: undefined },
     hooks:
      { beforeResolve: [AsyncSeriesWaterfallHook],
        afterResolve: [AsyncSeriesWaterfallHook],
        contextModuleFiles: [SyncWaterfallHook],
        alternatives: [AsyncSeriesWaterfallHook] },
     resolverFactory:
      ResolverFactory {
        _pluginCompat: [SyncBailHook],
        hooks: [Object],
        cache1: [WeakMap],
        cache2: Map {} } },
  compilationDependencies: Set {} }

终极总结

  • Compiler构造函数 ->
  • run方法 ->
  • this.compile(onCompiled) ->
  • this.compile()执行有了compilation ->
  • onCompiled执行 const stats = new Stats(compilation)
  • 最后返回 finalCallback(null, stats);

this.compile(onCompiled) 是个高阶函数,可以简单的理解为参数是函数,并且返回一个函数

撒花:rose: :tada::four_leaf_clover:我要买瓶skii犒赏自己


以上所述就是小编给大家介绍的《浅析webpack源码之Compiler.js模块(八)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Math Adventures with Python

Math Adventures with Python

Peter Farrell / No Starch Press / 2018-11-13 / GBP 24.99

Learn math by getting creative with code! Use the Python programming language to transform learning high school-level math topics like algebra, geometry, trigonometry, and calculus! In Math Adventu......一起来看看 《Math Adventures with Python》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具