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

CodeCipherPro
• 阅读 4288

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);
            // 如果需要额外的编译,上次的没编译完成吗😰
            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) 是个高阶函数,可以简单的理解为参数是函数,并且返回一个函数

撒花🌹 🎉🍀我要买瓶skii犒赏自己

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Souleigh ✨ Souleigh ✨
4年前
JavaScript 和 Node.js 中事件循环
1.JavaScript中事件循环可以参考《JavaScript忍者秘籍第二版》第十三章,讲解的很好。JavaScript中事件循环,主要就在理解宏任务和微任务这两种异步任务。宏任务(macrotask):setTimeOut、setInterval、setImmediate、I/O、各种callback、UI渲染、messageCh
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
明月 明月
3年前
记录微信分享方法
1.安装weixinjssdk(目前使用的是1.6.0)2.新建一个js文件,在main.js中引入3.封装方法wxShare.sharefunction(appId,timestamp,nonceStr,signature,options,callBack)console.log('9999',appId,timestamp
Stella981 Stella981
3年前
LinkedIn Authenticating OAuth 2.0
1\.获取授权码GET:https://www.linkedin.com/uas/oauth2/authorization?response\_typecode&client\_id75m3fi6kmh3k3z&redirect\_urihttp://192.168.31.210:3001/callback&state123
Wesley13 Wesley13
3年前
Java爬虫之JSoup使用教程
title:Java爬虫之JSoup使用教程date:201812248:00:000800update:201812248:00:000800author:mecover:https://imgblog.csdnimg.cn/20181224144920712(https://www.oschin
Stella981 Stella981
3年前
JavaScript:再谈Tasks和Microtasks
JavaScript是单线程,也就是说JS的堆栈中只允许有一类任务在执行,不可以同时执行多类任务。在读js文件时,所有的同步任务是一条task,当然了,每一条task都是一个队列,按顺序执行。而如果在中途遇到了setTimeout这种异步任务,就会将它挂起,放到任务队列中去执行,等执行完毕后,如果有callback,就把callback推入到Tasks中去,
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Stella981 Stella981
3年前
C++中Lambada表达式种隐式捕获this指针的隐患问题
include<iostreaminclude<functionalusingnamespacestd;classDispatcher{private:std::function<void()callback_;public:void
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(