@rspack/lite-tapable
Version:
Lite weight tapable for Rspack
849 lines (848 loc) • 26.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MultiHook = exports.QueriedHookMap = exports.HookMap = exports.AsyncSeriesWaterfallHook = exports.AsyncSeriesBailHook = exports.AsyncSeriesHook = exports.AsyncParallelHook = exports.SyncWaterfallHook = exports.SyncBailHook = exports.SyncHook = exports.QueriedHook = exports.safeStage = exports.maxStage = exports.minStage = exports.HookBase = void 0;
class HookBase {
constructor(args = [], name) {
this.args = args;
this.name = name;
this.taps = [];
this.interceptors = [];
}
intercept(interceptor) {
this.interceptors.push(Object.assign({}, interceptor));
if (interceptor.register) {
for (let i = 0; i < this.taps.length; i++) {
this.taps[i] = interceptor.register(this.taps[i]);
}
}
}
_runRegisterInterceptors(options) {
return this.interceptors.reduce((options, interceptor) => {
return interceptor.register?.(options) ?? options;
}, options);
}
_runCallInterceptors(...args) {
for (const interceptor of this.interceptors) {
if (interceptor.call) {
interceptor.call(...args);
}
}
}
_runErrorInterceptors(e) {
for (const interceptor of this.interceptors) {
if (interceptor.error) {
interceptor.error(e);
}
}
}
_runTapInterceptors(tap) {
for (const interceptor of this.interceptors) {
if (interceptor.tap) {
interceptor.tap(tap);
}
}
}
_runDoneInterceptors() {
for (const interceptor of this.interceptors) {
if (interceptor.done) {
interceptor.done();
}
}
}
_runResultInterceptors(r) {
for (const interceptor of this.interceptors) {
if (interceptor.result) {
interceptor.result(r);
}
}
}
withOptions(options) {
const mergeOptions = (opt) => Object.assign({}, options, typeof opt === 'string' ? { name: opt } : opt);
return {
name: this.name,
tap: (opt, fn) => this.tap(mergeOptions(opt), fn),
tapAsync: (opt, fn) => this.tapAsync(mergeOptions(opt), fn),
tapPromise: (opt, fn) => this.tapPromise(mergeOptions(opt), fn),
intercept: (interceptor) => this.intercept(interceptor),
isUsed: () => this.isUsed(),
withOptions: (opt) => this.withOptions(mergeOptions(opt)),
queryStageRange: (stageRange) => this.queryStageRange(stageRange),
};
}
isUsed() {
return this.taps.length > 0 || this.interceptors.length > 0;
}
queryStageRange(stageRange) {
return new QueriedHook(stageRange, this);
}
callAsyncStageRange(queried, ...args) {
throw new Error('Hook should implement there own _callAsyncStageRange');
}
callAsync(...args) {
return this.callAsyncStageRange(this.queryStageRange(allStageRange), ...args);
}
promiseStageRange(queried, ...args) {
return new Promise((resolve, reject) => {
this.callAsyncStageRange(queried,
// @ts-expect-error
...args, (e, r) => {
if (e)
return reject(e);
return resolve(r);
});
});
}
promise(...args) {
return this.promiseStageRange(this.queryStageRange(allStageRange), ...args);
}
tap(options, fn) {
this._tap('sync', options, fn);
}
tapAsync(options, fn) {
this._tap('async', options, fn);
}
tapPromise(options, fn) {
this._tap('promise', options, fn);
}
_tap(type, options, fn) {
let normalizedOptions = options;
if (typeof options === 'string') {
normalizedOptions = {
name: options.trim(),
};
}
else if (typeof options !== 'object' || options === null) {
throw new Error('Invalid tap options');
}
if (typeof normalizedOptions.name !== 'string' ||
normalizedOptions.name === '') {
throw new Error('Missing name for tap');
}
this._insert(this._runRegisterInterceptors(Object.assign({ type, fn }, normalizedOptions)));
}
_insert(item) {
let before;
if (typeof item.before === 'string') {
before = new Set([item.before]);
}
else if (Array.isArray(item.before)) {
before = new Set(item.before);
}
let stage = 0;
if (typeof item.stage === 'number') {
stage = item.stage;
}
let i = this.taps.length;
while (i > 0) {
i--;
const x = this.taps[i];
this.taps[i + 1] = x;
const xStage = x.stage || 0;
if (before) {
if (before.has(x.name)) {
before.delete(x.name);
continue;
}
if (before.size > 0) {
continue;
}
}
if (xStage > stage) {
continue;
}
i++;
break;
}
this.taps[i] = item;
}
_prepareArgs(args) {
const len = this.args.length;
if (args.length < len) {
args.length = len;
return args.fill(undefined, args.length, len);
}
if (args.length > len) {
args.length = len;
return args;
}
return args;
}
}
exports.HookBase = HookBase;
exports.minStage = Number.NEGATIVE_INFINITY;
exports.maxStage = Number.POSITIVE_INFINITY;
const allStageRange = [exports.minStage, exports.maxStage];
const i32MIN = -(2 ** 31);
const i32MAX = 2 ** 31 - 1;
const safeStage = (stage) => {
if (stage < i32MIN)
return i32MIN;
if (stage > i32MAX)
return i32MAX;
return stage;
};
exports.safeStage = safeStage;
class QueriedHook {
constructor(stageRange, hook) {
const tapsInRange = [];
const [from, to] = stageRange;
for (const tap of hook.taps) {
const stage = tap.stage ?? 0;
if (from <= stage && stage < to) {
tapsInRange.push(tap);
}
else if (to === exports.maxStage && stage === exports.maxStage) {
tapsInRange.push(tap);
}
}
this.stageRange = stageRange;
this.hook = hook;
this.tapsInRange = tapsInRange;
}
isUsed() {
if (this.tapsInRange.length > 0)
return true;
if (this.stageRange[0] === exports.minStage &&
this.hook.interceptors.some((i) => i.call))
return true;
if (this.stageRange[1] === exports.maxStage &&
this.hook.interceptors.some((i) => i.done))
return true;
return false;
}
call(...args) {
if (typeof this.hook.callStageRange !==
'function') {
throw new Error('hook is not a SyncHook, call methods only exists on SyncHook');
}
return this.hook.callStageRange(this, ...args);
}
callAsync(...args) {
return this.hook.callAsyncStageRange(this, ...args);
}
promise(...args) {
return this.hook.promiseStageRange(this, ...args);
}
}
exports.QueriedHook = QueriedHook;
class SyncHook extends HookBase {
callAsyncStageRange(queried, ...args) {
const { stageRange: [from, to], tapsInRange, } = queried;
const argsWithoutCb = args.slice(0, args.length - 1);
const cb = args[args.length - 1];
const args2 = this._prepareArgs(argsWithoutCb);
if (from === exports.minStage) {
this._runCallInterceptors(...args2);
}
for (const tap of tapsInRange) {
this._runTapInterceptors(tap);
try {
tap.fn(...args2);
}
catch (e) {
const err = e;
this._runErrorInterceptors(err);
return cb(err);
}
}
if (to === exports.maxStage) {
this._runDoneInterceptors();
cb(null);
}
}
call(...args) {
return this.callStageRange(this.queryStageRange(allStageRange), ...args);
}
callStageRange(queried, ...args) {
let result;
let error;
this.callAsyncStageRange(queried,
// @ts-expect-error
...args, (e, r) => {
error = e;
result = r;
});
if (error) {
throw error;
}
return result;
}
tapAsync() {
throw new Error('tapAsync is not supported on a SyncHook');
}
tapPromise() {
throw new Error('tapPromise is not supported on a SyncHook');
}
}
exports.SyncHook = SyncHook;
class SyncBailHook extends HookBase {
callAsyncStageRange(queried, ...args) {
const { stageRange: [from, to], tapsInRange, } = queried;
const argsWithoutCb = args.slice(0, args.length - 1);
const cb = args[args.length - 1];
const args2 = this._prepareArgs(argsWithoutCb);
if (from === exports.minStage) {
this._runCallInterceptors(...args2);
}
for (const tap of tapsInRange) {
this._runTapInterceptors(tap);
let r = undefined;
try {
r = tap.fn(...args2);
}
catch (e) {
const err = e;
this._runErrorInterceptors(err);
return cb(err);
}
if (r !== undefined) {
this._runResultInterceptors(r);
return cb(null, r);
}
}
if (to === exports.maxStage) {
this._runDoneInterceptors();
cb(null);
}
}
call(...args) {
return this.callStageRange(this.queryStageRange(allStageRange), ...args);
}
callStageRange(queried, ...args) {
let result;
let error;
this.callAsyncStageRange(queried,
// @ts-expect-error
...args, (e, r) => {
error = e;
result = r;
});
if (error) {
throw error;
}
return result;
}
tapAsync() {
throw new Error('tapAsync is not supported on a SyncBailHook');
}
tapPromise() {
throw new Error('tapPromise is not supported on a SyncBailHook');
}
}
exports.SyncBailHook = SyncBailHook;
class SyncWaterfallHook extends HookBase {
constructor(args = [], name) {
if (args.length < 1)
throw new Error('Waterfall hooks must have at least one argument');
super(args, name);
}
callAsyncStageRange(queried, ...args) {
const { stageRange: [from, to], tapsInRange, } = queried;
const argsWithoutCb = args.slice(0, args.length - 1);
const cb = args[args.length - 1];
const args2 = this._prepareArgs(argsWithoutCb);
if (from === exports.minStage) {
this._runCallInterceptors(...args2);
}
for (const tap of tapsInRange) {
this._runTapInterceptors(tap);
try {
const r = tap.fn(...args2);
if (r !== undefined) {
args2[0] = r;
}
}
catch (e) {
const err = e;
this._runErrorInterceptors(err);
return cb(err);
}
}
if (to === exports.maxStage) {
this._runDoneInterceptors();
cb(null, args2[0]);
}
}
call(...args) {
return this.callStageRange(this.queryStageRange(allStageRange), ...args);
}
callStageRange(queried, ...args) {
let result;
let error;
this.callAsyncStageRange(queried,
// @ts-expect-error
...args, (e, r) => {
error = e;
result = r;
});
if (error) {
throw error;
}
return result;
}
tapAsync() {
throw new Error('tapAsync is not supported on a SyncWaterfallHook');
}
tapPromise() {
throw new Error('tapPromise is not supported on a SyncWaterfallHook');
}
}
exports.SyncWaterfallHook = SyncWaterfallHook;
class AsyncParallelHook extends HookBase {
callAsyncStageRange(queried, ...args) {
const { stageRange: [from], tapsInRange, } = queried;
const argsWithoutCb = args.slice(0, args.length - 1);
const cb = args[args.length - 1];
const args2 = this._prepareArgs(argsWithoutCb);
if (from === exports.minStage) {
this._runCallInterceptors(...args2);
}
const done = () => {
this._runDoneInterceptors();
cb(null);
};
const error = (e) => {
this._runErrorInterceptors(e);
cb(e);
};
if (tapsInRange.length === 0)
return done();
let counter = tapsInRange.length;
for (const tap of tapsInRange) {
this._runTapInterceptors(tap);
if (tap.type === 'promise') {
const promise = tap.fn(...args2);
if (!promise || !promise.then) {
throw new Error(`Tap function (tapPromise) did not return promise (returned ${promise})`);
}
promise.then(() => {
counter -= 1;
if (counter === 0) {
done();
}
}, (e) => {
counter = 0;
error(e);
});
}
else if (tap.type === 'async') {
tap.fn(...args2, (e) => {
if (e) {
counter = 0;
error(e);
}
else {
counter -= 1;
if (counter === 0) {
done();
}
}
});
}
else {
let hasError = false;
try {
tap.fn(...args2);
}
catch (e) {
hasError = true;
counter = 0;
error(e);
}
if (!hasError && --counter === 0) {
done();
}
}
if (counter <= 0)
return;
}
}
}
exports.AsyncParallelHook = AsyncParallelHook;
class AsyncSeriesHook extends HookBase {
callAsyncStageRange(queried, ...args) {
const { stageRange: [from], tapsInRange, } = queried;
const argsWithoutCb = args.slice(0, args.length - 1);
const cb = args[args.length - 1];
const args2 = this._prepareArgs(argsWithoutCb);
if (from === exports.minStage) {
this._runCallInterceptors(...args2);
}
const done = () => {
this._runDoneInterceptors();
cb(null);
};
const error = (e) => {
this._runErrorInterceptors(e);
cb(e);
};
if (tapsInRange.length === 0)
return done();
let index = 0;
const next = () => {
const tap = tapsInRange[index];
this._runTapInterceptors(tap);
if (tap.type === 'promise') {
const promise = tap.fn(...args2);
if (!promise || !promise.then) {
throw new Error(`Tap function (tapPromise) did not return promise (returned ${promise})`);
}
promise.then(() => {
index += 1;
if (index === tapsInRange.length) {
done();
}
else {
next();
}
}, (e) => {
index = tapsInRange.length;
error(e);
});
}
else if (tap.type === 'async') {
tap.fn(...args2, (e) => {
if (e) {
index = tapsInRange.length;
error(e);
}
else {
index += 1;
if (index === tapsInRange.length) {
done();
}
else {
next();
}
}
});
}
else {
let hasError = false;
try {
tap.fn(...args2);
}
catch (e) {
hasError = true;
index = tapsInRange.length;
error(e);
}
if (!hasError) {
index += 1;
if (index === tapsInRange.length) {
done();
}
else {
next();
}
}
}
if (index === tapsInRange.length)
return;
};
next();
}
}
exports.AsyncSeriesHook = AsyncSeriesHook;
class AsyncSeriesBailHook extends HookBase {
callAsyncStageRange(queried, ...args) {
const { stageRange: [from], tapsInRange, } = queried;
const argsWithoutCb = args.slice(0, args.length - 1);
const cb = args[args.length - 1];
const args2 = this._prepareArgs(argsWithoutCb);
if (from === exports.minStage) {
this._runCallInterceptors(...args2);
}
const done = () => {
this._runDoneInterceptors();
cb(null);
};
const error = (e) => {
this._runErrorInterceptors(e);
cb(e);
};
const result = (r) => {
this._runResultInterceptors(r);
cb(null, r);
};
if (tapsInRange.length === 0)
return done();
let index = 0;
const next = () => {
const tap = tapsInRange[index];
this._runTapInterceptors(tap);
if (tap.type === 'promise') {
const promise = tap.fn(...args2);
if (!promise || !promise.then) {
throw new Error(`Tap function (tapPromise) did not return promise (returned ${promise})`);
}
promise.then((r) => {
index += 1;
if (r !== undefined) {
result(r);
}
else if (index === tapsInRange.length) {
done();
}
else {
next();
}
}, (e) => {
index = tapsInRange.length;
error(e);
});
}
else if (tap.type === 'async') {
tap.fn(...args2, (e, r) => {
if (e) {
index = tapsInRange.length;
error(e);
}
else {
index += 1;
if (r !== undefined) {
result(r);
}
else if (index === tapsInRange.length) {
done();
}
else {
next();
}
}
});
}
else {
let hasError = false;
let r = undefined;
try {
r = tap.fn(...args2);
}
catch (e) {
hasError = true;
index = tapsInRange.length;
error(e);
}
if (!hasError) {
index += 1;
if (r !== undefined) {
result(r);
}
else if (index === tapsInRange.length) {
done();
}
else {
next();
}
}
}
if (index === tapsInRange.length)
return;
};
next();
}
}
exports.AsyncSeriesBailHook = AsyncSeriesBailHook;
class AsyncSeriesWaterfallHook extends HookBase {
constructor(args = [], name) {
if (args.length < 1)
throw new Error('Waterfall hooks must have at least one argument');
super(args, name);
}
callAsyncStageRange(queried, ...args) {
const { stageRange: [from], tapsInRange, } = queried;
const argsWithoutCb = args.slice(0, args.length - 1);
const cb = args[args.length - 1];
const args2 = this._prepareArgs(argsWithoutCb);
if (from === exports.minStage) {
this._runCallInterceptors(...args2);
}
const result = (r) => {
this._runResultInterceptors(r);
cb(null, r);
};
const error = (e) => {
this._runErrorInterceptors(e);
cb(e);
};
if (tapsInRange.length === 0)
return result(args2[0]);
let index = 0;
const next = () => {
const tap = tapsInRange[index];
this._runTapInterceptors(tap);
if (tap.type === 'promise') {
const promise = tap.fn(...args2);
if (!promise || !promise.then) {
throw new Error(`Tap function (tapPromise) did not return promise (returned ${promise})`);
}
promise.then((r) => {
index += 1;
if (r !== undefined) {
args2[0] = r;
}
if (index === tapsInRange.length) {
result(args2[0]);
}
else {
next();
}
}, (e) => {
index = tapsInRange.length;
error(e);
});
}
else if (tap.type === 'async') {
tap.fn(...args2, (e, r) => {
if (e) {
index = tapsInRange.length;
error(e);
}
else {
index += 1;
if (r !== undefined) {
args2[0] = r;
}
if (index === tapsInRange.length) {
result(args2[0]);
}
else {
next();
}
}
});
}
else {
let hasError = false;
try {
const r = tap.fn(...args2);
if (r !== undefined) {
args2[0] = r;
}
}
catch (e) {
hasError = true;
index = tapsInRange.length;
error(e);
}
if (!hasError) {
index += 1;
if (index === tapsInRange.length) {
result(args2[0]);
}
else {
next();
}
}
}
if (index === tapsInRange.length)
return;
};
next();
}
}
exports.AsyncSeriesWaterfallHook = AsyncSeriesWaterfallHook;
const defaultFactory = (key, hook) => hook;
class HookMap {
constructor(factory, name) {
this._map = new Map();
this.name = name;
this._factory = factory;
this._interceptors = [];
}
get(key) {
return this._map.get(key);
}
for(key) {
const hook = this.get(key);
if (hook !== undefined) {
return hook;
}
let newHook = this._factory(key);
const interceptors = this._interceptors;
for (let i = 0; i < interceptors.length; i++) {
const factory = interceptors[i].factory;
if (factory) {
newHook = factory(key, newHook);
}
}
this._map.set(key, newHook);
return newHook;
}
intercept(interceptor) {
this._interceptors.push(Object.assign({
factory: defaultFactory,
}, interceptor));
}
isUsed() {
for (const key of this._map.keys()) {
const hook = this.get(key);
if (hook?.isUsed()) {
return true;
}
}
return false;
}
queryStageRange(stageRange) {
return new QueriedHookMap(stageRange, this);
}
}
exports.HookMap = HookMap;
class QueriedHookMap {
constructor(stageRange, hookMap) {
this.stageRange = stageRange;
this.hookMap = hookMap;
}
get(key) {
return this.hookMap.get(key)?.queryStageRange(this.stageRange);
}
for(key) {
return this.hookMap.for(key).queryStageRange(this.stageRange);
}
isUsed() {
for (const key of this.hookMap._map.keys()) {
if (this.get(key)?.isUsed()) {
return true;
}
}
return false;
}
}
exports.QueriedHookMap = QueriedHookMap;
class MultiHook {
constructor(hooks, name) {
this.hooks = hooks;
this.name = name;
}
tap(options, fn) {
for (const hook of this.hooks) {
hook.tap(options, fn);
}
}
tapAsync(options, fn) {
for (const hook of this.hooks) {
hook.tapAsync(options, fn);
}
}
tapPromise(options, fn) {
for (const hook of this.hooks) {
hook.tapPromise(options, fn);
}
}
isUsed() {
for (const hook of this.hooks) {
if (hook.isUsed())
return true;
}
return false;
}
intercept(interceptor) {
for (const hook of this.hooks) {
hook.intercept(interceptor);
}
}
withOptions(options) {
return new MultiHook(this.hooks.map((h) => h.withOptions(options)), this.name);
}
}
exports.MultiHook = MultiHook;