As a bug who can type code for 5 minutes and debug for 2 hours, I have to deal with console.log a lot every day. As I am getting lazy in middle age, I want to change console.log(‘bug: ‘, bug) into log.bug to make my lazy cancer more thorough. So I took a hard look at the writing method of webpack plug-in and recorded the study notes of Webpack system here.
Follow webpack source code to cross the river
Go to! Pikachu!!
- Webpack initialization
// webpack.config.js
const webpack = require('webpack');
const WebpackPlugin = require('webpack-plugin');
const options = {
entry: 'index.js'.output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.bundle.js'
},
module: {
rules: []},plugins: [
new WebpackPlugin()
]
// ...
};
webpack(options);
Copy the code
// webpack.js
// Introduce the Compiler class
const Compiler = require("./Compiler");
// Webpack entry function
const webpack = (options, callback) = > {
// Create an instance of Compiler
compiler = new Compiler(options.context);
// The instance holds the webPack configuration object
compiler.options = options
// Call the Apply method of the Webpack plug-in in turn, passing in the Compiler reference
for (const plugin of options.plugins) {
plugin.apply(compiler);
}
// Execute the instance's run method
compiler.run(callback)
// Return the instance
return compiler
}
module.exports = webpack
Copy the code
- Initially, we call the Webpack function (webpack) whenever we type the Webpack directive on the console or use the Node.js API.The source code), and passed in the configuration options for WebPack to create one
compiler
Instance. compiler
What is? – Apparently compiler saves the full webPack configuration parameter options. So officials say:
The Compiler object represents the complete Configuration of the WebPack environment. This object is created once when webPack is started, and all the actionable Settings are configured, including options, Loader, and plugin. You can use compiler to access the main webPack environment.
- all
webpack-plugin
Also here by offering a calledapply
Method to webpack to complete the initialization and receive the newly createdcompiler
The reference.
- Compiler, Compilation and Tapable
// Compiler.js
const {
Tapable,
SyncHook,
SyncBailHook,
AsyncParallelHook,
AsyncSeriesHook
} = require("tapable");
const Compilation = require("./Compilation");
class Compiler extends Tapable {
hooks = [
hook1: new SyncHook(),
hook2: new AsyncSeriesHook(),
hook3: new AsyncParallelHook()
]
run () {
// ...
// Trigger hook callback execution
this.hooks[hook1].call()
this.hooks[hook2].callAsync((a)= > {
this.hooks[hook4].call()
})
// Enter the compilation process
this.compile()
// ...
this.hooks[hook3].promise((a)= > {})
}
compile () {
// Create a Compilation instance
const compilation = new Compilation(this)}}Copy the code
-
Explore the compiler.js file, which introduces a library of tapable classes that provide Hook classes that internally implement a similar event subscription/publishing system.
-
Where is the Hook class used? — There are many interesting hooks in the Compiler class (compiler.js source code). These hooks represent each key event node in the whole compilation process. They are inherited from the hook class, so they all support listening/subscription. When the compilation process reaches the event point, it will execute the event callback we provided and do what we want.
-
How do I subscribe? There is a apply method in each webpack-plugin above. In fact, the registration code is hidden inside, through code similar to the following. Any Webpack plug-in can subscribe to HOOk1, so Hook1 maintains a TAPS array that holds all the callbacks.
Compiler.hook1.tap (name, callback) // Register/subscribe
Compiler. Hooks. Hook1. Call () / / trigger/release
-
Once the preparation is complete, compilation begins when the run() method is called. In this method, when events such as Call /callAsync/ Promise are executed (they are managed by the taps plugins that are triggered within WebPack, including some official WebPack plugins), the corresponding hook executes all of its taps functions once. The general logic is as follows.
-
Where the hooks are executed in the compiled process order, the hooks are dependent on each other.
-
The Compilation instance is also created here, which represents a resource version build. Whenever a file change is detected, a new compilation is created to generate a new set of compilation resources. A compilation object represents the current module resources, the compile-generated resources, the changing files, and the state information of the dependencies being tracked. The compilation, like compiler, has many hooks, so it also provides many event nodes to subscribe to.
- The use of the Webpack plugin system
class MyPlugin {
apply(compiler) {
// Set the callback to access the compilation object:
compiler.hooks.compilation.tap('myPlugin', (compilation) => {
// Now set the callback to access task points in compilation:
compilation.hooks.optimize.tap('myPlugin', () = > {console.log('Hello compilation! '); }); }); }}module.exports = MyPlugin;
Copy the code
- To subscribe to
compiler
和complation
All event nodes are in the Webpack-pluginapply
Write in the method, as shown above. Of course, if you want to get a resource in the compiler process, you should first subscribe to the corresponding Compiler event node that provides the resource reference (God created Adam first, then Eve). What parameters can be provided by each Compiler time node, as described in the hook instantiation (below)The source code
this.hooks = {
// Get compilation and params
compilation: new SyncHook(["compilation"."params"]),
/ / get the stats
done: new AsyncSeriesHook(["stats"]),
/ / get a compiler
beforeRun: new AsyncSeriesHook(["compiler"])}Copy the code
Comb through the Webpack process
After the above preliminary exploration, we should have a general grasp of several knowledge points to write webPack plug-in:
- The plugin provides
apply
Methods forwebpack
Call to initialize - use
tap
Registration method hook incompiler
和compilation
Compiling process of - use
webpack
To provide theapi
Personalize resources.
Now that you know how to write plug-ins, it’s just a matter of finding the right hooks and modifying resources. In a Webpack system, hooks are processes, which are the life cycle of the compile build effort. Of course, in order to understand the specific functions of hooks of all Tapable instance objects, we need to explore how all internal plug-ins of Webpack use these hooks and what work they do to summarize. It is complicated to think about, so we can only extract important processes to summarize ideas and borrow a classic diagram from Taobao. ! [webpack_flow. JPG] (file:///Users/Ando/Documents/webpack-plugin/webpack_flow.jpg) the whole compilation process roughly divided into three stages, to tidy it up now:
Preparation stage
Initialization of webPack
- Webpack, in turn, calls the developer’s custom plug-in
apply
Method to allow plug-ins to register events. WebpackOptionsApply
Receiver combinationThe command line
和webpack.config.js
Is responsible for the initialization of the plug-in and Compiler context environment used by the underlying process within WebPack. (*Plugin
All plug-ins for use within Webpack)// webpack.js // Developer-defined plug-in initialization 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) } } } // ... // Init the plugin used within webpack compiler.options = new WebpackOptionsApply().process(options, compiler) // WebpackOptionsApply.js class WebpackOptionsApply extends OptionsApply { process (options, compiler) { // ... new WebAssemblyModulesPlugin({ mangleImports: options.optimization.mangleWasmImports }).apply(compiler); new EntryOptionPlugin().apply(compiler); compiler.hooks.entryOption.call(options.context, options.entry); new CompatibilityPlugin().apply(compiler); new HarmonyModulesPlugin(options.module).apply(compiler); new AMDPlugin(options.module, options.amd || {}).apply(compiler); // ...}}Copy the code
- perform
run / watch
Trigger (once packing/listen packing mode)compile
phase
Compilation phase
To generate themodule
.chunk
Resources.
run
→compile
Compile → Createcompilation
Object.compilation
The creation of is the start of a compilation that will be done on all modulesLoad (load)
,Sequestration (seal)
,Optimization (optimiz)
,Block (the chunk)
,Hash (hash)
和Re-create (restore)
.module.exports = class Compiler extends Tapable { run () { // Declare the compile-end callback function onCompiled () {} // Triggers the run hook this.hooks.run.callAsync(this, err => { this.compile(onCompiled) }) } compile(callback) { // ... BeforeCompile hooks are triggered before compilation begins this.hooks.beforeCompile.callAsync(params, err => { // Compile begins, triggering the compile hook this.hooks.compile.call(params); // Create the compilation instance const compilation = this.newCompilation(params); // Trigger the make hook this.hooks.make.callAsync(compilation, err => { // After the module has been resolved, execute the Compilation finish method compilation.finish(); // Execute the seal method to seal the resource compilation.seal(err= > { After compiling, execute the afterCompile hook this.hooks.afterCompile.callAsync(compilation, err => { // ...}); }); }); }); }}Copy the code
Load module
:make
Hook trigger →DllEntryPlugin
Internal plug-in callcompilation.addEntry
→compilation
Some resource generation factory methods are maintainedcompilation.dependencyFactories
Is responsible for converting the entry file and its (loop) dependencies tomodule
(module
The parsing process of theloader
). eachmodule
The parsing process is providedbuildModule / succeedModule
Wait for the hook, allmodule
Triggered when parsing is completefinishModules
Hook.sequestration
:seal
The method containsOptimize/block/hash
, the compilation stops receiving new modules and starts generating chunks. This stage relies on some internal webpack plug-ins to optimize the Module and add hash to the chunk generated by this construction. CreateChunkAssets () uses a different template for rendering depending on the chunk type. After the completion of this phase, it represents the completion of a compilation, triggeringafterCompile
hookTo optimize the
:BannerPlugin
compilation.hooks.optimizeChunkAssets.tapAsync('MyPlugin', (chunks, callback) => { chunks.forEach(chunk= > { chunk.files.forEach(file= > { compilation.assets[file] = new ConcatSource( '\/**Sweet Banner**\/'.'\n', compilation.assets[file] ); }); }); callback(); }); Copy the code
block
: Used to split chunksSplitChunksPlugin
Plugins listenoptimizeChunksAdvanced
hookThe hash
:createHash
- File generation stage
- When the compilation is complete, trigger
emit
To traverse thecompilation.assets
Generate all files.
Write a Webpack plug-in that enhances the console.log debugging experiencesimple-log-webpack-plugin
A picture of the first to respect. (contrast picture) just write log.a, and add file path to identify field A through webpack plug-in automatic completion, easily support printing color effect, log information of the same file can be folded, giving you a simple and convenient debugging environment.
The following is the source code, welcome test feedback. github npm
const ColorHash = require('color-hash')
const colorHash = new ColorHash()
const Dependency = require('webpack/lib/Dependency');
class LogDependency extends Dependency {
constructor(module) {
super();
this.module = module;
}
}
LogDependency.Template = class {
apply(dep, source) { const before = `; console.group('${source._source._name}'); ` const after = `; console.groupEnd(); ` const _size = source.size() source.insert(0, before) source.replace(_size, _size, after) } }; module.exports = class LogPlugin { constructor (opts) { this.options = { expression: /\blog\.(\w+)\b/ig, ... opts } this.plugin = { name:'LogPlugin'}}doLog (module) {
if(! module._source)return
let_val = module._source.source(), _name = module.resource; Const filedColor = colorHash.hex(module._buildhash) // Determine whether to addif (this.options.expression.test(_val)) {
module.addDependency(new LogDependency(module));
}
_val = _val.replace(
this.options.expression,
`console.log('%c$1: % C % O, % C %s'.'color: ${filedColor}'.'color: red'.The $1.'color: pink'.'${_name}')`
)
return_val } apply (compiler) { compiler.hooks.compilation.tap(this.plugin, (compilation) = > {/ / registered custom dependent on Template compilation. DependencyTemplates. Set (LogDependency, new LogDependency Template ()); / / modlue processing_complete hook compilation. Hooks. SucceedModule. Tap (enclosing the plugin, Module._source. _value = this.dolog (module)})}}Copy the code