@depup/webpack-sources 3.3.4-depup.0 → 3.6.0-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +2 -2
  2. package/changes.json +1 -1
  3. package/lib/CachedSource.js +257 -63
  4. package/lib/CompatSource.js +42 -1
  5. package/lib/ConcatSource.js +100 -28
  6. package/lib/OriginalSource.js +111 -45
  7. package/lib/PrefixSource.js +60 -11
  8. package/lib/RawSource.js +85 -29
  9. package/lib/ReplaceSource.js +269 -73
  10. package/lib/SizeOnlySource.js +10 -1
  11. package/lib/Source.js +39 -0
  12. package/lib/SourceMapSource.js +92 -10
  13. package/lib/helpers/createMappingsSerializer.js +307 -54
  14. package/lib/helpers/getFromStreamChunks.js +165 -73
  15. package/lib/helpers/getGeneratedSourceInfo.js +7 -4
  16. package/lib/helpers/readMappings.js +6 -3
  17. package/lib/helpers/scopes.js +438 -0
  18. package/lib/helpers/splitIntoLines.js +10 -9
  19. package/lib/helpers/splitIntoPotentialTokens.js +99 -26
  20. package/lib/helpers/streamAndGetSourceAndMap.js +61 -22
  21. package/lib/helpers/streamChunks.js +3 -2
  22. package/lib/helpers/streamChunksOfCombinedSourceMap.js +7 -3
  23. package/lib/helpers/streamChunksOfRawSource.js +23 -15
  24. package/lib/helpers/streamChunksOfSourceMap.js +50 -42
  25. package/lib/index.js +3 -0
  26. package/package.json +34 -23
  27. package/types/CachedSource.d.ts +192 -0
  28. package/types/CompatSource.d.ts +87 -0
  29. package/types/ConcatSource.d.ts +75 -0
  30. package/types/OriginalSource.d.ts +87 -0
  31. package/types/PrefixSource.d.ts +59 -0
  32. package/types/RawSource.d.ts +75 -0
  33. package/types/ReplaceSource.d.ts +107 -0
  34. package/types/SizeOnlySource.d.ts +24 -0
  35. package/types/Source.d.ts +189 -0
  36. package/types/SourceMapSource.d.ts +136 -0
  37. package/types/helpers/createMappingsSerializer.d.ts +60 -0
  38. package/types/helpers/getFromStreamChunks.d.ts +16 -0
  39. package/types/helpers/getGeneratedSourceInfo.d.ts +31 -0
  40. package/types/helpers/getName.d.ts +15 -0
  41. package/types/helpers/getSource.d.ts +15 -0
  42. package/types/helpers/readMappings.d.ts +19 -0
  43. package/types/helpers/scopes.d.ts +119 -0
  44. package/types/helpers/splitIntoLines.d.ts +6 -0
  45. package/types/helpers/splitIntoPotentialTokens.d.ts +37 -0
  46. package/types/helpers/streamAndGetSourceAndMap.d.ts +39 -0
  47. package/types/helpers/streamChunks.d.ts +56 -0
  48. package/types/helpers/streamChunksOfCombinedSourceMap.d.ts +42 -0
  49. package/types/helpers/streamChunksOfRawSource.d.ts +16 -0
  50. package/types/helpers/streamChunksOfSourceMap.d.ts +19 -0
  51. package/types/helpers/stringBufferUtils.d.ts +55 -0
  52. package/types/index.d.ts +50 -0
  53. package/types.d.ts +42 -439
@@ -11,6 +11,7 @@ const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
11
11
  const streamChunks = require("./helpers/streamChunks");
12
12
 
13
13
  /** @typedef {import("./CompatSource").SourceLike} SourceLike */
14
+ /** @typedef {import("./Source").ClearCacheOptions} ClearCacheOptions */
14
15
  /** @typedef {import("./Source").HashLike} HashLike */
15
16
  /** @typedef {import("./Source").MapOptions} MapOptions */
16
17
  /** @typedef {import("./Source").RawSourceMap} RawSourceMap */
