Skip to content

Commit d506580

Browse files
perf: improve
1 parent 72ce205 commit d506580

24 files changed

Lines changed: 347 additions & 41 deletions

.changeset/buffers-all-sources.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack-sources": patch
3+
---
4+
5+
Implements more effective `buffers` and `buffer` for `ReplaceSource` and improve performance in other places.

benchmark/cases/original-source/index.bench.mjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ export default function register(bench) {
4343
}
4444
});
4545

46+
bench.add("original-source: buffers() (from string)", () => {
47+
for (let i = 0; i < 50; i++) {
48+
new sources.OriginalSource(fixtureCode, "fixture.js").buffers();
49+
}
50+
});
51+
52+
bench.add("original-source: buffers() (from buffer)", () => {
53+
for (let i = 0; i < 50; i++) {
54+
new sources.OriginalSource(fixtureBuffer, "fixture.js").buffers();
55+
}
56+
});
57+
4658
bench.add("original-source: size()", () => {
4759
for (let i = 0; i < 50; i++) {
4860
new sources.OriginalSource(fixtureCode, "fixture.js").size();

benchmark/cases/prefix-source/index.bench.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ export default function register(bench) {
6868
for (let i = 0; i < 10; i++) ps.buffer();
6969
});
7070

71+
bench.add("prefix-source: buffers()", () => {
72+
const ps = new sources.PrefixSource(
73+
"\t",
74+
new sources.RawSource(fixtureCode),
75+
);
76+
for (let i = 0; i < 10; i++) ps.buffers();
77+
});
78+
7179
bench.add("prefix-source: size()", () => {
7280
const ps = new sources.PrefixSource(
7381
"\t",

benchmark/cases/raw-source/index.bench.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ export default function register(bench) {
4343
for (let i = 0; i < 50; i++) new sources.RawSource(fixtureBuffer).buffer();
4444
});
4545

46+
bench.add("raw-source: buffers() (from string)", () => {
47+
for (let i = 0; i < 50; i++) new sources.RawSource(fixtureCode).buffers();
48+
});
49+
50+
bench.add("raw-source: buffers() (from buffer)", () => {
51+
for (let i = 0; i < 50; i++) new sources.RawSource(fixtureBuffer).buffers();
52+
});
53+
54+
bench.add("raw-source: buffers() cached", () => {
55+
const src = new sources.RawSource(fixtureBuffer);
56+
for (let i = 0; i < 500; i++) src.buffers();
57+
});
58+
4659
bench.add("raw-source: size()", () => {
4760
for (let i = 0; i < 50; i++) new sources.RawSource(fixtureCode).size();
4861
});

benchmark/cases/replace-source/index.bench.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,22 @@ export default function register(bench) {
9696
buildManyReplacements(1000).buffer();
9797
});
9898

99+
bench.add("replace-source: buffer() (no replacements)", () => {
100+
for (let i = 0; i < 100; i++) {
101+
new sources.ReplaceSource(new sources.RawSource(fixtureCode)).buffer();
102+
}
103+
});
104+
105+
bench.add("replace-source: buffers() (no replacements)", () => {
106+
for (let i = 0; i < 100; i++) {
107+
new sources.ReplaceSource(new sources.RawSource(fixtureCode)).buffers();
108+
}
109+
});
110+
111+
bench.add("replace-source: buffers() (1000 small replacements)", () => {
112+
buildManyReplacements(1000).buffers();
113+
});
114+
99115
bench.add("replace-source: map() (no replacements)", () => {
100116
for (let i = 0; i < 10; i++) {
101117
new sources.ReplaceSource(

benchmark/cases/size-only-source/index.bench.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ export default function register(bench) {
4444
}
4545
});
4646

47+
bench.add("size-only-source: buffers() (throws)", () => {
48+
const src = new sources.SizeOnlySource(1024);
49+
for (let i = 0; i < 100; i++) {
50+
try {
51+
src.buffers();
52+
} catch {
53+
// expected
54+
}
55+
}
56+
});
57+
4758
bench.add("size-only-source: map() (throws)", () => {
4859
const src = new sources.SizeOnlySource(1024);
4960
for (let i = 0; i < 100; i++) {

benchmark/cases/source-map-source/index.bench.mjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,26 @@ export default function register(bench) {
6666
}
6767
});
6868

69+
bench.add("source-map-source: buffers() (from string)", () => {
70+
for (let i = 0; i < 50; i++) {
71+
new sources.SourceMapSource(
72+
fixtureCode,
73+
"fixture.js",
74+
fixtureMap,
75+
).buffers();
76+
}
77+
});
78+
79+
bench.add("source-map-source: buffers() (from buffer)", () => {
80+
for (let i = 0; i < 50; i++) {
81+
new sources.SourceMapSource(
82+
fixtureBuffer,
83+
"fixture.js",
84+
fixtureMap,
85+
).buffers();
86+
}
87+
});
88+
6989
bench.add("source-map-source: size()", () => {
7090
for (let i = 0; i < 50; i++) {
7191
new sources.SourceMapSource(fixtureCode, "fixture.js", fixtureMap).size();

lib/CompatSource.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ class CompatSource extends Source {
5454
return this._sourceLike.source();
5555
}
5656

57+
/**
58+
* @returns {Buffer} buffer
59+
*/
5760
buffer() {
5861
if (typeof this._sourceLike.buffer === "function") {
5962
return this._sourceLike.buffer();
@@ -71,6 +74,9 @@ class CompatSource extends Source {
7174
return super.buffers();
7275
}
7376

77+
/**
78+
* @returns {number} size
79+
*/
7480
size() {
7581
if (typeof this._sourceLike.size === "function") {
7682
return this._sourceLike.size();

lib/ConcatSource.js

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ class ConcatSource extends Source {
8888
}
8989
}
9090

91+
/**
92+
* @returns {Buffer} buffer
93+
*/
9194
buffer() {
9295
return Buffer.concat(this.buffers());
9396
}
@@ -97,12 +100,19 @@ class ConcatSource extends Source {
97100
*/
98101
buffers() {
99102
if (!this._isOptimized) this._optimize();
103+
const children = /** @type {SourceLike[]} */ (this._children);
104+
const childCount = children.length;
100105
/** @type {Buffer[]} */
101106
const buffers = [];
102-
for (const child of /** @type {SourceLike[]} */ (this._children)) {
107+
// Indexed loop + manual splat avoids the iterator allocation per
108+
// child and the inner for-of allocation per child.buffers() call.
109+
// Hot path during webpack's emit on deeply-nested ConcatSources.
110+
for (let ci = 0; ci < childCount; ci++) {
111+
const child = children[ci];
103112
if (typeof child.buffers === "function") {
104-
for (const buffer of child.buffers()) {
105-
buffers.push(buffer);
113+
const childBuffers = child.buffers();
114+
for (let bi = 0, blen = childBuffers.length; bi < blen; bi++) {
115+
buffers.push(childBuffers[bi]);
106116
}
107117
} else if (typeof child.buffer === "function") {
108118
buffers.push(child.buffer());
@@ -124,18 +134,25 @@ class ConcatSource extends Source {
124134
*/
125135
source() {
126136
if (!this._isOptimized) this._optimize();
137+
const children = /** @type {Source[]} */ (this._children);
138+
const childCount = children.length;
127139
let source = "";
128-
for (const child of this._children) {
129-
source += /** @type {Source} */ (child).source();
140+
for (let ci = 0; ci < childCount; ci++) {
141+
source += children[ci].source();
130142
}
131143
return source;
132144
}
133145

146+
/**
147+
* @returns {number} size
148+
*/
134149
size() {
135150
if (!this._isOptimized) this._optimize();
151+
const children = /** @type {Source[]} */ (this._children);
152+
const childCount = children.length;
136153
let size = 0;
137-
for (const child of this._children) {
138-
size += /** @type {Source} */ (child).size();
154+
for (let ci = 0; ci < childCount; ci++) {
155+
size += children[ci].size();
139156
}
140157
return size;
141158
}
@@ -308,10 +325,11 @@ class ConcatSource extends Source {
308325
*/
309326
updateHash(hash) {
310327
if (!this._isOptimized) this._optimize();
328+
const children = /** @type {Source[]} */ (this._children);
329+
const childCount = children.length;
311330
hash.update("ConcatSource");
312-
for (const item of this._children) {
313-
/** @type {Source} */
314-
(item).updateHash(hash);
331+
for (let ci = 0; ci < childCount; ci++) {
332+
children[ci].updateHash(hash);
315333
}
316334
}
317335

lib/OriginalSource.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ class OriginalSource extends Source {
7171
return this._value;
7272
}
7373

74+
/**
75+
* @returns {Buffer} buffer
76+
*/
7477
buffer() {
7578
if (this._valueAsBuffer === undefined) {
7679
const value = Buffer.from(/** @type {string} */ (this._value), "utf8");
@@ -83,10 +86,7 @@ class OriginalSource extends Source {
8386
}
8487

8588
/**
86-
* Override Source.prototype.size (= `this.buffer().length`) to avoid
87-
* allocating a Buffer just to read the byte length, and memoize the
88-
* result so repeated calls don't re-scan the string.
89-
* @returns {number} byte length
89+
* @returns {number} size
9090
*/
9191
size() {
9292
if (this._cachedSize !== undefined) return this._cachedSize;

0 commit comments

Comments
 (0)