Skip to content

Commit 5da5021

Browse files
chore: test refactor + change changelog entry (#226)
1 parent 2ba184c commit 5da5021

22 files changed

Lines changed: 98 additions & 125 deletions

.changeset/perf-tap-registration.md

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,4 @@
22
"tapable": patch
33
---
44

5-
Perf: reduce allocations and work on the tap registration and compile paths.
6-
7-
- `Hook#_tap` builds the final tap descriptor in a single allocation for the
8-
common `hook.tap("name", fn)` string-options case instead of creating an
9-
intermediate `{ name }` object that was then merged via `Object.assign`.
10-
- `Hook#_insert` takes an O(1) fast path for the common append case (no
11-
`before`, and stage consistent with the last tap) - the previous
12-
implementation always ran the shift loop once.
13-
- `Hook#_runRegisterInterceptors` early-returns when there are no
14-
interceptors and uses an indexed loop instead of `for…of`.
15-
- `HookMap#for` inlines the `_map.get` lookup instead of routing through
16-
`this.get(key)`, saving a method dispatch on a path hit once per hook
17-
access in consumers like webpack.
18-
- `HookCodeFactory#setup` builds `_x` with a preallocated array + explicit
19-
loop instead of `Array.prototype.map`.
20-
- `HookCodeFactory#init` uses `Array.prototype.slice` instead of spread to
21-
skip the iterator protocol.
22-
- `HookCodeFactory#args` memoizes the common no-before/no-after result so
23-
arguments are joined once per compile rather than once per tap.
24-
- `HookCodeFactory#needContext`, `callTapsSeries`, `callTapsParallel` and
25-
`MultiHook`'s iteration use indexed loops with cached length, and the
26-
series/parallel code hoists the per-tap `done`/`doneBreak` closures
27-
out of the compile-time loop. Replaces `Array.prototype.findIndex`
28-
with a local loop to avoid callback allocation.
29-
30-
Registering 10 taps on a `SyncHook` is roughly 2× faster,
31-
`SyncHook: tap 5 + first call (compile)` is ~15% faster, and
32-
`HookMap#for (existing key)` is ~6% faster in the micro-benchmarks.
33-
The `.call()` path is unchanged.
5+
Improved performance in many places.

eslint.config.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ export default defineConfig([
1212
}
1313
},
1414
{
15-
files: ["lib/__tests__/**/*.js"],
15+
files: ["test/**/*"],
1616
languageOptions: {
1717
parserOptions: {
1818
ecmaVersion: 2018
1919
}
2020
}
2121
},
2222
{
23-
files: ["benchmark/**/*.mjs"],
23+
files: ["benchmark/**/*"],
2424
languageOptions: {
2525
parserOptions: {
2626
ecmaVersion: 2022

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"types": "./tapable.d.ts",
2121
"files": [
2222
"lib",
23-
"!lib/__tests__",
2423
"tapable.d.ts"
2524
],
2625
"scripts": {
@@ -38,7 +37,7 @@
3837
},
3938
"jest": {
4039
"transform": {
41-
"__tests__[\\\\/].+\\.js$": "babel-jest"
40+
"test[\\\\/].+\\.js$": "babel-jest"
4241
},
4342
"snapshotFormat": {
4443
"escapeString": true,
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
*/
55
"use strict";
66

7-
const AsyncParallelBailHook = require("../AsyncParallelBailHook");
8-
const AsyncParallelHook = require("../AsyncParallelHook");
9-
const HookTester = require("./HookTester");
7+
const AsyncParallelBailHook = require("../lib/AsyncParallelBailHook");
8+
const AsyncParallelHook = require("../lib/AsyncParallelHook");
9+
const HookTester = require("./HookTester.test");
1010

1111
describe("AsyncParallelHook", () => {
1212
it("should have to correct behavior", async () => {
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
*/
55
"use strict";
66

7-
const AsyncSeriesBailHook = require("../AsyncSeriesBailHook");
8-
const AsyncSeriesHook = require("../AsyncSeriesHook");
9-
const AsyncSeriesLoopHook = require("../AsyncSeriesLoopHook");
10-
const AsyncSeriesWaterfallHook = require("../AsyncSeriesWaterfallHook");
11-
const HookTester = require("./HookTester");
7+
const AsyncSeriesBailHook = require("../lib/AsyncSeriesBailHook");
8+
const AsyncSeriesHook = require("../lib/AsyncSeriesHook");
9+
const AsyncSeriesLoopHook = require("../lib/AsyncSeriesLoopHook");
10+
const AsyncSeriesWaterfallHook = require("../lib/AsyncSeriesWaterfallHook");
11+
const HookTester = require("./HookTester.test");
1212

1313
describe("AsyncSeriesHook", () => {
1414
it("should not have call method", () => {
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
*/
44
"use strict";
55

6-
const AsyncParallelBailHook = require("../AsyncParallelBailHook");
7-
const AsyncParallelHook = require("../AsyncParallelHook");
8-
const AsyncSeriesBailHook = require("../AsyncSeriesBailHook");
9-
const AsyncSeriesHook = require("../AsyncSeriesHook");
10-
const AsyncSeriesLoopHook = require("../AsyncSeriesLoopHook");
11-
const AsyncSeriesWaterfallHook = require("../AsyncSeriesWaterfallHook");
12-
const SyncBailHook = require("../SyncBailHook");
13-
const SyncHook = require("../SyncHook");
14-
const SyncLoopHook = require("../SyncLoopHook");
15-
const SyncWaterfallHook = require("../SyncWaterfallHook");
6+
const AsyncParallelBailHook = require("../lib/AsyncParallelBailHook");
7+
const AsyncParallelHook = require("../lib/AsyncParallelHook");
8+
const AsyncSeriesBailHook = require("../lib/AsyncSeriesBailHook");
9+
const AsyncSeriesHook = require("../lib/AsyncSeriesHook");
10+
const AsyncSeriesLoopHook = require("../lib/AsyncSeriesLoopHook");
11+
const AsyncSeriesWaterfallHook = require("../lib/AsyncSeriesWaterfallHook");
12+
const SyncBailHook = require("../lib/SyncBailHook");
13+
const SyncHook = require("../lib/SyncHook");
14+
const SyncLoopHook = require("../lib/SyncLoopHook");
15+
const SyncWaterfallHook = require("../lib/SyncWaterfallHook");
1616

1717
describe("Hooks without explicit args", () => {
1818
it("should construct SyncHook without args", () => {
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
*/
55
"use strict";
66

7-
const Hook = require("../Hook");
8-
const SyncHook = require("../SyncHook");
7+
const HookTest = require("../lib/Hook");
8+
const SyncHook = require("../lib/SyncHook");
99

1010
describe("Hook", () => {
1111
it("should throw when compile is not overridden", () => {
12-
const hook = new Hook(["arg"]);
12+
const hook = new HookTest(["arg"]);
1313
expect(() =>
1414
hook.compile({ taps: [], interceptors: [], args: [], type: "sync" })
1515
).toThrow(/Abstract: should be overridden/);
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
"use strict";
66

7-
const HookCodeFactory = require("../HookCodeFactory");
7+
const HookCodeFactoryTest = require("../lib/HookCodeFactory");
88

99
const expectNoSyntaxError = (code) => {
1010
// eslint-disable-next-line no-new
@@ -80,7 +80,7 @@ describe("HookCodeFactory", () => {
8080
let factory;
8181

8282
beforeEach(() => {
83-
factory = new HookCodeFactory();
83+
factory = new HookCodeFactoryTest();
8484
factory.init(factoryConfigurations[configurationName]);
8585
});
8686

@@ -233,7 +233,7 @@ describe("HookCodeFactory", () => {
233233
let factory;
234234

235235
beforeEach(() => {
236-
factory = new HookCodeFactory();
236+
factory = new HookCodeFactoryTest();
237237
factory.init(factoryConfigurations[configurationName]);
238238
});
239239

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
*/
44
"use strict";
55

6-
const HookMap = require("../HookMap");
7-
const SyncHook = require("../SyncHook");
6+
const HookMapTest = require("../lib/HookMap");
7+
const SyncHook = require("../lib/SyncHook");
88

99
describe("HookMap", () => {
1010
it("should return undefined from get when the key is unknown", () => {
11-
const map = new HookMap(() => new SyncHook());
11+
const map = new HookMapTest(() => new SyncHook());
1212
expect(map.get("missing")).toBeUndefined();
1313
});
1414

1515
it("should lazily create hooks through for(...) and cache them", () => {
1616
const factory = jest.fn(() => new SyncHook(["a"]));
17-
const map = new HookMap(factory, "myMap");
17+
const map = new HookMapTest(factory, "myMap");
1818

1919
expect(map.name).toBe("myMap");
2020

@@ -32,7 +32,7 @@ describe("HookMap", () => {
3232
});
3333

3434
it("should apply interceptor factories when creating hooks", () => {
35-
const map = new HookMap(() => new SyncHook());
35+
const map = new HookMapTest(() => new SyncHook());
3636
const wrapped = new SyncHook();
3737

3838
map.intercept({
@@ -47,7 +47,7 @@ describe("HookMap", () => {
4747
});
4848

4949
it("should default the interceptor factory to pass-through", () => {
50-
const map = new HookMap(() => new SyncHook());
50+
const map = new HookMapTest(() => new SyncHook());
5151
map.intercept({});
5252
const hook = map.for("bar");
5353
expect(hook).toBeDefined();
@@ -56,15 +56,15 @@ describe("HookMap", () => {
5656

5757
it("should forward deprecated tap helpers to the underlying hook", () => {
5858
const warn = jest.spyOn(console, "warn").mockImplementation(() => {});
59-
const map = new HookMap(() => new SyncHook(["a"]));
59+
const map = new HookMapTest(() => new SyncHook(["a"]));
6060

6161
const syncMock = jest.fn();
6262
map.tap("k", "plugin-sync", syncMock);
6363
map.for("k").call(1);
6464
expect(syncMock).toHaveBeenCalledWith(1);
6565

66-
const asyncMap = new HookMap(
67-
() => new (require("../AsyncSeriesHook"))(["a"])
66+
const asyncMap = new HookMapTest(
67+
() => new (require("../lib/AsyncSeriesHook"))(["a"])
6868
);
6969
const asyncMock = jest.fn((_a, cb) => cb());
7070
asyncMap.tapAsync("k", "plugin-async", asyncMock);
@@ -73,8 +73,8 @@ describe("HookMap", () => {
7373
asyncMap.for("k").callAsync(2, () => {
7474
expect(asyncMock).toHaveBeenCalled();
7575

76-
const promiseMap = new HookMap(
77-
() => new (require("../AsyncSeriesHook"))(["a"])
76+
const promiseMap = new HookMapTest(
77+
() => new (require("../lib/AsyncSeriesHook"))(["a"])
7878
);
7979
const promiseMock = jest.fn(() => Promise.resolve());
8080
promiseMap.tapPromise("k", "plugin-promise", promiseMock);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use strict";
22

3-
const AsyncSeriesHook = require("../AsyncSeriesHook");
3+
const AsyncSeriesHook = require("../lib/AsyncSeriesHook");
44

55
describe("HookStackOverflow", () => {
66
it("should not crash when compiling a large hook", () => {

0 commit comments

Comments
 (0)