Analysis of Compiler.js module of webpack source code

Keywords: Javascript Webpack Attribute git React

webpack.js little tail

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;
}

Finally, the compiler is returned

  
exports.version = version;

// ... attribute mount, exposing all the introduced function modules
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;

Some plug-ins are exposed below

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")}
)

Another popular explanation:

For example, when you api.AutomaticPrefetchPlugin, you can
Call the method under the automatic prefetchplugin file

The difference between this and the above is that the above is on the webback function object

Compiler.js

To understand Compiler.js
Must understand tapable
Write it again. git address

Let's simply understand it as a plug-in registered through tap
call is the event flow of the run plug-in. There are some asynchronous operations in it

The Compiler is organized as follows

class Compiler extends Tapable {
    constructor(context) {
        super();
        this.hooks = {
            //List some of the most frequent
            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"]),
            //...
            
        }
        // Add event flow
        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) {
        //... 
    }
    // Clear input file system
    purgeInputFileSystem() {
        if (this.inputFileSystem && this.inputFileSystem.purge) {
            this.inputFileSystem.purge();
        }
    }
    emitAssets(compilation, callback) {
        //...
    }
    createChildCompiler(
        compilation,
        compilerName,
        compilerIndex,
        outputOptions,
        plugins
    ) {
        //...
    
        
    }
    //...
    compile(callback){
        //...
    }    
}

We carefully study the run function, which is carved from the template of tapable usage

compiler.js is the core of webpack

run(callback) {
   //If running, return error handling
    if (this.running) return callback(new ConcurrentCompilationError());
    
    //running calls finalCallback 
    const finalCallback = (err, stats) => {
        this.running = false;

        if (callback !== undefined) return callback(err, stats);
    };
    //Record the initialization running time
    const startTime = Date.now();
    //Set the running flag to prevent multiple running
    this.running = true;
    
    //Being compiled
    const onCompiled = (err, compilation) => {
        //If an error is reported, compilation ends
        if (err) return finalCallback(err);

        //If the compilation is not completed
        if (this.hooks.shouldEmit.call(compilation) === false) {
             // The Stats module has 1400 lines,
             // Compiler.js is the core of webpack, and new Stats(compilation) is the core of compiler.js
            const stats = new Stats(compilation);
             //    stats object mount startTime, endTime 
            stats.startTime = startTime;
            stats.endTime = Date.now();
            // Asynchronous call completes event flow, running ends
            this.hooks.done.callAsync(stats, err => {
                if (err) return finalCallback(err);
                return finalCallback(null, stats);
            });
            return;
        }
        // Call the emitAsset method. emitAsset is mainly responsible for writing the output file of the file, which does not affect the compilation
        this.emitAssets(compilation, err => {
            // Similar to the above, if an error is reported, the compilation ends
            if (err) return finalCallback(err);
            // If additional compilation is needed, did not the last compilation complete
            if (compilation.hooks.needAdditionalPass.call()) {
                compilation.needAdditionalPass = true;
                //Again, new Stats
                const stats = new Stats(compilation);
                stats.startTime = startTime;
                stats.endTime = Date.now(); 
                //Continue asynchronous call time flow
                this.hooks.done.callAsync(stats, err => {
                    if (err) return finalCallback(err);
                    //  There is an extra time flow this time. Call extra compilation to tell that compilation is finally finished
                    this.hooks.additionalPass.callAsync(err => {
                        if (err) return finalCallback(err);
                        //Call compile and pass the return value of onCompiled into the compile function. The return value of onCompiled is the object of new Stats
                        this.compile(onCompiled);
                    });
                });
                return;
            }
            // If none of the above branches are taken, i.e. the compilation is completed, no additional compilation is required
            // Call the emitRecords method to record the output
            this.emitRecords(err => {
                if (err) return finalCallback(err);
                // Also new Stats
                const stats = new Stats(compilation);
                stats.startTime = startTime;
                stats.endTime = Date.now();
                // Asynchronous call completion event
                this.hooks.done.callAsync(stats, err => {
                    if (err) return finalCallback(err);
                    return finalCallback(null, stats);
                });
            });
            //Finally, no matter which branch is taken, it is the callback function of new Stats(compilation) returning stats. According to the current process, the last branch is called this.emitRecords
        });
    };
    
    // Call beforeRun hook
    this.hooks.beforeRun.callAsync(this, err => {
        if (err) return finalCallback(err);
        // Call run hook
        this.hooks.run.callAsync(this, err => {
            if (err) return finalCallback(err);
            //Read file record
            this.readRecords(err => {
                if (err) return finalCallback(err);
                //Pass the onCompiled function in and call compile
                this.compile(onCompiled);
            });
        });
    });
}

new Stats(compilation) is the core of compiler.js

compilation and Stats correspond to two modules respectively

Line 2500 of compilation.js, line 1400 of stats.js

Let's take a look at the compile function

newCompilationParams() {
        const params = {
            // General module factory
            normalModuleFactory: this.createNormalModuleFactory(),
            // Context module factory
            contextModuleFactory: this.createContextModuleFactory(),
            // Compile dependency
            compilationDependencies: new Set()
        };
        return params;
    }


compile(callback) {
    // params values are as follows
    const params = this.newCompilationParams();
    // Asynchronous call beforeCompile hook
    this.hooks.beforeCompile.callAsync(params, err => {
        if (err) return callback(err);
        // Call compile hook
        this.hooks.compile.call(params);
        // Finally, there is compilation. I haven't talked about what it is before. Let's see the following functions for new compilation
        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);
                // Call afterCompile asynchronously and return the callback function
                this.hooks.afterCompile.callAsync(compilation, err => {
                    if (err) return callback(err);

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

newCompilation(params) {
   // compilation is this.createCompilation(), continue
    const compilation = this.createCompilation();
    //Attaching properties to a compilation object
    compilation.fileTimestamps = this.fileTimestamps;
    compilation.contextTimestamps = this.contextTimestamps;
    compilation.name = this.name;
    compilation.records = this.records;
    compilation.compilationDependencies = params.compilationDependencies;
    //Tell the hook to finish calling
    this.hooks.thisCompilation.call(compilation, params);
    this.hooks.compilation.call(compilation, params);
    return compilation;
}

createCompilation() {
   // It turns out that Compilation comes from new Compilation, with 2500 lines in total, which is the core of the whole compiler.js
    return new Compilation(this);
}

params is as follows

{ normalModuleFactory:
   NormalModuleFactory {
     _pluginCompat:
      SyncBailHook {
      // key is the name of the tapable method
        _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],
     //File path
     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 {} }

Ultimate conclusion

  • Compiler constructor - >
  • run method >
  • this.compile(onCompiled) ->
  • this.compile() execution has compilation - >
  • onCompiled execute const stats = new Stats(compilation)
  • Finally, return final callback (null, stats);

this.compile(onCompiled) is a high-order function. It can be simply understood that the parameter is a function and returns a function

Sprinkle flowers I want to buy a bottle of skii to reward myself

Posted by deep on Sat, 30 Nov 2019 11:01:15 -0800