@atlaspack/packager-js 2.25.9 → 2.25.11-dev-fix-sourcemaps-50acc1acf.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 +6 -0
- package/LICENSE +201 -0
- package/dist/ScopeHoistingPackager.js +77 -5
- package/lib/ScopeHoistingPackager.js +83 -5
- package/lib/types/ScopeHoistingPackager.d.ts +29 -0
- package/package.json +13 -10
- package/src/ScopeHoistingPackager.ts +87 -6
- package/test/appendHoistedValues.test.ts +94 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -53,6 +53,29 @@ const REPLACEMENT_RE =
|
|
|
53
53
|
/\n|import\s+"([0-9a-f]{16,20}:.+?)";|(?:\$[0-9a-f]{16,20}\$exports)|(?:\$[0-9a-f]{16,20}\$(?:import|importAsync|require)\$[0-9a-f]+(?:\$[0-9a-f]+)?)/g;
|
|
54
54
|
|
|
55
55
|
const BUILTINS = Object.keys(globals.builtin);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Joins filtered hoisted parcelRequire values into the bundle output,
|
|
59
|
+
* returning the text to append and the number of newlines it adds.
|
|
60
|
+
*
|
|
61
|
+
* Returns `{text: '', lineCount: 0}` when there are no values, to avoid
|
|
62
|
+
* emitting a stray leading `\n` (which would over-count lineCount and
|
|
63
|
+
* desynchronise the bundle source-map offset bookkeeping).
|
|
64
|
+
*
|
|
65
|
+
* Exported for direct unit testing of the lineCount bookkeeping.
|
|
66
|
+
*/
|
|
67
|
+
export function appendHoistedValues(values: ReadonlyArray<string>): {
|
|
68
|
+
text: string;
|
|
69
|
+
lineCount: number;
|
|
70
|
+
} {
|
|
71
|
+
if (values.length === 0) {
|
|
72
|
+
return {text: '', lineCount: 0};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
text: '\n' + values.join('\n'),
|
|
76
|
+
lineCount: values.length,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
56
79
|
const GLOBALS_BY_CONTEXT = {
|
|
57
80
|
browser: new Set([...BUILTINS, ...Object.keys(globals.browser)]),
|
|
58
81
|
'web-worker': new Set([...BUILTINS, ...Object.keys(globals.worker)]),
|
|
@@ -191,9 +214,13 @@ export class ScopeHoistingPackager {
|
|
|
191
214
|
let [content, map, lines] = this.visitAsset(asset);
|
|
192
215
|
|
|
193
216
|
if (sourceMap && map) {
|
|
217
|
+
this.addAssetBoundaryMarker(sourceMap, asset, lineCount);
|
|
194
218
|
sourceMap.addSourceMap(map, lineCount);
|
|
195
219
|
} else if (this.bundle.env.sourceMap) {
|
|
196
220
|
sourceMap = map;
|
|
221
|
+
if (sourceMap) {
|
|
222
|
+
this.addAssetBoundaryMarker(sourceMap, asset, lineCount);
|
|
223
|
+
}
|
|
197
224
|
}
|
|
198
225
|
|
|
199
226
|
res += content + '\n';
|
|
@@ -334,6 +361,7 @@ export class ScopeHoistingPackager {
|
|
|
334
361
|
this.parcelRequireName,
|
|
335
362
|
);
|
|
336
363
|
if (sourceMap && map) {
|
|
364
|
+
this.addAssetBoundaryMarker(sourceMap, mainEntry, lineCount);
|
|
337
365
|
// @ts-expect-error TS2339 - addSourceMap method exists but missing from @parcel/source-map type definitions
|
|
338
366
|
sourceMap.addSourceMap(map, lineCount);
|
|
339
367
|
}
|
|
@@ -696,6 +724,34 @@ export class ScopeHoistingPackager {
|
|
|
696
724
|
return this.buildAsset(asset, code, map);
|
|
697
725
|
}
|
|
698
726
|
|
|
727
|
+
/**
|
|
728
|
+
* Insert a single "boundary marker" mapping at the start of the line where
|
|
729
|
+
* `asset`'s source map is about to be appended. Without this marker, source
|
|
730
|
+
* map consumers (and downstream optimizers such as swc that need to compose
|
|
731
|
+
* via nearest-neighbour lookups) will attribute the columns at the start of
|
|
732
|
+
* this asset's region to the *previous* asset's last mapping, because there
|
|
733
|
+
* is no mapping anchoring the start of the new asset. This is especially
|
|
734
|
+
* visible for assets with sparse maps (e.g. codegen'd `.graphql.ts` files
|
|
735
|
+
* from Relay) whose first mapping may be hundreds of columns into the line.
|
|
736
|
+
*
|
|
737
|
+
* The marker points at `(asset.filePath, 1, 0)`. Any mapping the asset's
|
|
738
|
+
* own map provides at the same generated position will override this one
|
|
739
|
+
* (last-write-wins inside `parcel_sourcemap`).
|
|
740
|
+
*/
|
|
741
|
+
addAssetBoundaryMarker(
|
|
742
|
+
sourceMap: SourceMap,
|
|
743
|
+
asset: Asset,
|
|
744
|
+
lineOffset: number,
|
|
745
|
+
): void {
|
|
746
|
+
if (!sourceMap) return;
|
|
747
|
+
let source = this.getAssetFilePath(asset);
|
|
748
|
+
sourceMap.addIndexedMapping({
|
|
749
|
+
generated: {line: lineOffset + 1, column: 0},
|
|
750
|
+
original: {line: 1, column: 0},
|
|
751
|
+
source,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
699
755
|
getAssetFilePath(asset: Asset): string {
|
|
700
756
|
return path.relative(this.options.projectRoot, asset.filePath);
|
|
701
757
|
}
|
|
@@ -712,6 +768,13 @@ export class ScopeHoistingPackager {
|
|
|
712
768
|
this.bundle.env.sourceMap && map
|
|
713
769
|
? new SourceMap(this.options.projectRoot, map)
|
|
714
770
|
: null;
|
|
771
|
+
// Anchor the start of this asset's region with a boundary marker so the
|
|
772
|
+
// first columns of the asset don't fall back to a previous asset's
|
|
773
|
+
// mapping during downstream source-map composition. See the
|
|
774
|
+
// addAssetBoundaryMarker JSDoc for the full rationale.
|
|
775
|
+
if (sourceMap) {
|
|
776
|
+
this.addAssetBoundaryMarker(sourceMap, asset, 0);
|
|
777
|
+
}
|
|
715
778
|
|
|
716
779
|
// If this asset is skipped, just add dependencies and not the asset's content.
|
|
717
780
|
if (this.shouldSkipAsset(asset)) {
|
|
@@ -751,6 +814,7 @@ export class ScopeHoistingPackager {
|
|
|
751
814
|
let [code, map, lines] = this.visitAsset(resolved);
|
|
752
815
|
depCode += code + '\n';
|
|
753
816
|
if (sourceMap && map) {
|
|
817
|
+
this.addAssetBoundaryMarker(sourceMap, resolved, lineCount);
|
|
754
818
|
sourceMap.addSourceMap(map, lineCount);
|
|
755
819
|
}
|
|
756
820
|
lineCount += lines + 1;
|
|
@@ -788,7 +852,9 @@ export class ScopeHoistingPackager {
|
|
|
788
852
|
code += append;
|
|
789
853
|
|
|
790
854
|
let lineCount = 0;
|
|
791
|
-
let depContent: Array<
|
|
855
|
+
let depContent: Array<
|
|
856
|
+
[string, SourceMap | null | undefined, number, Asset?]
|
|
857
|
+
> = [];
|
|
792
858
|
if (depMap.size === 0 && replacements.size === 0) {
|
|
793
859
|
// If there are no dependencies or replacements, use a simple function to count the number of lines.
|
|
794
860
|
lineCount = countLines(code) - 1;
|
|
@@ -877,7 +943,13 @@ export class ScopeHoistingPackager {
|
|
|
877
943
|
}
|
|
878
944
|
} else {
|
|
879
945
|
if (shouldWrap) {
|
|
880
|
-
|
|
946
|
+
let visited = this.visitAsset(resolved);
|
|
947
|
+
depContent.push([
|
|
948
|
+
visited[0],
|
|
949
|
+
visited[1],
|
|
950
|
+
visited[2],
|
|
951
|
+
resolved,
|
|
952
|
+
]);
|
|
881
953
|
} else {
|
|
882
954
|
let [depCode, depMap, depLines] =
|
|
883
955
|
this.visitAsset(resolved);
|
|
@@ -906,6 +978,7 @@ export class ScopeHoistingPackager {
|
|
|
906
978
|
}
|
|
907
979
|
|
|
908
980
|
if (map) {
|
|
981
|
+
this.addAssetBoundaryMarker(sourceMap, resolved, lineCount);
|
|
909
982
|
sourceMap.addSourceMap(map, lineCount);
|
|
910
983
|
}
|
|
911
984
|
}
|
|
@@ -959,10 +1032,13 @@ ${code}
|
|
|
959
1032
|
lineCount += 1;
|
|
960
1033
|
}
|
|
961
1034
|
|
|
962
|
-
for (let [depCode, map, lines] of depContent) {
|
|
1035
|
+
for (let [depCode, map, lines, depAsset] of depContent) {
|
|
963
1036
|
if (!depCode) continue;
|
|
964
1037
|
code += depCode + '\n';
|
|
965
1038
|
if (sourceMap && map) {
|
|
1039
|
+
if (depAsset) {
|
|
1040
|
+
this.addAssetBoundaryMarker(sourceMap, depAsset, lineCount);
|
|
1041
|
+
}
|
|
966
1042
|
sourceMap.addSourceMap(map, lineCount);
|
|
967
1043
|
}
|
|
968
1044
|
lineCount += lines + 1;
|
|
@@ -1453,8 +1529,13 @@ ${code}
|
|
|
1453
1529
|
this.seenHoistedRequires.add(val);
|
|
1454
1530
|
}
|
|
1455
1531
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1532
|
+
// Only emit the leading `\n` and bump the line count when we
|
|
1533
|
+
// actually have values to write. Otherwise `res += '\n' + ''` would
|
|
1534
|
+
// emit a stray blank line and the line count would over-count by
|
|
1535
|
+
// `hoisted.size` (which includes the now-filtered entries).
|
|
1536
|
+
let appended = appendHoistedValues(hoistedValues);
|
|
1537
|
+
res += appended.text;
|
|
1538
|
+
lineCount += appended.lineCount;
|
|
1458
1539
|
} else {
|
|
1459
1540
|
res += '\n' + [...hoisted.values()].join('\n');
|
|
1460
1541
|
lineCount += hoisted.size;
|
|
@@ -1703,7 +1784,7 @@ ${code}
|
|
|
1703
1784
|
exp,
|
|
1704
1785
|
)}, ${get}${set});\n`;
|
|
1705
1786
|
this.usedHelpers.add('$parcel$export');
|
|
1706
|
-
prependLineCount += 1
|
|
1787
|
+
prependLineCount += 1;
|
|
1707
1788
|
}
|
|
1708
1789
|
}
|
|
1709
1790
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
|
|
3
|
+
import {appendHoistedValues} from '../src/ScopeHoistingPackager';
|
|
4
|
+
|
|
5
|
+
describe('appendHoistedValues', function () {
|
|
6
|
+
// This helper exists because the previous implementation in
|
|
7
|
+
// `getHoistedParcelRequires` would emit `res += '\n' + ''` and then
|
|
8
|
+
// `lineCount += hoisted.size` whenever the filtered hoisted set was empty
|
|
9
|
+
// (or smaller than the unfiltered set). Both bugs desynchronised the
|
|
10
|
+
// source-map line-offset bookkeeping in `ScopeHoistingPackager`. These
|
|
11
|
+
// tests pin the corrected behaviour.
|
|
12
|
+
|
|
13
|
+
it('returns an empty result and zero lineCount when given no values', function () {
|
|
14
|
+
const out = appendHoistedValues([]);
|
|
15
|
+
assert.strictEqual(
|
|
16
|
+
out.text,
|
|
17
|
+
'',
|
|
18
|
+
'no text should be appended when there are zero hoisted values (avoids stray leading \\n)',
|
|
19
|
+
);
|
|
20
|
+
assert.strictEqual(
|
|
21
|
+
out.lineCount,
|
|
22
|
+
0,
|
|
23
|
+
'lineCount must be 0 when no values were appended (was previously off-by-`hoisted.size`)',
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('emits exactly one leading \\n plus the joined values, with lineCount equal to value count', function () {
|
|
28
|
+
const out = appendHoistedValues([
|
|
29
|
+
'parcelRequire("aaaaaaa");',
|
|
30
|
+
'parcelRequire("bbbbbbb");',
|
|
31
|
+
'parcelRequire("ccccccc");',
|
|
32
|
+
]);
|
|
33
|
+
assert.strictEqual(
|
|
34
|
+
out.text,
|
|
35
|
+
'\nparcelRequire("aaaaaaa");\nparcelRequire("bbbbbbb");\nparcelRequire("ccccccc");',
|
|
36
|
+
'joined values must be prefixed with one \\n and separated by \\n',
|
|
37
|
+
);
|
|
38
|
+
assert.strictEqual(
|
|
39
|
+
out.lineCount,
|
|
40
|
+
3,
|
|
41
|
+
'lineCount must equal the number of newlines actually written (3 for 3 values: 1 leading + 2 separators = 3 \\n)',
|
|
42
|
+
);
|
|
43
|
+
// Cross-check: count actual newlines in the returned text.
|
|
44
|
+
assert.strictEqual(
|
|
45
|
+
out.text.split('\n').length - 1,
|
|
46
|
+
out.lineCount,
|
|
47
|
+
'lineCount must equal the actual number of \\n characters in the appended text',
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('handles a single value correctly', function () {
|
|
52
|
+
const out = appendHoistedValues(['parcelRequire("only");']);
|
|
53
|
+
assert.strictEqual(out.text, '\nparcelRequire("only");');
|
|
54
|
+
assert.strictEqual(out.lineCount, 1);
|
|
55
|
+
assert.strictEqual(
|
|
56
|
+
out.text.split('\n').length - 1,
|
|
57
|
+
out.lineCount,
|
|
58
|
+
'lineCount must equal the actual number of \\n characters in the appended text',
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('lineCount always equals the newline count of the emitted text', function () {
|
|
63
|
+
// Property: for *any* input, the lineCount must match the actual number
|
|
64
|
+
// of newline characters in `text`. This is the invariant the
|
|
65
|
+
// ScopeHoistingPackager relies on to track lineOffset for the merged
|
|
66
|
+
// source map. The previous bug violated this invariant when filtering
|
|
67
|
+
// dropped some entries (lineCount was `hoisted.size`, but text only
|
|
68
|
+
// contained `filteredValues.length` newlines).
|
|
69
|
+
for (const values of [
|
|
70
|
+
[],
|
|
71
|
+
['x'],
|
|
72
|
+
['x', 'y'],
|
|
73
|
+
['x', 'y', 'z'],
|
|
74
|
+
['p\nq', 'r\ns'], // values containing embedded newlines — lineCount
|
|
75
|
+
// intentionally tracks only the separator newlines we emit (1 leading
|
|
76
|
+
// + N-1 separators = N), NOT the embedded ones; the caller is
|
|
77
|
+
// responsible for not embedding extra newlines in hoisted require
|
|
78
|
+
// strings. Document the assumption with this guard:
|
|
79
|
+
]) {
|
|
80
|
+
const out = appendHoistedValues(values);
|
|
81
|
+
if (values.length === 0) {
|
|
82
|
+
assert.strictEqual(out.text, '');
|
|
83
|
+
assert.strictEqual(out.lineCount, 0);
|
|
84
|
+
} else if (values.every((v) => !v.includes('\n'))) {
|
|
85
|
+
const actualNewlines = out.text.split('\n').length - 1;
|
|
86
|
+
assert.strictEqual(
|
|
87
|
+
out.lineCount,
|
|
88
|
+
actualNewlines,
|
|
89
|
+
`lineCount mismatch for values=${JSON.stringify(values)}: claimed ${out.lineCount}, actual ${actualNewlines}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|