introduce
How does Webpack work with various plug-ins to achieve packaging?
The main thing is the Tapable library. Below I refer to tapable source code, simple implementation of part of the hook.
SyncHook synchronization hook
Use the sample
const SyncHook = require("./lib/syncHook").default;
const hook = new SyncHook(["name"."age"]);
hook.tap("fn1".(name, age) = > {
console.log("fn1->>>", name, age);
});
hook.tap("fn2".(name, age) = > {
console.log("fn2->>>", name, age);
});
hook.call("kww".16);
// Console prints
fn1->>> kww 16
fn2->>> kww 16
Copy the code
The source code to achieve
Abstract base class Hook
I will simply implement an abstract class Hook. SyncHook, asyncParallelHook and other hooks in Tapable inherit from this Hook.
/** tap structure {name: string; fn: Function; type: 'sync' | 'async; } * /
abstract class Hook {
// List of formal parameters, such as ["name", "age"] in new SyncHook(["name", "age"])
public args: string[];
// List of registered subscription objects
public taps: object[];
// A list of callback functions for the subscription object
protected _x: Function[];
constructor(args = []) {
this.args = args;
this.taps = [];
this._x = undefined;
}
// hook.tap('fn1', () => {})
tap(options, fn) {
if (typeof options === "string") {
options = {
name: options,
};
}
// tap = { fn: f(){}, name: "fn1" }
const tap = Object.assign(
{
// The tap object in the source code also has type: "sync", and interceptors, WHICH I omit here
fn,
},
options
);
this._insert(tap);
}
/ / register the tap
private _insert(tap) {
this.taps[this.taps.length] = tap; // this.taps.push(tap);
}
call(. args) {
// Create the structure of the function code to be executed in the future
let callFn = this._createCall();
// Call the above function
return callFn.apply(this, args);
}
private _createCall(type) {
return this.compile({
taps: this.taps,
args: this.args,
type}); }abstract compile(data: { taps: any[]; args: string[]; type: "sync" | "async"|"promise"}) :Function;
}
export default Hook;
Copy the code
Implement the subclass SyncHook
The final execution function is dynamically generated by different CodeFactories. Implement SyncHookCodeFactory first, then SyncHook.
import Hook from "./hook";
SyncHookCodeFactory also inherits from a base class factory named HookCodeFactory
// Let's simplify this a little bit and write it right here
class SyncHookCodeFactory {
public options: {
taps: any[]; // [{ name:'fn1', fn: f(){} }, { name:'fn2', fn: f(){} },]
args: string[]; // ["name", "age"]
};
setup(instance, options) {
// This is done in the source code via init
this.options = options;
// Get all the registered tap callback functions
instance._x = options.taps.map((o) = > o.fn);
}
/** * Create a code problem that can be executed, then return ** the source code is written in the base class factory, through switch(this.options.type) to execute the different logic, * this.options. Type is sync, async, promise *@param options* /
create(options) {
let fn;
// Use new Function to convert strings into functions
fn = new Function(
/ / / "name", "age" into "the name, age," as a function of the generated form parameters, such as f (name, age) {... }
this.args(),
`
The ${this.head()}
The ${this.content()}
`
);
return fn;
}
// Fixed method in base class
head() {
return "var _x = this._x;";
}
// Subclass the method to generate a string similar to the following
//
// var _fn0 = _x[0]; _x is the callback function for all registered taps
// _fn0(name, age);
//
content() {
let code = "";
for (var i = 0; i < this.options.taps.length; i++) {
code += `
var _fn${i} = _x[${i}];
_fn${i}(The ${this.args()});
`;
}
return code;
}
args() {
return this.options.args.join(","); }}let factory = new SyncHookCodeFactory();
class SyncHook extends Hook {
constructor(args) {
super(args);
}
compile(options) {
factory.setup(this, options);
returnfactory.create(options); }}export default SyncHook;
Copy the code
AsyncParallelHook Asynchronous parallel hook
Use the sample
const AsyncParallelHook = require("./src/asyncParallelHook").default;
const hook = new AsyncParallelHook(["name"."age"]);
console.time('time')
hook.tapAsync("fn1".(name, age, callback) = > {
console.log("fn1->>>", name, age);
setTimeout(() = >{
callback();
},1000)}); hook.tapAsync("fn2".(name, age, callback) = > {
console.log("fn2->>>", name, age);
setTimeout(() = >{
callback();
},2000)}); hook.callAsync("kww".16.function () {
console.log("end");
console.timeEnd('time')});// Terminal print
fn1->>> kww 16
fn2->>> kww 16
end
time: 2009.890ms
Copy the code
The source code to achieve
Implement subclass AsyncParallelHook
AsyncParallelHook and SyncHook are not very different, mainly due to their codeFactory differences.
class AsyncParallelHookCodeFactory {...create(options) {
let fn;
// f(name,age, _callback){... }
fn = new Function(
this.args({ after: "_callback" }), // "name,age,_callback"
`
The ${this.head()}
The ${this.content()}
`
);
return fn;
}
// Generate code similar to the following
// var _counter = 2;
// var _done = function(){_callback(); };
//
// var _fn0 = _x[0];
// _fn0(name, age, function() {
// if(--_counter === 0) _done();
// });
//
// var _fn1 = _x[1];
// _fn1(name, age, function() {
// if(--_counter === 0) _done();
// });
//
content() {
let code = `var _counter = The ${this.options.taps.length}; var _done = function(){_callback(); }; `;
for (var i = 0; i < this.options.taps.length; i++) {
code += `
var _fn${i} = _x[${i}];
_fn${i}(The ${this.args()}, function(){
if(--_counter === 0) _done();
});
`;
}
return code;
}
args(data? : { before? :string; after? :string }) {
let args = this.options.args.slice();
//if (data? .before) {
// args = [data.before].concat(args);
/ /}
if(data? .after) { args = args.concat([data.after]);// ["name", "age", "_callback"]
}
return args.join(","); }... }var factory = new AsyncParallelHookCodeFactory();
class AsyncParallelHook extends Hook {
compile(options) {
factory.setup(this, options);
returnfactory.create(options); }}Copy the code