@@ -33,15 +34,19 @@ class ConcatSource extends Source {
33
34
  constructor(...args) {
34
35
  super();
35
36
  /**
36
- * @private
37
37
  * @type {Child[]}
38
38
  */
39
39
  this._children = [];
40
40
 
41
- for (const item of args) {
41
+ // Indexed loops avoid the iterator-protocol overhead `for...of`
42
+ // pays per element. Hot during webpack's emit when many
43
+ // ConcatSources are constructed/flattened.
44
+ for (let i = 0, l = args.length; i < l; i++) {
45
+ const item = args[i];
42
46
  if (item instanceof ConcatSource) {
43
- for (const child of item._children) {
44
- this._children.push(child);
47
+ const children = item._children;
48
+ for (let j = 0, jl = children.length; j < jl; j++) {
49
+ this._children.push(children[j]);
45
50
  }
46
51
  } else {
47
52
  this._children.push(item);
@@ -49,7 +54,6 @@ class ConcatSource extends Source {
49
54
  }
50
55
 
51
56
  /**
52
- * @private
53
57
  * @type {boolean}
54
58
  */
55
59
  this._isOptimized = args.length === 0;
@@ -69,8 +73,9 @@ class ConcatSource extends Source {
69
73
  */
70
74
  add(item) {
71
75
  if (item instanceof ConcatSource) {
72
- for (const child of item._children) {
73
- this._children.push(child);
76
+ const children = item._children;
77
+ for (let i = 0, l = children.length; i < l; i++) {
78
+ this._children.push(children[i]);
74
79
  }
75
80
  } else {
76
81
  this._children.push(item);
@@ -83,17 +88,38 @@ class ConcatSource extends Source {
83
88
  * @returns {void}
84
89
  */
85
90
  addAllSkipOptimizing(items) {
86
- for (const item of items) {
87
- this._children.push(item);
91
+ for (let i = 0, l = items.length; i < l; i++) {
92
+ this._children.push(items[i]);
88
93
  }
89
94
  }
90
95
 
96
+ /**
97
+ * @returns {Buffer} buffer
98
+ */
91
99
  buffer() {
100
+ return Buffer.concat(this.buffers());
101
+ }
102
+
103
+ /**
104
+ * @returns {Buffer[]} buffers
105
+ */
106
+ buffers() {
92
107
  if (!this._isOptimized) this._optimize();
108
+ const children = /** @type {SourceLike[]} */ (this._children);
109
+ const childCount = children.length;
93
110
  /** @type {Buffer[]} */
94
111
  const buffers = [];
95
- for (const child of /** @type {SourceLike[]} */ (this._children)) {
96
- if (typeof child.buffer === "function") {
112
+ // Indexed loop + manual splat avoids the iterator allocation per
113
+ // child and the inner for-of allocation per child.buffers() call.
114
+ // Hot path during webpack's emit on deeply-nested ConcatSources.
115
+ for (let ci = 0; ci < childCount; ci++) {
116
+ const child = children[ci];
117
+ if (typeof child.buffers === "function") {
118
+ const childBuffers = child.buffers();
119
+ for (let bi = 0, blen = childBuffers.length; bi < blen; bi++) {
120
+ buffers.push(childBuffers[bi]);
121
+ }
122
+ } else if (typeof child.buffer === "function") {
97
123
  buffers.push(child.buffer());
98
124
  } else {
99
125
  const bufferOrString = child.source();
@@ -105,7 +131,7 @@ class ConcatSource extends Source {
105
131
  }
106
132
  }
107
133
  }
108
- return Buffer.concat(buffers);
134
+ return buffers;
109
135
  }
110
136
 
111
137
  /**
@@ -113,18 +139,25 @@ class ConcatSource extends Source {
113
139
  */
114
140
  source() {
115
141
  if (!this._isOptimized) this._optimize();
142
+ const children = /** @type {Source[]} */ (this._children);
143
+ const childCount = children.length;
116
144
  let source = "";
117
- for (const child of this._children) {
118
- source += /** @type {Source} */ (child).source();
145
+ for (let ci = 0; ci < childCount; ci++) {
146
+ source += children[ci].source();
119
147
  }
120
148
  return source;
121
149
  }
122
150
 
151
+ /**
152
+ * @returns {number} size
153
+ */
123
154
  size() {
124
155
  if (!this._isOptimized) this._optimize();
156
+ const children = /** @type {Source[]} */ (this._children);
157
+ const childCount = children.length;
125
158
  let size = 0;
126
- for (const child of this._children) {
127
- size += /** @type {Source} */ (child).size();
159
+ for (let ci = 0; ci < childCount; ci++) {
160
+ size += children[ci].size();
128
161
  }
129
162
  return size;
130
163
  }
@@ -169,7 +202,10 @@ class ConcatSource extends Source {
169
202
  const finalSource = Boolean(options && options.finalSource);
170
203
  let code = "";
171
204
  let needToCloseMapping = false;
172
- for (const item of /** @type {Source[]} */ (this._children)) {
205
+ const children = /** @type {Source[]} */ (this._children);
206
+ const childCount = children.length;
207
+ for (let ci = 0; ci < childCount; ci++) {
208
+ const item = children[ci];
173
209
  /** @type {number[]} */
174
210
  const sourceIndexMapping = [];
175
211
  /** @type {number[]} */
@@ -211,11 +247,6 @@ class ConcatSource extends Source {
211
247
  sourceIndex < 0 || sourceIndex >= sourceIndexMapping.length
212
248
  ? -1
213
249
  : sourceIndexMapping[sourceIndex];
214
- const resultNameIndex =
215
- nameIndex < 0 || nameIndex >= nameIndexMapping.length
216
- ? -1
217
- : nameIndexMapping[nameIndex];
218
- lastMappingLine = resultSourceIndex < 0 ? 0 : generatedLine;
219
250
  let _chunk;
220
251
  // When using finalSource, we process the entire source code at once at the end, rather than chunk by chunk
221
252
  if (finalSource) {
@@ -224,8 +255,17 @@ class ConcatSource extends Source {
224
255
  _chunk = chunk;
225
256
  }
226
257
  if (resultSourceIndex < 0) {
258
+ lastMappingLine = 0;
227
259
  onChunk(_chunk, line, column, -1, -1, -1, -1);
228
260
  } else {
261
+ // Only compute the remapped name index when the chunk
262
+ // actually carries a source mapping; otherwise it is
263
+ // unused.
264
+ const resultNameIndex =
265
+ nameIndex < 0 || nameIndex >= nameIndexMapping.length
266
+ ? -1
267
+ : nameIndexMapping[nameIndex];
268
+ lastMappingLine = generatedLine;
229
269
  onChunk(
230
270
  _chunk,
231
271
  line,
@@ -237,11 +277,11 @@ class ConcatSource extends Source {
237
277
  );
238
278
  }
239
279
  },
240
- (i, source, sourceContent) => {
280
+ (i, source, sourceContent, scopeBindings) => {
241
281
  let globalIndex = sourceMapping.get(source);
242
282
  if (globalIndex === undefined) {
243
283
  sourceMapping.set(source, (globalIndex = sourceMapping.size));
244
- onSource(globalIndex, source, sourceContent);
284
+ onSource(globalIndex, source, sourceContent, scopeBindings);
245
285
  }
246
286
  sourceIndexMapping[i] = globalIndex;
247
287
  },
@@ -287,16 +327,46 @@ class ConcatSource extends Source {
287
327
  };
288
328
  }
289
329
 
330
+ /**
331
+ * Release cached data held by this source. clearCache is a memory
332
+ * hint: it never affects correctness or output, only how expensive
333
+ * the next read is. Subclasses override; the base is a no-op so
334
+ * every Source supports the call. Composite sources always recurse
335
+ * into wrapped sources. When the same child is reachable via several
336
+ * parents (e.g. modules shared across webpack chunks), pass a shared
337
+ * `visited` WeakSet so each subtree is walked at most once.
338
+ * Not safe to call concurrently with source/map/sourceAndMap/
339
+ * streamChunks/updateHash on the same instance.
340
+ * @param {ClearCacheOptions=} options selectors
341
+ * @param {WeakSet<Source>=} visited de-duplication set shared across calls
342
+ * @returns {void}
343
+ */
344
+ clearCache(options, visited) {
345
+ if (visited !== undefined && visited.has(this)) return;
346
+ const children = this._children;
347
+ const { length } = children;
348
+ let v = visited;
349
+ if (v === undefined && length > 0) v = new WeakSet();
350
+ if (v !== undefined) v.add(this);
351
+ for (let i = 0; i < length; i++) {
352
+ const child = children[i];
353
+ if (typeof child !== "string" && typeof child.clearCache === "function") {
354
+ child.clearCache(options, v);
355
+ }
356
+ }
357
+ }
358
+
290
359
  /**
291
360
  * @param {HashLike} hash hash
292
361
  * @returns {void}
293
362
  */
294
363
  updateHash(hash) {
295
364
  if (!this._isOptimized) this._optimize();
365
+ const children = /** @type {Source[]} */ (this._children);
366
+ const childCount = children.length;
296
367
  hash.update("ConcatSource");
297
- for (const item of this._children) {
298
- /** @type {Source} */
299
- (item).updateHash(hash);
368
+ for (let ci = 0; ci < childCount; ci++) {
369
+ children[ci].updateHash(hash);
300
370
  }
301
371
  }
302
372
 
@@ -358,7 +428,9 @@ class ConcatSource extends Source {
358
428
  newChildren.push(currentRawSources);
359
429
  }
360
430
  };
361
- for (const child of this._children) {
431
+ const children = this._children;
432
+ for (let ci = 0, cl = children.length; ci < cl; ci++) {
433
+ const child = children[ci];
362
434
  if (typeof child === "string") {
363
435
  if (currentString === undefined) {
364
436
  currentString = child;
@@ -8,12 +8,12 @@
8
8
  const Source = require("./Source");
9
9
  const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
10
10
  const getGeneratedSourceInfo = require("./helpers/getGeneratedSourceInfo");
11
- const splitIntoLines = require("./helpers/splitIntoLines");
12
- const splitIntoPotentialTokens = require("./helpers/splitIntoPotentialTokens");
11
+ const { eachPotentialToken } = require("./helpers/splitIntoPotentialTokens");
13
12
  const {
14
13
  isDualStringBufferCachingEnabled,
15
14
  } = require("./helpers/stringBufferUtils");
16
15
 
16
+ /** @typedef {import("./Source").ClearCacheOptions} ClearCacheOptions */
17
17
  /** @typedef {import("./Source").HashLike} HashLike */
18
18
  /** @typedef {import("./Source").MapOptions} MapOptions */
19
19
  /** @typedef {import("./Source").RawSourceMap} RawSourceMap */
@@ -24,32 +24,36 @@ const {
24
24
  /** @typedef {import("./helpers/streamChunks").OnName} OnName */
25
25
  /** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
26
26
  /** @typedef {import("./helpers/streamChunks").Options} Options */
27
+ /** @typedef {import("./helpers/streamChunks").ScopeBindings} ScopeBindings */
27
28
 
28
29
  class OriginalSource extends Source {
29
30
  /**
30
31
  * @param {string | Buffer} value value
31
32
  * @param {string} name name
33
+ * @param {ScopeBindings=} scopeBindings what each name this source declares
34
+ * evaluates to in the generated code, for the map's `scopes` field
32
35
  */
33
- constructor(value, name) {
36
+ constructor(value, name, scopeBindings) {
34
37
  super();
35
38
 
36
39
  const isBuffer = Buffer.isBuffer(value);
37
40
 
38
41
  /**
39
- * @private
40
42
  * @type {undefined | string}
41
43
  */
42
44
  this._value = isBuffer ? undefined : value;
43
45
  /**
44
- * @private
45
46
  * @type {undefined | Buffer}
46
47
  */
47
48
  this._valueAsBuffer = isBuffer ? value : undefined;
48
49
  /**
49
- * @private
50
50
  * @type {string}
51
51
  */
52
52
  this._name = name;
53
+ /**
54
+ * @type {undefined | ScopeBindings}
55
+ */
56
+ this._scopeBindings = scopeBindings;
53
57
  }
54
58
 
55
59
  getName() {
@@ -72,6 +76,9 @@ class OriginalSource extends Source {
72
76
  return this._value;
73
77
  }
74
78
 
79
+ /**
80
+ * @returns {Buffer} buffer
81
+ */
75
82
  buffer() {
76
83
  if (this._valueAsBuffer === undefined) {
77
84
  const value = Buffer.from(/** @type {string} */ (this._value), "utf8");
@@ -83,6 +90,20 @@ class OriginalSource extends Source {
83
90
  return this._valueAsBuffer;
84
91
  }
85
92
 
93
+ /**
94
+ * @returns {number} size
95
+ */
96
+ size() {
97
+ if (this._cachedSize !== undefined) return this._cachedSize;
98
+ if (this._valueAsBuffer !== undefined) {
99
+ return (this._cachedSize = this._valueAsBuffer.length);
100
+ }
101
+ return (this._cachedSize = Buffer.byteLength(
102
+ /** @type {string} */ (this._value),
103
+ "utf8",
104
+ ));
105
+ }
106
+
86
107
  /**
87
108
  * @param {MapOptions=} options map options
88
109
  * @returns {RawSourceMap | null} map
@@ -112,34 +133,36 @@ class OriginalSource extends Source {
112
133
  /** @type {Buffer} */
113
134
  (this._valueAsBuffer).toString("utf8");
114
135
  }
115
- onSource(0, this._name, this._value);
136
+ onSource(0, this._name, this._value, this._scopeBindings);
116
137
  const finalSource = Boolean(options && options.finalSource);
117
138
  if (!options || options.columns !== false) {
118
- // With column info we need to read all lines and split them
119
- const matches = splitIntoPotentialTokens(this._value);
139
+ // With column info we need to walk every potential token. The
140
+ // scan is streamed by offset (see `eachPotentialToken`) so we
141
+ // only allocate a chunk substring when one is actually emitted —
142
+ // and `map()` / `sourceAndMap()` set `finalSource`, in which case
143
+ // the chunk is dropped and no substring is allocated at all.
144
+ const value = this._value;
120
145
  let line = 1;
121
146
  let column = 0;
122
- if (matches !== null) {
123
- for (const match of matches) {
124
- const isEndOfLine = match.endsWith("\n");
125
- if (isEndOfLine && match.length === 1) {
126
- if (!finalSource) onChunk(match, line, column, -1, -1, -1, -1);
127
- } else {
128
- const chunk = finalSource ? undefined : match;
129
- onChunk(chunk, line, column, 0, line, column, -1);
130
- }
131
- if (isEndOfLine) {
132
- line++;
133
- column = 0;
134
- } else {
135
- column += match.length;
136
- }
147
+ eachPotentialToken(value, (start, end, newline) => {
148
+ const length = end - start;
149
+ if (newline && length === 1) {
150
+ if (!finalSource) onChunk("\n", line, column, -1, -1, -1, -1);
151
+ } else {
152
+ const chunk = finalSource ? undefined : value.slice(start, end);
153
+ onChunk(chunk, line, column, 0, line, column, -1);
137
154
  }
138
- }
155
+ if (newline) {
156
+ line++;
157
+ column = 0;
158
+ } else {
159
+ column += length;
160
+ }
161
+ });
139
162
  return {
140
163
  generatedLine: line,
141
164
  generatedColumn: column,
142
- source: finalSource ? this._value : undefined,
165
+ source: finalSource ? value : undefined,
143
166
  };
144
167
  } else if (finalSource) {
145
168
  // Without column info and with final source we only
@@ -165,27 +188,64 @@ class OriginalSource extends Source {
165
188
  }
166
189
  return result;
167
190
  }
168
- // Without column info, but also without final source
169
- // we need to split source by lines
191
+ // Without column info, but also without final source.
192
+ // We only get here when (options.columns === false && !finalSource),
193
+ // so the source field is always undefined and the chunk arg is always
194
+ // the line text. Single-pass scan over newlines avoids the
195
+ // splitIntoLines array allocation.
196
+ const value = this._value;
197
+ const len = value.length;
198
+ if (len === 0) {
199
+ return { generatedLine: 1, generatedColumn: 0, source: undefined };
200
+ }
170
201
  let line = 1;
171
- const matches = splitIntoLines(this._value);
172
- /** @type {string | undefined} */
173
- let match;
174
- for (match of matches) {
175
- onChunk(finalSource ? undefined : match, line, 0, 0, line, 0, -1);
202
+ let i = 0;
203
+ while (i < len) {
204
+ const n = value.indexOf("\n", i);
205
+ if (n === -1) {
206
+ const lastLine = i === 0 ? value : value.slice(i);
207
+ onChunk(lastLine, line, 0, 0, line, 0, -1);
208
+ return {
209
+ generatedLine: line,
210
+ generatedColumn: lastLine.length,
211
+ source: undefined,
212
+ };
213
+ }
214
+ const chunk = n === i ? "\n" : value.slice(i, n + 1);
215
+ onChunk(chunk, line, 0, 0, line, 0, -1);
176
216
  line++;
217
+ i = n + 1;
218
+ }
219
+ // Source ended with a newline.
220
+ return { generatedLine: line, generatedColumn: 0, source: undefined };
221
+ }
222
+
223
+ /**
224
+ * Release cached data held by this source. clearCache is a memory
225
+ * hint: it never affects correctness or output, only how expensive
226
+ * the next read is. Subclasses override; the base is a no-op so
227
+ * every Source supports the call. Composite sources always recurse
228
+ * into wrapped sources. When the same child is reachable via several
229
+ * parents (e.g. modules shared across webpack chunks), pass a shared
230
+ * `visited` WeakSet so each subtree is walked at most once.
231
+ * Not safe to call concurrently with source/map/sourceAndMap/
232
+ * streamChunks/updateHash on the same instance.
233
+ * @param {ClearCacheOptions=} options selectors
234
+ * @param {WeakSet<Source>=} visited de-duplication set shared across calls
235
+ * @returns {void}
236
+ */
237
+ clearCache(options, visited) {
238
+ if (visited !== undefined) {
239
+ if (visited.has(this)) return;
240
+ visited.add(this);
241
+ }
242
+ if (options !== undefined && options.source === false) return;
243
+ if (this._value !== undefined && this._valueAsBuffer !== undefined) {
244
+ // When both forms are cached, drop the string (UTF-16 in V8 is
245
+ // ~2 bytes/char) and keep the more compact buffer. source() will
246
+ // rehydrate the string on demand.
247
+ this._value = undefined;
177
248
  }
178
- return matches.length === 0 || /** @type {string} */ (match).endsWith("\n")
179
- ? {
180
- generatedLine: matches.length + 1,
181
- generatedColumn: 0,
182
- source: finalSource ? this._value : undefined,
183
- }
184
- : {
185
- generatedLine: matches.length,
186
- generatedColumn: /** @type {string} */ (match).length,
187
- source: finalSource ? this._value : undefined,
188
- };
189
249
  }
190
250
 
191
251
  /**
@@ -194,7 +254,13 @@ class OriginalSource extends Source {
194
254
  */
195
255
  updateHash(hash) {
196
256
  hash.update("OriginalSource");
197
- hash.update(this.buffer());
257
+ // Same as RawSource: utf8 is what `buffer()` would have encoded to,
258
+ // and one of the two forms is always set.
259
+ if (this._valueAsBuffer === undefined) {
260
+ hash.update(/** @type {string} */ (this._value));
261
+ } else {
262
+ hash.update(this._valueAsBuffer);
263
+ }
198
264
  hash.update(this._name || "");
199
265
  }
200
266
  }
@@ -10,6 +10,7 @@ const Source = require("./Source");
10
10
  const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
11
11
  const streamChunks = require("./helpers/streamChunks");
12
12
 
13
+ /** @typedef {import("./Source").ClearCacheOptions} ClearCacheOptions */
13
14
  /** @typedef {import("./Source").HashLike} HashLike */
14
15
  /** @typedef {import("./Source").MapOptions} MapOptions */
15
16
  /** @typedef {import("./Source").RawSourceMap} RawSourceMap */
@@ -21,7 +22,31 @@ const streamChunks = require("./helpers/streamChunks");
21
22
  /** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
22
23
  /** @typedef {import("./helpers/streamChunks").Options} Options */
23
24
 
24
- const REPLACE_REGEX = /\n(?=.|\s)/g;
25
+ // `/\n/g` (no lookahead) lets V8's regex compiler take its
26
+ // literal-character fast path; the previous `/\n(?=.|\s)/g` form
27
+ // disabled it. Output stays identical because `buildPrefixed` strips
28
+ // the spurious trailing prefix when the input ended with a newline.
29
+ const NEWLINE_REGEX = /\n/g;
30
+
31
+ /**
32
+ * Prepend `prefix` and insert `prefix` after every newline that has
33
+ * content following — the original `/\n(?=.|\s)/g` semantics, but
34
+ * implemented as a fast-path regex + tail-strip.
35
+ * @param {string} prefix prefix
36
+ * @param {string} node underlying source string
37
+ * @returns {string} prefixed string
38
+ */
39
+ const buildPrefixed = (prefix, node) => {
40
+ if (prefix.length === 0) return node;
41
+ const replaced = node.replace(NEWLINE_REGEX, `\n${prefix}`);
42
+ const len = node.length;
43
+ // `/\n/g` matches the trailing newline too, so the replace appended
44
+ // a spurious prefix at the end. Trim it.
45
+ if (len > 0 && node.charCodeAt(len - 1) === 10) {
46
+ return prefix + replaced.slice(0, replaced.length - prefix.length);
47
+ }
48
+ return prefix + replaced;
49
+ };
25
50
 
26
51
  class PrefixSource extends Source {
27
52
  /**
@@ -31,12 +56,10 @@ class PrefixSource extends Source {
31
56
  constructor(prefix, source) {
32
57
  super();
33
58
  /**
34
- * @private
35
59
  * @type {string}
36
60
  */
37
61
  this._prefix = prefix;
38
62
  /**
39
- * @private
40
63
  * @type {Source}
41
64
  */
42
65
  this._source =
@@ -57,12 +80,19 @@ class PrefixSource extends Source {
57
80
  * @returns {SourceValue} source
58
81
  */
59
82
  source() {
60
- const node = /** @type {string} */ (this._source.source());
61
- const prefix = this._prefix;
62
- return prefix + node.replace(REPLACE_REGEX, `\n${prefix}`);
83
+ return buildPrefixed(
84
+ this._prefix,
85
+ /** @type {string} */ (this._source.source()),
86
+ );
63
87
  }
64
88
 
65
- // TODO efficient buffer() implementation
89
+ // buffer() / buffers() / size() inherit from Source.prototype.
90
+ // Source.buffer() does Buffer.from(this.source(), "utf8") — cheaper
91
+ // in CodSpeed instruction count than any safe override we tried
92
+ // (caching is unsafe with mutable child; a JS-side splice loop
93
+ // regressed ~5x in instruction count). Speeding up source() via the
94
+ // simpler regex above lifts buffer(), buffers(), size(), and any
95
+ // other `this.source()`-using path with no override needed.
66
96
 
67
97
  /**
68
98
  * @param {MapOptions=} options map options
@@ -141,10 +171,7 @@ class PrefixSource extends Source {
141
171
  generatedColumn === 0
142
172
  ? 0
143
173
  : prefixOffset + /** @type {number} */ (generatedColumn),
144
- source:
145
- source !== undefined
146
- ? prefix + source.replace(REPLACE_REGEX, `\n${prefix}`)
147
- : undefined,
174
+ source: source !== undefined ? buildPrefixed(prefix, source) : undefined,
148
175
  };
149
176
  }
150
177
 
@@ -157,6 +184,28 @@ class PrefixSource extends Source {
157
184
  this._source.updateHash(hash);
158
185
  hash.update(this._prefix);
159
186
  }
187
+
188
+ /**
189
+ * Release cached data held by this source. clearCache is a memory
190
+ * hint: it never affects correctness or output, only how expensive
191
+ * the next read is. Subclasses override; the base is a no-op so
192
+ * every Source supports the call. Composite sources always recurse
193
+ * into wrapped sources. When the same child is reachable via several
194
+ * parents (e.g. modules shared across webpack chunks), pass a shared
195
+ * `visited` WeakSet so each subtree is walked at most once.
196
+ * Not safe to call concurrently with source/map/sourceAndMap/
197
+ * streamChunks/updateHash on the same instance.
198
+ * @param {ClearCacheOptions=} options selectors
199
+ * @param {WeakSet<Source>=} visited de-duplication set shared across calls
200
+ * @returns {void}
201
+ */
202
+ clearCache(options, visited) {
203
+ if (visited !== undefined && visited.has(this)) return;
204
+ let v = visited;
205
+ if (v === undefined) v = new WeakSet();
206
+ v.add(this);
207
+ this._source.clearCache(options, v);
208
+ }
160
209
  }
161
210
 
162
211
  module.exports = PrefixSource;