This is the sixth day of my participation in the First Challenge 2022. For details: First Challenge 2022.
preface
Webpack Plugin is an important part of Webpack. More than 80% of webpack functions are realized by plugin. This paper is divided into three parts, from understanding tapable to understand the operation mechanism of Webpack Plugin. And an introduction to the WEBPack Plugin API, as well as plugin practices. The contents are as follows:
- basis
- Webpack plugin is introduced
- The tapable module is introduced
- Focus on
- The compiler is introduced
- Compilation is introduced
- extension
- Introduction to other Hook apis
- The plugin practice
webpack plugin
Before getting into the details of the WebPack plug-in, you need to understand some of the basics of how WebPack works.
Rely on the analysis of
The main function of Webpack is to read the source file from the entry file and find its dependencies. And then you read the dependency files, and you keep looking for their dependencies, and you keep recursing.
The module mapping
During dependency analysis, WebPack does module mapping, putting the contents of the analyzed files into a map (large files), using the values associated with the file path and filename as keys.
The API is introduced
Along the way, WebPack provides a series of hooks and related objects that developers can optimize during webPack packaging or do additional customization.
- Compiler: A top-level API that provides most of the WebPack execution hooks. Provides webPack execution parameter objects as well as configuration information objects.
- Compilation: Through compiler access, can obtain the module information, dependency information
- Resolvers: Webpack provides entry’s configuration path to the Resolver, checks for the existence of a given partial path, and returns the complete absolute path along with additional information about context, request, invocation, and so on. in
compiler
Class, three types of built-in parsers are provided:normal
: Resolves modules by absolute or relative paths.context
: Parses modules in a given context.loader
: Parses webPack Loader.
- NormalModuleFactory and ContextModuleFactory Hooks: Factory creates objects or instances. From the entry point, it parses each request, parses the content to find further requests, and continues to crawl files by parsing all files and parsing any new files. In the final phase, each dependency becomes an instance of Module.
- JavascriptParser: Provides an API for module interpretation, allowing developers to customize the process of module interpretation.
Tapable
The WebPack apis described above all inherit from the Tapable class. Tapable encapsulates a set of APIS that control the publishing and subscribing of hook functions using a publish and subscribe model.
Next, four classes of tapable module will be introduced:
- SyncHook: SyncHook. Tasks are executed one by one from first to last.
- SyncBailHook: Ensures that hooks are synchronized and provides a termination mechanism to interrupt hook task execution.
- AsyncSeriesHook: Asynchronous serial task hook.
- AsyncParallelHook: Asynchronous parallel task hook.
The preparatory work
Create a tapable_test directory, go to the directory and create test.js.
|- tapable_test
|- test.js
Copy the code
Install tapable dependencies.
NPM I -d tapable // or YARN add -d tapableCopy the code
SyncHook
Synchronous execution hooks trigger all consumers, and consumer callbacks execute in sequence.
Test.js contains the following contents:
const { SyncHook } = require('tapable');
class TapableTest {
constructor() {
this.hooks = {
// instantiate the syncHook, assign it to rest.synchook, and take two arguments.
syncHook: new SyncHook(['params1'.'params2'])}}// Add message consumers
init() {
SyncHook is an instance of the syncHook class that uses the tap method to add the consumer of the message
this.hooks.syncHook.tap('pluginA'.(params1, params2) = > {
console.log(new Date(), 'pluginA', params1, params2)
return true
})
this.hooks.syncHook.tap('pluginB'.(params1, params2) = > {
console.log(new Date(), 'pluginB', params1, params2)
return true
})
this.hooks.syncHook.tap('pluginC'.(params1,params2) = > {
console.log(new Date(), 'pluginC', params1, params2)
return true
})
return this
}
/ / call
start() {
// Trigger syncHook and pass in arguments
this.hooks.syncHook.call({test: 'hello world'}, 'SyncHook'); }}const test = new TapableTest()
test.init()
test.start()
Copy the code
Console command-line execution:
node test.js
Copy the code
Output result:
2022-02-24T14:50:15.255z pluginA {test: 'Hello world'} SyncHook 2022-02-24T14:50:15.262z pluginB {test: 'Hello world'} SyncHook 2022-02-24T14:50:15.262z pluginC {test:' Hello World '} SyncHook 2022-02-24t14:50:15.262z pluginC {test: 'Hello World'} SyncHookCopy the code
After the SyncHook object is instantiated, the tap method is used to register consumers and the Call method is used to trigger all consumers. From the output, you can see that consumers follow the order of registration.
SyncBailHook
Make sure that the hooks are synchronized to execute the registered consumer callbacks in sequence, interrupting subsequent execution as soon as one of the consumer returns a value.
The contents of test.js are updated as follows:
const { SyncBailHook } = require('tapable');
class TapableTest {
constructor() {
this.hooks = {
// Instantiate the sync hook and assign it to links.syncbailhook, which takes two arguments.
syncBailHook: new SyncBailHook(['params1'.'params2'])}}// Add message consumers
init() {
SyncBailHook is an instance of the syncBailHook class that uses the tap method to add the consumer of the message
this.hooks.syncBailHook.tap('pluginA'.(params1, params2) = > {
console.log(new Date(), 'pluginA', params1, params2)
})
this.hooks.syncBailHook.tap('pluginB'.(params1, params2) = > {
console.log(new Date(), 'pluginB', params1, params2)
return true
})
this.hooks.syncBailHook.tap('pluginC'.(params1,params2) = > {
console.log(new Date(), 'pluginC', params1, params2)
})
return this
}
/ / call
start() {
// Trigger syncBailHook and pass in the argument
this.hooks.syncBailHook.call({test: 'hello world'}, 'syncBailHook'); }}const test = new TapableTest()
test.init()
test.start()
Copy the code
Console command-line execution:
node test.js
Copy the code
Output result:
2022-02-24T15:31:15.835z pluginA {test: 'Hello world'} 2022-02-24T15:31:15.843z {test: 'hello world' } syncBailHookCopy the code
After the SyncBailHook object is instantiated, the tap method is used to register consumers and the Call method is used to trigger all consumers. The pluginB consumer callback returns true to interrupt subsequent execution.
AsyncSeriesHook
Asynchronous serial task hook that executes registration callbacks in parallel. Unlike synchronous hooks, which need to be registered with tapAsync or tapPromise, callAsync triggers the consumer.
const { AsyncSeriesHook } = require('tapable');
class TapableTest {
constructor() {
this.hooks = {
// Instantiate the sync hook and assign it to rests. asyncSeriesHook, which takes two arguments.
asyncSeriesHook: new AsyncSeriesHook(['params1'.'params2'])}}// Add message consumers
init() {
AsyncSeriesHook is an instance of the asyncSeriesHook class, adding the consumer of the message using the tapAsync/tapPromise method
this.hooks.asyncSeriesHook.tapAsync('pluginA'.(params1, params2, cb) = > {
setTimeout(() = >{
console.log(new Date(), 'pluginA', params1, params2)
cb()
}, 1000)})this.hooks.asyncSeriesHook.tapAsync('pluginB'.(params1, params2, cb) = > {
setTimeout(() = >{
console.log(new Date(), 'pluginB', params1, params2)
cb()
}, 2000)})this.hooks.asyncSeriesHook.tapPromise('pluginC'.(params1,params2) = > {
return new Promise((resolve) = > {
setTimeout(() = > {
console.log(new Date(), 'pluginC', params1, params2)
resolve();
}, 3000)})})return this
}
/ / call
start() {
// Triggers asyncSeriesHook and passes in arguments and callbacks
this.hooks.asyncSeriesHook.callAsync({test: 'hello world'}, 'asyncSeriesHook'.() = > {
console.log('all is done')}); }}const test = new TapableTest()
test.init()
test.start()
Copy the code
Console command-line execution:
node test.js
Copy the code
Output result:
2022-02-24T15:53:59.401z pluginA {test: 'Hello world'} asyncSeriesHook 2022-02-24t15:54:01.410z {test: 'Hello world'} asyncSeriesHook 2022-02-24t15:54:04.411z pluginC {test:' Hello world'} asyncSeriesHook all is doneCopy the code
There are two ways to register asynchronous consumers: tapAsync and tapPromise:
- The tapAsync parameter will finally tell the caller that the consumption-completed callback needs to be executed after the custom logic execution is complete.
- TapPromise returns a Promise instance, and a resolve call in the Promise instance constructor’s input callback tells the call that the current consumption is complete.
Asynchronous consumers use callAsync calls to pass an end callback that triggers execution after all asynchronous consumers have been executed. The console finally prints all is done.
Since it is a serial execution consumer, the console log output time interval is 1000ms, 2000ms, 3000ms respectively.
AsyncParallelHook
Asynchronous parallel task hook that executes registration callbacks in parallel. Unlike synchronous hooks, which need to be registered with tapAsync or tapPromise, callAsync triggers the consumer.
Console command-line execution:
const { AsyncParallelHook } = require('tapable');
class TapableTest {
constructor() {
this.hooks = {
// Instantiate the sync hook and assign it to cross. asyncParallelHook, which takes two arguments.
asyncParallelHook: new AsyncParallelHook(['params1'.'params2'])}}// Add message consumers
init() {
AsyncParallelHook is an instance of the asyncParallelHook class, adding the consumer of the message using the tapAsync/tapPromise method
this.hooks.asyncParallelHook.tapAsync('pluginA'.(params1, params2, cb) = > {
setTimeout(() = >{
console.log(new Date(), 'pluginA', params1, params2)
cb()
}, 1000)})this.hooks.asyncParallelHook.tapAsync('pluginB'.(params1, params2, cb) = > {
setTimeout(() = >{
console.log(new Date(), 'pluginB', params1, params2)
cb()
}, 2000)})this.hooks.asyncParallelHook.tapPromise('pluginC'.(params1,params2) = > {
return new Promise((resolve) = > {
setTimeout(() = > {
console.log(new Date(), 'pluginC', params1, params2)
resolve();
}, 3000)})})return this
}
/ / call
start() {
// Triggers asyncParallelHook and passes in arguments and callbacks
this.hooks.asyncParallelHook.callAsync({test: 'hello world'}, 'asyncParallelHook'.() = > {
console.log('all is done')}); }}const test = new TapableTest()
test.init()
test.start()
Copy the code
Output result:
2022-02-24T15:51:12.116z pluginA {test: 'Hello world'} asyncParallelHook 2022-02-24t15:51:12.116z {test: 'Hello world'} asyncParallelHook 2022-02-24t15:51:13.116z pluginC {test:' Hello world'} asyncParallelHook all is doneCopy the code
The API of AsyncParallelHook is the same as that of AsyncSeriesHook.
As parallel execution consumers, console log output is performed at intervals of 1000ms, 1000ms, and 1000ms, respectively.
conclusion
The main Api of the WebPack Plugin is implemented by inheriting classes from the Tapable library. By understanding SyncHook, SyncBailHook, AsyncParallelHook and AsyncSeriesHook classes, we have a general understanding of the Hook API, serial, parallel, synchronous and asynchronous operations under WebPack Plugin. So you can read what’s coming up.
Understanding Tapable is really the beginning of the WebPack Plugin, thank you for reading.