preface
Earlier, we learned how the core webPack functionality enables source code transformation and packaging.
- Based on the
@babel/parser
Convert the string toAST
Abstract syntax tree. - right
AST
For modification, both to the source code modification. - use
babel
theAPI
And then put the modifiedAST
Convert to a new string (desired code). - And understand
css-loader
andstyle-loader
The basic implementation of.
Let’s take a look at the usage scenarios and how the Plugin works.
Lead the problem
- The basic code structure of a plug-in
- The webPack build process
- What does Tapable do by itself, and how does it link webPack plug-ins together?
compiler
As well ascompilation
Object and the main API
The basic structure of the plug-in
Here is a simple version of the MD file into an HTML file a case
The use of plug-in
const {resolve} = require('path')
const MdToHtmlPlugin = require('./plugins/md-to-html-plugin')
module.exports = {
mode: 'development'.entry: resolve(__dirname, 'src/app.js'),
output: {
path: resolve(__dirname, 'dist'),
filename: 'app.js'
},
plugins: [
new MdToHtmlPlugin({
template: resolve(__dirname, 'test.md'),
filename: 'test.html'}})]Copy the code
Define the plugin
const fs = require('fs')
const path = require('path')
function mdToHtml(mdStr) {
let html = ' '
// Filter out the empty ones
let mdArrTemp = mdStr.split('\n').filter(item= >item ! = ="")
//ol 1.
//ul -
//h2 ##
let olReg = /\d\.\s/,
ulReg = /\-\s/,
h2Reg = /\#{2}\s/
mdArrTemp.forEach((item, index) = > {
// There is no previous element, or the previous element is not the current traversal element
let prev = mdArrTemp[index - 1];
let next = mdArrTemp[index + 1];
if (h2Reg.test(item)) {
html += item.replace(h2Reg, "<h2>");
html += "</h2>\n";
} else if (ulReg.test(item)) {
if(! prev || ! ulReg.test(prev)) { html +="<ul>\n";
}
html += item.replace(ulReg, " " );
html += "</li>\n";
if(! next || ! ulReg.test(next)) { html +="</ul>\n"; }}else if (olReg.test(item)) {
if(! prev || ! olReg.test(prev)) { html +="<ol>\n";
}
html += item.replace(olReg, " " );
html += "</li>\n";
if(! next || ! olReg.test(next)) { html +="<ol>\n"; }}})return html
}
class MdToHtmlPlugin {
// You can pass instance arguments inside the constructor
constructor({template, filename}) {
if(! template) {throw new Error('"template" must be configured')}this.template = template
this.filename = filename
}
// Webpack calls the Apply method of the HelloPlugin instance to pass in the Compiler object to the plug-in.
apply(compiler) {
compiler.hooks.emit.tap("md-to-html-plugin".(compilation) = > {
let _assets = compilation.assets
// Read the original md file.
let mdContent = fs.readFileSync(this.template, "utf-8");
let htmlTemplateContent = fs.readFileSync(path.resolve(__dirname, './template.html')).toString()
let html = mdToHtml(mdContent)
// Read the template raw file.
// Replace the format of the MD file with HTML.
// Insert the HTML generated by the substitution into the HTML template file.
let genHtml = htmlTemplateContent.replace("<! --md-->", html);
fs.writeFileSync(path.resolve(__dirname, '.. /.. /dist/'+this.filename), genHtml, 'utf-8')}); }}module.exports = MdToHtmlPlugin;
Copy the code
How does the plug-in work?
- We use it first when reading the WebPack configuration
new MdToHtmlPlugin(options)
I initialized oneMdToHtmlPlugin
The instance - It’s also initialized in other code
compiler
Object, calledMdToHtmlPlugin.apply(compiler)
Method passed to the plug-incompiler
Object. - The plug-in gets
compiler
Object can be calledcompiler.hooks.<hook name>.call
. Specific events are documented.
node events && tabable
node
The Events module provides only one object: Events.EventEmitter. The core of EventEmitter is the encapsulation of event triggering and event listener functions.
var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
event.on('some_event'.function() {
console.log('some_event event triggered ');
});
setTimeout(function() {
event.emit('some_event');
}, 1000);
Copy the code
tapable
github tapable
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Copy the code
Implement a simple tapable
class Hook{
constructor(args){
this.taps = []
this._args = args
}
tap(name,fn){
this.taps.push({name,fn})
}
}
class SyncHook extends Hook{
call(name,fn){
try {
this.taps.forEach(tap= > tap.fn(name))
fn(null,name)
} catch (error) {
fn(error)
}
}
}
/ / use
let$=new SyncHook()
$.tap('xx'.() = > {console.log('xx')})
$.call('xx'.() = > {console.log('xx called')})
Copy the code
Understand sync-type hooks
1, SyncHook
const {SyncHook} = require('tapable')
// Create an instance
const syncHook = new SyncHook(['name'.'age'])
// Register events
syncHook.tap('1'.(name, age) = > {
console.log('1' ,name , age)
})
syncHook.tap("2".(name, age) = > {
console.log("2", name, age);
});
syncHook.tap("3".(name, age) = > {
console.log("3", name, age);
});
// Trigger the event to execute the callback function
syncHook.call('morning'.10)
Copy the code
Understand Async hooks
1, AsyncSeriesHook
const { AsyncSeriesHook } = require("tapable");
// Create a real column
const asyncSeriesHook = new AsyncSeriesHook(["name"."age"]);
// Register events
asyncSeriesHook.tapAsync("1".(name, age, done) = > {
setTimeout(() = > {
console.log("1", name, age, new Date());
done();
}, 1000);
});
asyncSeriesHook.tapAsync("2".(name, age, done) = > {
setTimeout(() = > {
console.log("2", name, age, new Date());
done();
}, 2000);
});
asyncSeriesHook.tapAsync("3".(name, age, done) = > {
setTimeout(() = > {
console.log("3", name, age, new Date());
done();
}, 3000);
});
// Trigger the event to make the listener function execute
asyncSeriesHook.callAsync("Moon".10.() = > {
console.log("Execution completed");
});
Copy the code
How do Tapable and Webpack relate
Compiler.js
const { AsyncSeriesHook ,SyncHook } = require("tapable");
/ / create the class
class Compiler {
constructor(options) {
// Take the plugin from options and pass in the apply method
this.hooks = {
run: new SyncHook(["run"]), // Synchronize the hook
compile: new AsyncSeriesHook(["name"."age"]), // Asynchronous hooks
done: new SyncHook(['done'])}; options.plugins[0].apply(this);
}
run() {
// Execute asynchronous hooks
this.hooks.run.call();
this.compile()
}
compile() {
// Execute the sync hook and pass the parameter
this.hooks.compile.callAsync("Moon".10.(err) = > {
this.done();
});
}
done() {
this.hooks.done.call(); }}module.exports = Compiler
Copy the code
- MyPlugin.js
const Compiler = require("./Compiler");
class MyPlugin {
apply(compiler) {
// This is the hook,
// Wait for the hook callback function to trigger within webpack at some point and add your own logic to it
// Accept the compiler argument
compiler.hooks.run.tap("MyPlugin".() = > console.log("Start compiling..."));
// Trigger the asynchronous hook
compiler.hooks.compile.tapAsync("MyPlugin".(name, age, done) = > {
setTimeout(() = > {
console.log('Compiling... Received parameter name:${name}-age:${age}Compilation of... `);
done();
}, 3000);
});
// Synchronize the hook
compiler.hooks.done.tap("MyPlugin".() = > console.log("Finish compiling...")); }}This is similar to the plugins configuration for webpack.config.js
// Pass a new instance to the plugins property
const myPlugin = new MyPlugin();
const options = {
plugins: [myPlugin],
};
let compiler = new Compiler(options);
compiler.run();
Copy the code
After running the code above, you get the following output.
By the way, make fun of the code in the original text, and see several versions of the code.
Webpack build process
- Verify configuration files: read command line incoming or
webpack.config.js
File that initializes the configuration parameters for this build - generate
Compiler
Object: Executes the plug-in instantiation statement in the configuration filenew MyWebpackPlugin()
forwebpack
Event flow hang customhooks
- Enter the
entryOption
Phase:webpack
Start reading configurationEntries
, recursively traverses all entry files run/watch
: If run inwatch
Mode executeswatch
Method, otherwise executerun
methodscompilation
: createCompilation
Object callbackcompilation
Related hooks to enter each entry file in turn (entry
), use loader to compile the file. throughcompilation
I can read itmodule
theresource
(Resource path),loaders
(Loader used). Then use the compiled file contentsacorn
Parsing generates an AST static syntax tree. This process is then recursively and repeatedly executed after all modules and dependencies are analyzedcompilation
的seal
Method To sort, optimize and encapsulate each chunk__webpack_require__
To simulate modular operations.emit
: All files have been compiled and converted, containing the final output resources that we can call back to in the incoming eventcompilation.assets
Get the required data, including the resources to be exported, chunks of code, and so on.
// Modify or add the compilation. Assets ['new-file.js'] = {source() {return 'var a=1'; }, size() { return this.source().length; }}; Copy the codeCopy the code
afterEmit
: The file has been written to the diskdone
Finish compiling
Again, with a picture
Compiler (is responsible for compiling)
The Compiler module is the main engine for WebPack and creates a Compilation instance through all the options passed through the CLI or Node API. It extends from the Tapable class to register and invoke plug-ins. Most user-oriented plug-ins are registered with Compiler first.
When developing plug-ins for WebPack, you may need to know where each hook function is called. To learn about this, search for hooks.
.call in the Webpack source code. Refer to plugins in webpack
Compilation (Responsible for creating bundles)
In a nutshell,Compilation is about building modules and chunks and optimizing the build process with plug-ins.
Commonly used API
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
Webpack document link
reference
Cnblogs nuggets
series
Getting started with Webpack to Mastering one (AST, Babel, Dependencies)
Getting started with Webpack to Mastering 2 (Core Principles)
Webpack entry to Master three (Loader principle)
Webpack entry to Master four (Plugin principle)
Webpack Getting started to Mastering 5 (Common Configuration)