preface
Webpack is a rich topic. Here’s an example.
webpack
的loader
和plugin
What’s the difference?- written
webpack
的loader
orplugin
? webpack
What is the compilation process?
And then you can start!!!!! A lot of. Both of you are right, but the interview time is limited, plus the Internet has a lot of answers. So, questions like these are not likely to be at the top of an interviewer’s list.
So, have you ever encountered or seen a problem like this?
Q:webpack
In the compilation processhook
What are the nodes?
I mentioned the webPack compilation process in a previous article, and the text is already very detailed. For details, click on how the advanced/advanced front end answers JavaScript interview questions (ii).
The beginning of a webpack
Let’s start with webpack.config.js
const path = require('path')
module.exports = {
devtool: 'none'.mode: 'development'.context: process.cwd(),
entry: './src/index.js'.output: {
filename: 'index.js'.path: path.resolve('dist')},module: {rules: [{test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader".options: {
presets: [["@babel/preset-env",
{
useBuiltIns: "usage"}]."@babel/preset-react"]}}}]},plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'}})]Copy the code
Think about it, why can you write webpack.config.js in your project to do everything?
- There are
entry
(entry) - There are
loader
- There are
plugin
- There are
output
Module. exports finally produced a {}. So who is the commonJS {} used here?
We look atnode_modules/webpack-cli/bin/cli.js
Inside part of the source code
/ / cli. Js... Part of the
const webpack = require("webpack");
let lastHash = null;
let compiler;
try {
compiler = webpack(options); / / 1
} catch (err) {
if (err.name === "WebpackOptionsValidationError") {
if (argv.color) console.error(`\u001b[1m\u001b[31m${err.message}\u001b[39m\u001b[22m`);
else console.error(err.message);
// eslint-disable-next-line no-process-exit
process.exit(1);
}
throw err;
}
// ...
// ...
// ...
if (firstOptions.watch || options.watch) { / / watch model
const watchOptions =
irstOptions.watchOptions || options.watchOptions ||
firstOptions.watch || options.watch || {};
if (watchOptions.stdin) {
process.stdin.on("end".function(_) {
process.exit(); // eslint-disable-line
});
process.stdin.resume();
}
compiler.watch(watchOptions, compilerCallback);
if(outputOptions.infoVerbosity ! = ="none") console.error("\nwebpack is watching the files... \n");
} else {
// run method,
compiler.run((err, stats) = > { / / 2
if (compiler.close) {
ompiler.close(err2= > {
compilerCallback(err || err2, stats);
});
} else{ compilerCallback(err, stats); }}); }// ...
Copy the code
Look at 1,2.
compiler = webpack(options);
compiler.run
Module. exports exports {}, options (1). Options = webpack.config.js module. Exports + webpack default built-in configuration. The key point I want to talk about is not here, if you have time, please learn more about it.
Moving on, there’s webpack(options), so look at node_modules/lib/webpack.js
/ * * *@param {WebpackOptions} options options object
* @param {function(Error=, Stats=): void=} callback callback
* @returns {Compiler | MultiCompiler} the compiler object
*/
const webpack = (options, callback) = > {
// code ...
if (Array.isArray(options)) {
Module.exports = []; // exports = module. Exports = []
// code ...
} else if (typeof options === "object") {
options = new WebpackOptionsDefaulter().process(options); // Get the default built-in configuration information for WebPack
compiler = new Compiler(options.context);
compiler.options = options;
// NodeEnvironmentPlugin gives compiler files the ability to read and write
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
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);
}
}
}
compiler.hooks.environment.call(); // the first hook encountered
compiler.hooks.afterEnvironment.call(); // meet the second hook
// The following line of code is important and will be highlighted later
compiler.options = new WebpackOptionsApply().process(options, compiler);
} else {
throw new Error("Invalid argument: options");
}
if (callback) {
// code ...
}
return compiler;
};
Copy the code
The first hook I met
compiler.hooks.environment
// the first hook encounteredcompiler.hooks.afterEnvironment
// meet the second hook
What are these hooks for, you ask?
We’ll follow the source code directly to the location of the two hooks: node_modules/lib/ compiler.js
class Compiler extends Tapable {
constructor(context) {
super(a);this.hooks = {
/ * *@type {SyncBailHook<Compilation>} * /
shouldEmit: new SyncBailHook(["compilation"]),
/ * *@type {AsyncSeriesHook<Stats>} * /
done: new AsyncSeriesHook(["stats"]),
/ * *@type {AsyncSeriesHook<>} * /
additionalPass: new AsyncSeriesHook([]),
/ * *@type {AsyncSeriesHook<Compiler>} * /
beforeRun: new AsyncSeriesHook(["compiler"]),
/ * *@type {AsyncSeriesHook<Compiler>} * /
run: new AsyncSeriesHook(["compiler"]),
/ * *@type {AsyncSeriesHook<Compilation>} * /
emit: new AsyncSeriesHook(["compilation"]),
/ * *@type {AsyncSeriesHook<string, Buffer>} * /
assetEmitted: new AsyncSeriesHook(["file"."content"]),
/ * *@type {AsyncSeriesHook<Compilation>} * /
afterEmit: new AsyncSeriesHook(["compilation"]),
/ * *@type {SyncHook<Compilation, CompilationParams>} * /
thisCompilation: new SyncHook(["compilation"."params"]),
/ * *@type {SyncHook<Compilation, CompilationParams>} * /
compilation: new SyncHook(["compilation"."params"]),
/ * *@type {SyncHook<NormalModuleFactory>} * /
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/ * *@type {SyncHook<ContextModuleFactory>} * /
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/ * *@type {AsyncSeriesHook<CompilationParams>} * /
beforeCompile: new AsyncSeriesHook(["params"]),
/ * *@type {SyncHook<CompilationParams>} * /
compile: new SyncHook(["params"]),
/ * *@type {AsyncParallelHook<Compilation>} * /
make: new AsyncParallelHook(["compilation"]),
/ * *@type {AsyncSeriesHook<Compilation>} * /
afterCompile: new AsyncSeriesHook(["compilation"]),
/ * *@type {AsyncSeriesHook<Compiler>} * /
watchRun: new AsyncSeriesHook(["compiler"]),
/ * *@type {SyncHook<Error>} * /
failed: new SyncHook(["error"]),
/ * *@type {SyncHook<string, string>} * /
invalid: new SyncHook(["filename"."changeTime"]),
/ * *@type {SyncHook} * /
watchClose: new SyncHook([]),
/ * *@type {SyncBailHook<string, string, any[]>} * /
infrastructureLog: new SyncBailHook(["origin"."type"."args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/ * *@type {SyncHook} * /
environment: new SyncHook([]),
/ * *@type {SyncHook} * /
afterEnvironment: new SyncHook([]),
/ * *@type {SyncHook<Compiler>} * /
afterPlugins: new SyncHook(["compiler"]),
/ * *@type {SyncHook<Compiler>} * /
afterResolvers: new SyncHook(["compiler"]),
/ * *@type {SyncBailHook<string, Entry>} * /
entryOption: new SyncBailHook(["context"."entry"])};// code ...
}
// code ...
}
Copy the code
The following hooks are weirdly located here. Move them for webpack 5. These hooks are a bit weird and webpack5 will remove them. Because Webpack has some hooks reserved, the purpose is to fire them at different stage times. These two hooks, however, are really just stage reminders and don’t do anything special.
Continue with the compiler.run method.
// code...
run(callback) {
if (this.running) return callback(new ConcurrentCompilationError());
const finalCallback = (err, stats) = > {
this.running = false;
if (err) {
this.hooks.failed.call(err);
}
if(callback ! = =undefined) return callback(err, stats);
};
const startTime = Date.now();
this.running = true;
const onCompiled = (err, compilation) = > {
if (err) return finalCallback(err);
if (this.hooks.shouldEmit.call(compilation) === false) {
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);
});
return;
}
this.emitAssets(compilation, err= > {
if (err) return finalCallback(err);
if (compilation.hooks.needAdditionalPass.call()) {
compilation.needAdditionalPass = true;
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);
this.compile(onCompiled);
});
});
return;
}
this.emitRecords(err= > {
if (err) return finalCallback(err);
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);
});
});
});
};
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);
});
});
});
}
Copy the code
Resolution:
-
We define a finalCallback that fires the this.links. failed hook inside the method. The method is not called at this point.
-
OnCompiled defines an onCompiled that is ready to be called later in the compile method. This means the logic to occur after a module has been compiled.
this.hooks.shouldEmit
Hooks are used to determine the compilation of the current module (i.ecompilation
(Have you finished it? If done, execute finalCallback directly.- Next, execute
this.emitAssets
Methods (which will eventually be handled herechunk
Writes to the specified file and prints todist
) - The trigger
compilation.hooks.needAdditionalPass
Hook, which means that there are additional conditions that need to be met, otherwise return undefined, and the program terminates.- will
compilation.needAdditionalPass
Set totrue
- Assigns some values from the compilation object to STATS
- Trigger the this.hooks. Done hook to execute finalCallback.
- will
-
BeforeRun hook, or asynchronously if there are no exceptions.
-
Call the this.readRecords method (to read the file) and pass what you read to this.pile. So let’s go ahead and look at the compile method
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err= > {
if (err) return callback(err);
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
this.hooks.make.callAsync(compilation, err= > {
if (err) return callback(err);
compilation.finish(err= > {
if (err) return callback(err);
compilation.seal(err= > {
if (err) return callback(err);
this.hooks.afterCompile.callAsync(compilation, err= > {
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
}
// code ...
newCompilation(params) {
const compilation = this.createCompilation();
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;
}
Copy the code
- The compile method
- By calling the
this.newCompilationParams
Method to get the initialization parameters to instantiate the compilation. - The trigger
this.hooks.beforeCompile
Hook, or return if an exception occurs. - Immediate trigger
this.hooks.compile
hook - call
this.newCompilation
methods- Instantiate object:
compilation
- The trigger
this.hooks.thisCompilation
hook - The trigger
this.hooks.compilation
hook - Get the instantiated object:
compilation
- Instantiate object:
- The trigger
this.hooks.make
Hook, or return if an exception occurs. - call
compilation.finish
Method, and return a callback if an exception occurs. - call
compilation.seal
Method (to process chunk) and return a callback if an exception occurs. - The trigger
this.hooks.afterCompile
Hook, which represents completion of compilation. - The final callback for the return is
this.onCompiled
. Performthis.onCompiled
And passed incompilation
. Back to theStep 2
- By calling the
What about the hooks for the compilation?
Q: Where did you go? What happened to the important hooks in the Compilation object?
A: When you trigger the this.hooks. Make hook, the compilation. AddEntry method is called.
How do I listen for the make hook?
A: You may not have forgotten the beginning of the code…
compiler.options = new WebpackOptionsApply().process(options, compiler); // Please search globally for this line of code
Copy the code
WebpackOptionsApply is what?node_modules/lib/WebpackOptionsApply.js
// Abbreviate the code, because there are too many 😂
//WebpackOptionsApply.js
const EntryOptionPlugin = require("./EntryOptionPlugin")
class WebpackOptionsApply {
process(options, compiler) {
new EntryOptionPlugin().apply(compiler)
compiler.hooks.entryOption.call(options.context, options.entry)
}
}
module.exports = WebpackOptionsApply
Copy the code
Obviously, depend onEntryOptionPlugin.js
// EntryOptionPlugin.js
const SingleEntryPlugin = require("./SingleEntryPlugin")
const itemToPlugin = function (context, item, name) {
return new SingleEntryPlugin(context, item, name)
}
class EntryOptionPlugin {
apply(compiler) {
compiler.hooks.entryOption.tap('EntryOptionPlugin'.(context, entry) = > {
itemToPlugin(context, entry, "main").apply(compiler)
})
}
}
module.exports = EntryOptionPlugin
Copy the code
Obviously, depend onSingleEntryPlugin.js
// SingleEntryPlugin.js
class SingleEntryPlugin {
constructor(context, entry, name) {
this.context = context
this.entry = entry
this.name = name
}
apply(compiler) {
compiler.hooks.compilation.tap(
"SingleEntryPlugin".(compilation, { normalModuleFactory }) = >{ compilation.dependencyFactories.set( SingleEntryDependency, normalModuleFactory ); }); compiler.hooks.make.tapAsync("SingleEntryPlugin".(compilation, callback) = > {
const { entry, name, context } = this;
constdep = SingleEntryPlugin.createDependency(entry, name); compilation.addEntry(context, dep, name, callback); }); }}module.exports = SingleEntryPlugin
Copy the code
Resolution:
new WebpackOptionsApply().process(options, compiler)
Mount all webPack built-in plug-ins (entry)- Go to the
WebpackOptionsApply.js
amongprocess
Method is callednew EntryOptionPlugin().apply(compiler)
- Go to the
EntryOptionPlugin.js
among- In the call
itemToPlugin
, and returned another oneSingleEntryPlugin
The constructor of the instance object is responsible for receiving the abovecontext entry name
entryOption
Is a hook instance,entryOption
在EntryOptionPlugin
The inside of theapply
Method is calledtap
(Registered event listener)
- In the call
- Go to the
SingleEntryPlugin.js
compilation
Hooks to monitormake
The hook listens.
- Go to the
- triggered
compiler.hooks.entryOption
hook
On the blackboard! To highlight! Once the make hook is triggered, it is executed in its callback methodcompilation.addEntry
“, indicating that all the preparatory work has been done before the module is compiled.
addEntry
->this._addModuleChain
->this.createModule
- Finally by
compiler
callcompilation.seal
methods-
Triggers the compilation. h-ooks. seal hook
-
Trigger compilation. Hooks. BeforeChunks hook
-
Trigger compilation. Hooks. AfterChunks hook
-
Call compilation. CreateChunkAssets method, finally to invoke this. EmitAssets method, under the output files to the package path.
-
The subtotal
Q: What are the hook nodes in the Webpack compilation process?
A:
compiler.hooks.environment
compiler.hooks.afterEnvironment
compiler.hooks.failed
compiler.hooks.shouldEmit
compilation.hooks.needAdditionalPass
compiler.hooks.beforeRun
compiler.hooks.run
compiler.hooks.beforeCompile
compiler.hooks.compile
compiler.hooks.thisCompilation
compiler.hooks.compilation
compiler.hooks.make
compiler.hooks.afterCompile
compiler.hooks.entryOption
compilation.hooks.seal
compilation.hooks.beforeChunks
compilation.hooks.afterChunks
compilation.createChunkAssets
(Wish you all the best)