Web pack source code analysis (4) - complier module
In the previous article, we saw that webpack-cli transforms the parameters passed in from the command line and the configuration items in the configuration file through `yargs', and then passes them to the compiler module of webpack to compile.
Let's take a look at what compiler has done.
Entrance
First, let's take a look at where webpack introduced the Compiler module. In webpack/lib/webpack.js, we found that the first line of the file introduced it and used it in the following main logic:
const Compiler = require("./Compiler"); ...... /* * options: This is the configuration item wrapped by the webpack-cli transformation * callback: In the second analysis, it was found that `webpack-cli'only passed in the options object to get the `compiler' object, but not the callback. */ const webpack = (options, callback) => { // Verify the format of configuration items // WebpackOptions Schema is a json-formatted description file that describes all configuration items acceptable to webpack and their formats // options are all configuration items exported from user-defined webpack.config.*.js // validateSchema uses ajv packages to verify various configuration items in options according to the data types and descriptions defined in webpackOptions Schema, and finally returns an error object containing all the wrong configuration items and descriptions. const webpackOptionsValidationErrors = validateSchema( webpackOptionsSchema, options ); // If there is a configuration item error, all errors are thrown if (webpackOptionsValidationErrors.length) { throw new WebpackOptionsValidationError(webpackOptionsValidationErrors); } //Determine whether the configuration item is an array, if it is an array, use MultiCompiler to compile, otherwise use Compiler module to compile, in general, our options are objects rather than arrays. let compiler; if (Array.isArray(options)) { compiler = new MultiCompiler(options.map(options => webpack(options))); } else if (typeof options === "object") { //Processing input configuration items with default configuration items // WebpackOptions Defaulter inherits from Options Defaulter class // There are two objects in this class: // * this.defaults: The value used to store configuration items // * this.config: The type of value used to store configuration items // Processing logic of process method: // * If the defaults object exists, but does not exist in the config object, and does not exist in options, copy a copy from the defaults object to options // * If there is a "call" value in the config object, it means that its value is a method call, it is called directly, and options are passed in as a parameter. // * If it exists in the config object and the value is "make" and it is not in options, then it is a method. Call it directly and pass the options as a parameter, get the return value and assign it to the corresponding item in options. // * If the tangent value in the config object is "append", the corresponding value in options is taken out. If it is not an array, it is reset to an array, and the value in the defaults object is copied to an array. Finally, the array is assigned to the corresponding item of options as a value. options = new WebpackOptionsDefaulter().process(options); // new is a compiler instance with a parameter of the directory path where the node command is currently executed // The Compiler class inherits from the Tapable class we talked about last time. In the constructor, various types of hook instances are initialized. // The internal logic of the compiler class is explained in detail later on. compiler = new Compiler(options.context); // options returned after the WebpackOptions Defaulter class processing are copied to compiler compiler.options = options; // Using NodeEnviromentPlugin class to add file input and output capability to compiler // The internal logic of Node Enviroment Plugin is explained in detail later on. new NodeEnvironmentPlugin().apply(compiler); // If there is a plug-in configuration in the configuration item and the plug-in configuration is an array // Traversing the plug-in array, if the plug-in is a function, use complier to call it, and use compier as a parameter in and out // Otherwise, use the apply method of WebpackPluginInstance to return a void value (equivalent to undefined) if (options.plugins && Array.isArray(options.plugins)) { for (const plugin of options.plugins) { if (typeof plugin === "function") { plugin.call(compiler, compiler); } else { plugin.apply(compiler); } } } // Triggering environment synchronization hooks, where call() is the triggering method for the Tapable hook event we talked about // When the environment is ready, execute the plug-in compiler.hooks.environment.call(); // Trigger the after environment synchronization hook // After the environment installation is complete, execute the plug-in compiler.hooks.afterEnvironment.call(); // Using the WebpackOptionsApply class to process options, return the processed option object // Processing logic of Web pack Options Apply, detailed in ++++++++++++++++++++++++++++++++++++ later compiler.options = new WebpackOptionsApply().process(options, compiler); } else { //If the configuration is neither an array type nor an object type, throw an error throw new Error("Invalid argument: options"); } // If you pass in back to the function if (callback) { // If the callback is not a function, throw a parameter type error if (typeof callback !== "function") { throw new Error("Invalid argument: callback"); } // If it is a listening mode, or (the configuration is an array and the listening option is true in the items in the array), initialize the listening configuration, and finally return the listening method of the compiler instance. // The instance listening method returns an instance of the Watching class, which is essentially an observer 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); } // Running callbacks using compiler's run method compiler.run(callback); } //Return the compiler instance return compiler; };
From the above source code analysis, we know that in webpack(), there are actually three things to do:
- The parameters are checked and standardized.
- new compiler instance and initializes various tapable hooks, and executes response hooks when the environment is ready and installed
- Initialize listening.
In the analysis above, we can see that the core is actually the compiler instance. Next, we will look at the internal logic of its class.
compiler analysis
Main body structure
First, let's look at the main structure:
/*...... *// Various introduction // The Compiler class inherits from Tapable class Compiler extends Tapable { constructor(context) {......}//Constructor performs various initialization operations watch(watchOptions, handler) {......}//Listen Initialization run(callback) {......}// Run compile runAsChild(callback) {...} // Running as a subcompilation process purgeInputFileSystem() {...} // Purification input emitAssets(compilation, callback) {...} // Publishing resources emitRecords(callback) {...} // Release record readRecords(callback) {...} // Read record createChildCompiler( compilation, compilerName, compilerIndex, outputOptions, plugins ) {...} // Create a subcompiler isChild() {return !!this.parentCompilation;} // Is subassembly or not createCompilation() {return new Compilation(this);} // Create assembly instances newCompilation(params) {return compilation;} // Create new assembly instances based on parameters createNormalModuleFactory() {return normalModuleFactory;} // Create factories for common modules createContextModuleFactory() {return contextModuleFactory;} // Create a factory for context modules newCompilationParams() {return params;} // Get a new assembly parameter object compile(callback) {} // Compile } module.exports = Compiler; class SizeOnlySource extends Source {} // A class is defined to function as ++++++++++++++++++++++.
Next, we'll break through one by one.