@atlaspack/core 2.33.0 → 2.34.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.
- package/CHANGELOG.md +46 -0
- package/dist/PackagerRunner.js +5 -35
- package/dist/applyRuntimes.js +2 -1
- package/dist/requests/AssetGraphRequestRust.js +71 -18
- package/dist/requests/BundleGraphRequest.js +1 -0
- package/dist/requests/WriteBundleRequest.js +132 -9
- package/dist/requests/WriteBundlesRequest.js +15 -9
- package/lib/PackagerRunner.js +5 -35
- package/lib/applyRuntimes.js +1 -1
- package/lib/requests/AssetGraphRequestRust.js +40 -19
- package/lib/requests/BundleGraphRequest.js +2 -1
- package/lib/requests/WriteBundleRequest.js +130 -7
- package/lib/requests/WriteBundlesRequest.js +12 -5
- package/lib/types/requests/WriteBundleRequest.d.ts +20 -1
- package/package.json +15 -15
- package/src/PackagerRunner.ts +6 -46
- package/src/applyRuntimes.ts +3 -1
- package/src/requests/AssetGraphRequestRust.ts +40 -19
- package/src/requests/BundleGraphRequest.ts +1 -0
- package/src/requests/WriteBundleRequest.ts +180 -10
- package/src/requests/WriteBundlesRequest.ts +25 -13
- package/test/requests/WriteBundleRequest.test.ts +239 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -13,6 +13,7 @@ import type {ProjectPath} from '../projectPath';
|
|
|
13
13
|
import {HASH_REF_HASH_LEN, HASH_REF_PREFIX} from '../constants';
|
|
14
14
|
import nullthrows from 'nullthrows';
|
|
15
15
|
import path from 'path';
|
|
16
|
+
import url from 'url';
|
|
16
17
|
import {NamedBundle} from '../public/Bundle';
|
|
17
18
|
import {blobToStream, TapStream} from '@atlaspack/utils';
|
|
18
19
|
import {Readable, Transform, pipeline} from 'stream';
|
|
@@ -40,9 +41,18 @@ import {PluginTracer, tracer} from '@atlaspack/profiler';
|
|
|
40
41
|
import {requestTypes} from '../RequestTracker';
|
|
41
42
|
import {getFeatureFlag} from '@atlaspack/feature-flags';
|
|
42
43
|
import {fromEnvironmentId} from '../EnvironmentManager';
|
|
44
|
+
import SourceMap from '@atlaspack/source-map';
|
|
43
45
|
|
|
44
46
|
const HASH_REF_PREFIX_LEN = HASH_REF_PREFIX.length;
|
|
45
47
|
const BOUNDARY_LENGTH = HASH_REF_PREFIX.length + 32 - 1;
|
|
48
|
+
const HASH_REF_PLACEHOLDER_LEN = HASH_REF_PREFIX_LEN + HASH_REF_HASH_LEN;
|
|
49
|
+
|
|
50
|
+
export type HashRefReplacement = {
|
|
51
|
+
line: number;
|
|
52
|
+
column: number;
|
|
53
|
+
originalLength: number;
|
|
54
|
+
newLength: number;
|
|
55
|
+
};
|
|
46
56
|
|
|
47
57
|
type WriteBundleRequestInput = {
|
|
48
58
|
bundleGraph: BundleGraph;
|
|
@@ -158,6 +168,9 @@ async function run({input, options, api}) {
|
|
|
158
168
|
let {devDeps, invalidDevDeps} = await getDevDepRequests(api);
|
|
159
169
|
invalidateDevDeps(invalidDevDeps, options, config);
|
|
160
170
|
|
|
171
|
+
const bundleReplacements = getFeatureFlag('fixSourceMapHashRefs')
|
|
172
|
+
? []
|
|
173
|
+
: undefined;
|
|
161
174
|
await writeFiles(
|
|
162
175
|
contentStream,
|
|
163
176
|
info,
|
|
@@ -169,17 +182,50 @@ async function run({input, options, api}) {
|
|
|
169
182
|
writeOptions,
|
|
170
183
|
devDeps,
|
|
171
184
|
api,
|
|
185
|
+
bundleReplacements,
|
|
172
186
|
);
|
|
173
187
|
|
|
174
188
|
const hasSourceMap = getFeatureFlag('cachePerformanceImprovements')
|
|
175
189
|
? await options.cache.hasLargeBlob(mapKey)
|
|
176
190
|
: await options.cache.has(mapKey);
|
|
177
191
|
if (mapKey && env.sourceMap && !env.sourceMap.inline && hasSourceMap) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
let mapStream: Readable;
|
|
193
|
+
if (
|
|
194
|
+
getFeatureFlag('fixSourceMapHashRefs') &&
|
|
195
|
+
bundleReplacements &&
|
|
196
|
+
bundleReplacements.length > 0
|
|
197
|
+
) {
|
|
198
|
+
const mapEntry = getFeatureFlag('cachePerformanceImprovements')
|
|
199
|
+
? await options.cache.getLargeBlob(mapKey)
|
|
200
|
+
: await options.cache.getBlob(mapKey);
|
|
201
|
+
const mapBuffer = Buffer.isBuffer(mapEntry)
|
|
202
|
+
? mapEntry
|
|
203
|
+
: Buffer.from(mapEntry);
|
|
204
|
+
const projectRoot =
|
|
205
|
+
typeof options.projectRoot === 'string'
|
|
206
|
+
? options.projectRoot
|
|
207
|
+
: String(options.projectRoot);
|
|
208
|
+
const sourceMap = new SourceMap(projectRoot, mapBuffer);
|
|
209
|
+
applyReplacementsToSourceMap(sourceMap, bundleReplacements);
|
|
210
|
+
const mapJson = await sourceMap.stringify({
|
|
211
|
+
format: 'string',
|
|
212
|
+
file: name,
|
|
213
|
+
sourceRoot: computeSourceMapRoot(bundle, options),
|
|
214
|
+
});
|
|
215
|
+
mapStream = blobToStream(
|
|
216
|
+
Buffer.from(
|
|
217
|
+
typeof mapJson === 'string' ? mapJson : JSON.stringify(mapJson),
|
|
218
|
+
'utf8',
|
|
219
|
+
),
|
|
220
|
+
);
|
|
221
|
+
} else {
|
|
222
|
+
const mapEntry = getFeatureFlag('cachePerformanceImprovements')
|
|
223
|
+
? await options.cache.getLargeBlob(mapKey)
|
|
224
|
+
: await options.cache.getBlob(mapKey);
|
|
225
|
+
mapStream = blobToStream(mapEntry);
|
|
226
|
+
}
|
|
181
227
|
await writeFiles(
|
|
182
|
-
|
|
228
|
+
mapStream,
|
|
183
229
|
info,
|
|
184
230
|
hashRefToNameHash,
|
|
185
231
|
options,
|
|
@@ -206,6 +252,88 @@ async function run({input, options, api}) {
|
|
|
206
252
|
return res;
|
|
207
253
|
}
|
|
208
254
|
|
|
255
|
+
export function applyReplacementsToSourceMap(
|
|
256
|
+
sourceMap: SourceMap,
|
|
257
|
+
replacements: HashRefReplacement[],
|
|
258
|
+
): void {
|
|
259
|
+
if (replacements.length === 0) return;
|
|
260
|
+
const sorted = [...replacements].sort(
|
|
261
|
+
(a, b) => a.line - b.line || a.column - b.column,
|
|
262
|
+
);
|
|
263
|
+
for (const r of sorted) {
|
|
264
|
+
const delta = r.newLength - r.originalLength;
|
|
265
|
+
if (delta !== 0) {
|
|
266
|
+
// r.column is in post-replacement coordinates (matching the already-shifted
|
|
267
|
+
// source map state after previous offsetColumns calls). The end of the
|
|
268
|
+
// placeholder in these coordinates is simply r.column + r.originalLength.
|
|
269
|
+
const offsetStartColumn = r.column + r.originalLength;
|
|
270
|
+
const line1Based = r.line + 1;
|
|
271
|
+
if (line1Based >= 1 && offsetStartColumn + delta >= 0) {
|
|
272
|
+
sourceMap.offsetColumns(line1Based, offsetStartColumn, delta);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Computes the sourceRoot for a source map file. This is the relative path from
|
|
280
|
+
* the output directory back to the project root, so that source paths (stored
|
|
281
|
+
* relative to projectRoot) resolve correctly from the .map file location.
|
|
282
|
+
*
|
|
283
|
+
* Returns undefined when sources are inlined (inlineSources), since the browser
|
|
284
|
+
* doesn't need to fetch them and sourceRoot would be unnecessary.
|
|
285
|
+
*
|
|
286
|
+
* This logic must stay in sync with PackagerRunner.generateSourceMap.
|
|
287
|
+
*/
|
|
288
|
+
export function computeSourceMapRoot(
|
|
289
|
+
bundle: Bundle,
|
|
290
|
+
options: AtlaspackOptions,
|
|
291
|
+
): string | undefined {
|
|
292
|
+
let name = nullthrows(bundle.name);
|
|
293
|
+
let filePath = joinProjectPath(bundle.target.distDir, name);
|
|
294
|
+
let fullPath = fromProjectPath(options.projectRoot, filePath);
|
|
295
|
+
let sourceRoot: string = path.relative(
|
|
296
|
+
path.dirname(fullPath),
|
|
297
|
+
options.projectRoot,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
let inlineSources = false;
|
|
301
|
+
|
|
302
|
+
const bundleEnv = fromEnvironmentId(bundle.env);
|
|
303
|
+
if (bundle.target) {
|
|
304
|
+
const bundleTargetEnv = fromEnvironmentId(bundle.target.env);
|
|
305
|
+
|
|
306
|
+
if (bundleEnv.sourceMap && bundleEnv.sourceMap.sourceRoot !== undefined) {
|
|
307
|
+
sourceRoot = bundleEnv.sourceMap.sourceRoot;
|
|
308
|
+
} else if (options.serveOptions && bundleTargetEnv.context === 'browser') {
|
|
309
|
+
sourceRoot = '/__parcel_source_root';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (
|
|
313
|
+
bundleEnv.sourceMap &&
|
|
314
|
+
bundleEnv.sourceMap.inlineSources !== undefined
|
|
315
|
+
) {
|
|
316
|
+
inlineSources = bundleEnv.sourceMap.inlineSources;
|
|
317
|
+
} else if (bundleTargetEnv.context !== 'node') {
|
|
318
|
+
inlineSources = options.mode === 'production';
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let isInlineMap = bundleEnv.sourceMap && bundleEnv.sourceMap.inline;
|
|
323
|
+
|
|
324
|
+
if (getFeatureFlag('omitSourcesContentInMemory') && !isInlineMap) {
|
|
325
|
+
if (!(bundleEnv.sourceMap && bundleEnv.sourceMap.inlineSources === false)) {
|
|
326
|
+
inlineSources = true;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (inlineSources) {
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return url.format(url.parse(sourceRoot + '/'));
|
|
335
|
+
}
|
|
336
|
+
|
|
209
337
|
async function writeFiles(
|
|
210
338
|
// @ts-expect-error TS2503
|
|
211
339
|
inputStream: stream.Readable,
|
|
@@ -218,6 +346,7 @@ async function writeFiles(
|
|
|
218
346
|
writeOptions: FileOptions | null | undefined,
|
|
219
347
|
devDeps: Map<string, string>,
|
|
220
348
|
api: RunAPI<PackagedBundleInfo>,
|
|
349
|
+
bundleReplacements?: HashRefReplacement[],
|
|
221
350
|
) {
|
|
222
351
|
let compressors = await config.getCompressors(
|
|
223
352
|
fromProjectPathRelative(filePath),
|
|
@@ -225,7 +354,7 @@ async function writeFiles(
|
|
|
225
354
|
let fullPath = fromProjectPath(options.projectRoot, filePath);
|
|
226
355
|
|
|
227
356
|
let stream = info.hashReferences.length
|
|
228
|
-
? inputStream.pipe(replaceStream(hashRefToNameHash))
|
|
357
|
+
? inputStream.pipe(replaceStream(hashRefToNameHash, bundleReplacements))
|
|
229
358
|
: inputStream;
|
|
230
359
|
|
|
231
360
|
let promises: Array<Promise<undefined>> = [];
|
|
@@ -314,9 +443,30 @@ async function runCompressor(
|
|
|
314
443
|
}
|
|
315
444
|
}
|
|
316
445
|
|
|
317
|
-
function
|
|
446
|
+
function advanceLineColumn(
|
|
447
|
+
line: number,
|
|
448
|
+
column: number,
|
|
449
|
+
buf: Buffer,
|
|
450
|
+
): {line: number; column: number} {
|
|
451
|
+
for (let i = 0; i < buf.length; i++) {
|
|
452
|
+
if (buf[i] === 0x0a) {
|
|
453
|
+
line++;
|
|
454
|
+
column = 0;
|
|
455
|
+
} else {
|
|
456
|
+
column++;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return {line, column};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function replaceStream(
|
|
463
|
+
hashRefToNameHash: Map<string, string>,
|
|
464
|
+
replacements?: HashRefReplacement[],
|
|
465
|
+
) {
|
|
318
466
|
let boundaryStr = Buffer.alloc(0);
|
|
319
467
|
let replaced = Buffer.alloc(0);
|
|
468
|
+
let outputLine = 0;
|
|
469
|
+
let outputColumn = 0;
|
|
320
470
|
return new Transform({
|
|
321
471
|
transform(
|
|
322
472
|
chunk: Buffer | string,
|
|
@@ -347,22 +497,42 @@ function replaceStream(hashRefToNameHash: Map<string, string>) {
|
|
|
347
497
|
.subarray(matchI, matchI + HASH_REF_PREFIX_LEN + HASH_REF_HASH_LEN)
|
|
348
498
|
.toString();
|
|
349
499
|
let replacement = Buffer.from(hashRefToNameHash.get(match) ?? match);
|
|
500
|
+
// Copy pre-match content FIRST so position calculation includes it
|
|
350
501
|
replaced.set(str.subarray(lastMatchI, matchI), replacedLength);
|
|
351
502
|
replacedLength += matchI - lastMatchI;
|
|
503
|
+
if (replacements) {
|
|
504
|
+
const pos = advanceLineColumn(
|
|
505
|
+
outputLine,
|
|
506
|
+
outputColumn,
|
|
507
|
+
replaced.subarray(0, replacedLength),
|
|
508
|
+
);
|
|
509
|
+
replacements.push({
|
|
510
|
+
line: pos.line,
|
|
511
|
+
column: pos.column,
|
|
512
|
+
originalLength: HASH_REF_PLACEHOLDER_LEN,
|
|
513
|
+
newLength: replacement.byteLength,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
352
516
|
replaced.set(replacement, replacedLength);
|
|
353
517
|
replacedLength += replacement.byteLength;
|
|
354
518
|
lastMatchI = matchI + HASH_REF_PREFIX_LEN + HASH_REF_HASH_LEN;
|
|
355
519
|
}
|
|
356
520
|
}
|
|
357
521
|
|
|
522
|
+
const pushLen = replacedLength - BOUNDARY_LENGTH;
|
|
523
|
+
const pushed = advanceLineColumn(
|
|
524
|
+
outputLine,
|
|
525
|
+
outputColumn,
|
|
526
|
+
replaced.subarray(0, pushLen),
|
|
527
|
+
);
|
|
528
|
+
outputLine = pushed.line;
|
|
529
|
+
outputColumn = pushed.column;
|
|
530
|
+
|
|
358
531
|
boundaryStr = replaced.subarray(
|
|
359
532
|
replacedLength - BOUNDARY_LENGTH,
|
|
360
533
|
replacedLength,
|
|
361
534
|
);
|
|
362
|
-
let strUpToBoundary = replaced.subarray(
|
|
363
|
-
0,
|
|
364
|
-
replacedLength - BOUNDARY_LENGTH,
|
|
365
|
-
);
|
|
535
|
+
let strUpToBoundary = replaced.subarray(0, pushLen);
|
|
366
536
|
cb(null, strUpToBoundary);
|
|
367
537
|
},
|
|
368
538
|
|
|
@@ -22,6 +22,16 @@ import {createPackageRequest} from './PackageRequest';
|
|
|
22
22
|
import createWriteBundleRequest from './WriteBundleRequest';
|
|
23
23
|
import {debugTools} from '@atlaspack/utils';
|
|
24
24
|
|
|
25
|
+
/** Length of the hash suffix in output filenames (e.g. .runtime.13dc01ac.js). */
|
|
26
|
+
const NAME_HASH_DISPLAY_LEN = 8;
|
|
27
|
+
|
|
28
|
+
/** Use at most NAME_HASH_DISPLAY_LEN chars for the name hash so filenames stay short. */
|
|
29
|
+
function nameHashForFilename(hash: string): string {
|
|
30
|
+
return hash.length <= NAME_HASH_DISPLAY_LEN
|
|
31
|
+
? hash
|
|
32
|
+
: hash.slice(-NAME_HASH_DISPLAY_LEN);
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
type WriteBundlesRequestInput = {
|
|
26
36
|
bundleGraph: BundleGraph;
|
|
27
37
|
optionsRef: SharedReference;
|
|
@@ -111,12 +121,12 @@ async function run({
|
|
|
111
121
|
// Do not package and write placeholder bundles to disk. We just
|
|
112
122
|
// need to update the name so other bundles can reference it.
|
|
113
123
|
if (bundle.isPlaceholder) {
|
|
114
|
-
|
|
115
|
-
hashRefToNameHash.set(bundle.hashReference,
|
|
124
|
+
const nameHash = nameHashForFilename(bundle.id);
|
|
125
|
+
hashRefToNameHash.set(bundle.hashReference, nameHash);
|
|
116
126
|
let name = nullthrows(
|
|
117
127
|
bundle.name,
|
|
118
128
|
`Expected ${bundle.type} bundle to have a name`,
|
|
119
|
-
).replace(bundle.hashReference,
|
|
129
|
+
).replace(bundle.hashReference, nameHash);
|
|
120
130
|
res.set(bundle.id, {
|
|
121
131
|
filePath: joinProjectPath(bundle.target.distDir, name),
|
|
122
132
|
bundleId: bundle.id,
|
|
@@ -178,9 +188,9 @@ async function run({
|
|
|
178
188
|
if (!info.hashReferences.length) {
|
|
179
189
|
hashRefToNameHash.set(
|
|
180
190
|
bundle.hashReference,
|
|
181
|
-
|
|
182
|
-
? info.hash.
|
|
183
|
-
|
|
191
|
+
nameHashForFilename(
|
|
192
|
+
options.shouldContentHash ? info.hash : bundle.id,
|
|
193
|
+
),
|
|
184
194
|
);
|
|
185
195
|
let writeBundleRequest = createWriteBundleRequest({
|
|
186
196
|
bundle,
|
|
@@ -256,13 +266,15 @@ function assignComplexNameHashes(
|
|
|
256
266
|
}
|
|
257
267
|
hashRefToNameHash.set(
|
|
258
268
|
bundle.hashReference,
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
269
|
+
nameHashForFilename(
|
|
270
|
+
options.shouldContentHash
|
|
271
|
+
? hashString(
|
|
272
|
+
[...getBundlesIncludedInHash(bundle.id, bundleInfoMap)]
|
|
273
|
+
.map((bundleId) => bundleInfoMap[bundleId].hash)
|
|
274
|
+
.join(':'),
|
|
275
|
+
)
|
|
276
|
+
: bundle.id,
|
|
277
|
+
),
|
|
266
278
|
);
|
|
267
279
|
}
|
|
268
280
|
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import SourceMap from '@atlaspack/source-map';
|
|
3
|
+
import {
|
|
4
|
+
applyReplacementsToSourceMap,
|
|
5
|
+
type HashRefReplacement,
|
|
6
|
+
} from '../../src/requests/WriteBundleRequest';
|
|
7
|
+
|
|
8
|
+
const HASH_REF = 'HASH_REF_0123456789abcdef'; // 25 chars
|
|
9
|
+
const HASH_REPLACEMENT = 'a1b2c3d4'; // 8 chars
|
|
10
|
+
const HASH_REF_LEN = HASH_REF.length; // 25
|
|
11
|
+
const REPLACEMENT_LEN = HASH_REPLACEMENT.length; // 8
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build a single-line code string with HASH_REF placeholders at known positions.
|
|
15
|
+
* Returns original code, replaced code, correct replacement coordinates, and
|
|
16
|
+
* identifier position tracking in both coordinate spaces.
|
|
17
|
+
*/
|
|
18
|
+
function buildCodeWithHashRefs(
|
|
19
|
+
segments: Array<{type: 'code'; text: string} | {type: 'hashref'}>,
|
|
20
|
+
) {
|
|
21
|
+
let code = '';
|
|
22
|
+
const hashPositions: Array<{column: number}> = [];
|
|
23
|
+
const identifierPositions = new Map<string, number>();
|
|
24
|
+
|
|
25
|
+
for (const seg of segments) {
|
|
26
|
+
if (seg.type === 'hashref') {
|
|
27
|
+
hashPositions.push({column: code.length});
|
|
28
|
+
code += HASH_REF;
|
|
29
|
+
} else {
|
|
30
|
+
const regex = /[A-Z][A-Z_0-9]{3,}/g;
|
|
31
|
+
let match;
|
|
32
|
+
while ((match = regex.exec(seg.text)) !== null) {
|
|
33
|
+
identifierPositions.set(match[0], code.length + match.index);
|
|
34
|
+
}
|
|
35
|
+
code += seg.text;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Build replaced code and compute CORRECT replacement coordinates
|
|
40
|
+
let replacedCode = '';
|
|
41
|
+
const correctReplacements: HashRefReplacement[] = [];
|
|
42
|
+
let srcIdx = 0;
|
|
43
|
+
for (const hp of hashPositions) {
|
|
44
|
+
replacedCode += code.slice(srcIdx, hp.column);
|
|
45
|
+
correctReplacements.push({
|
|
46
|
+
line: 0,
|
|
47
|
+
column: replacedCode.length,
|
|
48
|
+
originalLength: HASH_REF_LEN,
|
|
49
|
+
newLength: REPLACEMENT_LEN,
|
|
50
|
+
});
|
|
51
|
+
replacedCode += HASH_REPLACEMENT;
|
|
52
|
+
srcIdx = hp.column + HASH_REF_LEN;
|
|
53
|
+
}
|
|
54
|
+
replacedCode += code.slice(srcIdx);
|
|
55
|
+
|
|
56
|
+
// Track where identifiers end up in the replaced code
|
|
57
|
+
const replacedIdentifierPositions = new Map<string, number>();
|
|
58
|
+
for (const [name] of identifierPositions) {
|
|
59
|
+
const idx = replacedCode.indexOf(name);
|
|
60
|
+
if (idx >= 0) replacedIdentifierPositions.set(name, idx);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
originalCode: code,
|
|
65
|
+
replacedCode,
|
|
66
|
+
correctReplacements,
|
|
67
|
+
identifierPositions,
|
|
68
|
+
replacedIdentifierPositions,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe('applyReplacementsToSourceMap', () => {
|
|
73
|
+
describe('with correct replacement coordinates', () => {
|
|
74
|
+
it('should correctly adjust a single HASH_REF replacement', () => {
|
|
75
|
+
const {
|
|
76
|
+
correctReplacements,
|
|
77
|
+
identifierPositions,
|
|
78
|
+
replacedIdentifierPositions,
|
|
79
|
+
} = buildCodeWithHashRefs([
|
|
80
|
+
{type: 'hashref'},
|
|
81
|
+
{type: 'code', text: ';var x=SOME_IDENT;'},
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const origCol = identifierPositions.get('SOME_IDENT')!;
|
|
85
|
+
const expectedCol = replacedIdentifierPositions.get('SOME_IDENT')!;
|
|
86
|
+
|
|
87
|
+
const sm = new SourceMap('/');
|
|
88
|
+
sm.addIndexedMapping({
|
|
89
|
+
generated: {line: 1, column: origCol},
|
|
90
|
+
original: {line: 10, column: 5},
|
|
91
|
+
source: 'test.js',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
applyReplacementsToSourceMap(sm, correctReplacements);
|
|
95
|
+
|
|
96
|
+
const mappings = sm.getMap().mappings;
|
|
97
|
+
const mapping = mappings.find(
|
|
98
|
+
(m) => m.original?.line === 10 && m.original?.column === 5,
|
|
99
|
+
);
|
|
100
|
+
assert.ok(mapping, 'Mapping should exist');
|
|
101
|
+
assert.strictEqual(mapping!.generated.column, expectedCol);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should correctly adjust multiple HASH_REF replacements on the same line', () => {
|
|
105
|
+
const {
|
|
106
|
+
correctReplacements,
|
|
107
|
+
identifierPositions,
|
|
108
|
+
replacedIdentifierPositions,
|
|
109
|
+
} = buildCodeWithHashRefs([
|
|
110
|
+
{type: 'hashref'},
|
|
111
|
+
{type: 'code', text: ';var a=IDENT_ALPHA;require("'},
|
|
112
|
+
{type: 'hashref'},
|
|
113
|
+
{type: 'code', text: '");var b=IDENT_BETA;require("'},
|
|
114
|
+
{type: 'hashref'},
|
|
115
|
+
{type: 'code', text: '");var c=IDENT_GAMMA;'},
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
const sm = new SourceMap('/');
|
|
119
|
+
sm.addIndexedMapping({
|
|
120
|
+
generated: {line: 1, column: identifierPositions.get('IDENT_ALPHA')!},
|
|
121
|
+
original: {line: 10, column: 0},
|
|
122
|
+
source: 'test.js',
|
|
123
|
+
});
|
|
124
|
+
sm.addIndexedMapping({
|
|
125
|
+
generated: {line: 1, column: identifierPositions.get('IDENT_BETA')!},
|
|
126
|
+
original: {line: 20, column: 0},
|
|
127
|
+
source: 'test.js',
|
|
128
|
+
});
|
|
129
|
+
sm.addIndexedMapping({
|
|
130
|
+
generated: {line: 1, column: identifierPositions.get('IDENT_GAMMA')!},
|
|
131
|
+
original: {line: 30, column: 0},
|
|
132
|
+
source: 'test.js',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
applyReplacementsToSourceMap(sm, correctReplacements);
|
|
136
|
+
|
|
137
|
+
const mappings = sm.getMap().mappings;
|
|
138
|
+
for (const [name, origLine] of [
|
|
139
|
+
['IDENT_ALPHA', 10],
|
|
140
|
+
['IDENT_BETA', 20],
|
|
141
|
+
['IDENT_GAMMA', 30],
|
|
142
|
+
] as const) {
|
|
143
|
+
const mapping = mappings.find((m) => m.original?.line === origLine);
|
|
144
|
+
const expectedCol = replacedIdentifierPositions.get(name)!;
|
|
145
|
+
assert.ok(mapping, `${name} mapping should exist`);
|
|
146
|
+
assert.strictEqual(
|
|
147
|
+
mapping!.generated.column,
|
|
148
|
+
expectedCol,
|
|
149
|
+
`${name}: expected col ${expectedCol}, got ${mapping!.generated.column}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle 10 replacements on the same line', () => {
|
|
155
|
+
const segments: Array<{type: 'code'; text: string} | {type: 'hashref'}> =
|
|
156
|
+
[];
|
|
157
|
+
for (let i = 0; i < 10; i++) {
|
|
158
|
+
segments.push({type: 'hashref'});
|
|
159
|
+
segments.push({
|
|
160
|
+
type: 'code',
|
|
161
|
+
text: `;var x${i}=TARGET_${String(i).padStart(2, '0')};require("`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
segments.push({type: 'code', text: '");'});
|
|
165
|
+
|
|
166
|
+
const {
|
|
167
|
+
correctReplacements,
|
|
168
|
+
identifierPositions,
|
|
169
|
+
replacedIdentifierPositions,
|
|
170
|
+
} = buildCodeWithHashRefs(segments);
|
|
171
|
+
|
|
172
|
+
const sm = new SourceMap('/');
|
|
173
|
+
for (let i = 0; i < 10; i++) {
|
|
174
|
+
const name = `TARGET_${String(i).padStart(2, '0')}`;
|
|
175
|
+
sm.addIndexedMapping({
|
|
176
|
+
generated: {line: 1, column: identifierPositions.get(name)!},
|
|
177
|
+
original: {line: (i + 1) * 10, column: 0},
|
|
178
|
+
source: 'test.js',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
applyReplacementsToSourceMap(sm, correctReplacements);
|
|
183
|
+
|
|
184
|
+
const mappings = sm.getMap().mappings;
|
|
185
|
+
for (let i = 0; i < 10; i++) {
|
|
186
|
+
const name = `TARGET_${String(i).padStart(2, '0')}`;
|
|
187
|
+
const mapping = mappings.find((m) => m.original?.line === (i + 1) * 10);
|
|
188
|
+
const expectedCol = replacedIdentifierPositions.get(name)!;
|
|
189
|
+
assert.ok(mapping, `${name} mapping should exist`);
|
|
190
|
+
assert.strictEqual(
|
|
191
|
+
mapping!.generated.column,
|
|
192
|
+
expectedCol,
|
|
193
|
+
`${name}: expected col ${expectedCol}, got ${mapping!.generated.column}`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should not affect mappings before the first HASH_REF', () => {
|
|
199
|
+
const {
|
|
200
|
+
correctReplacements,
|
|
201
|
+
identifierPositions,
|
|
202
|
+
replacedIdentifierPositions,
|
|
203
|
+
} = buildCodeWithHashRefs([
|
|
204
|
+
{type: 'code', text: 'var BEFORE_HASH=1;'},
|
|
205
|
+
{type: 'hashref'},
|
|
206
|
+
{type: 'code', text: ';var AFTER_HASH=2;'},
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
const sm = new SourceMap('/');
|
|
210
|
+
sm.addIndexedMapping({
|
|
211
|
+
generated: {line: 1, column: identifierPositions.get('BEFORE_HASH')!},
|
|
212
|
+
original: {line: 1, column: 0},
|
|
213
|
+
source: 'test.js',
|
|
214
|
+
});
|
|
215
|
+
sm.addIndexedMapping({
|
|
216
|
+
generated: {line: 1, column: identifierPositions.get('AFTER_HASH')!},
|
|
217
|
+
original: {line: 2, column: 0},
|
|
218
|
+
source: 'test.js',
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
applyReplacementsToSourceMap(sm, correctReplacements);
|
|
222
|
+
|
|
223
|
+
const mappings = sm.getMap().mappings;
|
|
224
|
+
const beforeMapping = mappings.find((m) => m.original?.line === 1);
|
|
225
|
+
assert.ok(beforeMapping);
|
|
226
|
+
assert.strictEqual(
|
|
227
|
+
beforeMapping!.generated.column,
|
|
228
|
+
replacedIdentifierPositions.get('BEFORE_HASH'),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const afterMapping = mappings.find((m) => m.original?.line === 2);
|
|
232
|
+
assert.ok(afterMapping);
|
|
233
|
+
assert.strictEqual(
|
|
234
|
+
afterMapping!.generated.column,
|
|
235
|
+
replacedIdentifierPositions.get('AFTER_HASH'),
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|