Skip to content

Commit a0eb41f

Browse files
committed
support multiple source files, fix types
1 parent 3d053d9 commit a0eb41f

File tree

6 files changed

+112
-60
lines changed

6 files changed

+112
-60
lines changed

src/compiler/compile/Component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ export default class Component {
335335
if (compile_options.sourcemap) {
336336
if (js.map) {
337337
const pre_remap_sources = js.map.sources;
338-
js.map = remapping([js.map, compile_options.sourcemap], () => null);
338+
js.map = remapping([js.map, compile_options.sourcemap], () => null, true);
339339
// remapper can remove our source if it isn't used (no segments map back to it). It is still handy to have a source
340340
// so we add it back
341341
if (js.map.sources && js.map.sources.length == 0) {
@@ -357,7 +357,7 @@ export default class Component {
357357
});
358358
}
359359
if (css.map) {
360-
css.map = remapping([css.map, compile_options.sourcemap], () => null);
360+
css.map = remapping([css.map, compile_options.sourcemap], () => null, true);
361361
}
362362
}
363363
}

src/compiler/preprocess/index.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import remapper from '@ampproject/remapping';
2-
import { decode as sourcemap_decode } from 'sourcemap-codec';
1+
import remapping from '@ampproject/remapping';
2+
import { SourceMapInput, SourceMapLoader, RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
3+
import { decode as decode_mappings } from 'sourcemap-codec';
34
import { getLocator } from 'locate-character';
45
import { StringWithSourcemap, sourcemap_add_offset } from '../utils/string_with_sourcemap';
56

6-
77
export interface Processed {
88
code: string;
9-
map?: object | string;
9+
map?: SourceMapInput;
1010
dependencies?: string[];
1111
}
1212

@@ -102,7 +102,7 @@ function get_replacement(
102102
if (processed.map) {
103103
decoded_map = typeof processed.map === "string" ? JSON.parse(processed.map) : processed.map;
104104
if (typeof(decoded_map.mappings) === 'string')
105-
decoded_map.mappings = sourcemap_decode(decoded_map.mappings);
105+
decoded_map.mappings = decode_mappings(decoded_map.mappings);
106106
sourcemap_add_offset(decoded_map, get_location(offset + prefix.length));
107107
}
108108
const processed_with_map = StringWithSourcemap.from_processed(processed.code, decoded_map);
@@ -130,7 +130,7 @@ export default async function preprocess(
130130
// sourcemap_list is sorted in reverse order from last map (index 0) to first map (index -1)
131131
// so we use sourcemap_list.unshift() to add new maps
132132
// https://github.com/ampproject/remapping#multiple-transformations-of-a-file
133-
const sourcemap_list: Array<Processed['map']> = [];
133+
const sourcemap_list: (DecodedSourceMap | RawSourceMap)[] = [];
134134

135135
for (const fn of markup) {
136136

@@ -142,7 +142,12 @@ export default async function preprocess(
142142

143143
if (processed && processed.dependencies) dependencies.push(...processed.dependencies);
144144
source = processed ? processed.code : source;
145-
if (processed && processed.map) sourcemap_list.unshift(processed.map);
145+
if (processed && processed.map)
146+
sourcemap_list.unshift(
147+
typeof(processed.map) === 'string'
148+
? JSON.parse(processed.map) as RawSourceMap
149+
: processed.map as (RawSourceMap | DecodedSourceMap)
150+
);
146151
}
147152

148153
for (const fn of script) {
@@ -211,20 +216,31 @@ export default async function preprocess(
211216
sourcemap_list.unshift(res.map);
212217
}
213218

214-
// remapper can throw error
215-
// `Transformation map ${i} must have exactly one source file.`
216-
// for 0 <= i <= (sourcemap_list.length - 2)
217-
218-
let map: ReturnType<typeof remapper>;
219+
let map: RawSourceMap;
220+
let map_idx = 0;
219221
try {
220222
map =
221223
sourcemap_list.length == 0
222224
? null
223-
: remapper(sourcemap_list as any, () => null, true); // true: skip optional field `sourcesContent`
225+
: sourcemap_list.slice(0, -1).find(m => m.sources.length !== 1) === undefined
226+
? remapping( // use array interface
227+
sourcemap_list,
228+
() => null,
229+
true // skip optional field `sourcesContent`
230+
)
231+
: remapping( // use loader interface
232+
sourcemap_list[map_idx++],
233+
function loader(sourcefile) {
234+
if (sourcefile === filename)
235+
return sourcemap_list[map_idx++] || null;
236+
// bundle file = branch node
237+
else return null; // source file = leaf node
238+
} as SourceMapLoader
239+
);
224240
} catch (error) {
225241
throw { ...error, message: error.message +
226242
'\n\ncould not combine sourcemaps:\n' +
227-
JSON.stringify((sourcemap_list as any).map(m => {
243+
JSON.stringify(sourcemap_list.map(m => {
228244
return { ...m, mappings: JSON.stringify(m.mappings).slice(0, 100)+' ....'};
229245
}), null, 2)
230246
};

src/compiler/utils/string_with_sourcemap.ts

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,4 @@
1-
type MappingSegment =
2-
| [number]
3-
| [number, number, number, number]
4-
| [number, number, number, number, number];
5-
6-
type SourceMappings = {
7-
version: number;
8-
sources: string[];
9-
names: string[];
10-
mappings: MappingSegment[][];
11-
};
1+
import { DecodedSourceMap, SourceMapSegment } from '@ampproject/remapping/dist/types/types';
122

133
type SourceLocation = {
144
line: number;
@@ -21,10 +11,10 @@ function last_line_length(s: string) {
2111

2212
// mutate map in-place
2313
export function sourcemap_add_offset(
24-
map: SourceMappings, offset: SourceLocation
14+
map: DecodedSourceMap, offset: SourceLocation
2515
) {
2616
// shift columns in first line
27-
const m = map.mappings as any;
17+
const m = map.mappings;
2818
m[0].forEach(seg => {
2919
if (seg[3]) seg[3] += offset.column;
3020
});
@@ -69,12 +59,12 @@ function pushArray<T>(_this: T[], other: T[]) {
6959

7060
export class StringWithSourcemap {
7161
string: string;
72-
map: SourceMappings;
62+
map: DecodedSourceMap;
7363

7464
constructor(string = '', map = null) {
7565
this.string = string;
7666
if (map)
77-
this.map = map as SourceMappings;
67+
this.map = map as DecodedSourceMap;
7868
else
7969
this.map = {
8070
version: 3,
@@ -97,8 +87,8 @@ export class StringWithSourcemap {
9787

9888
this.string += other.string;
9989

100-
const m1 = this.map as any;
101-
const m2 = other.map as any;
90+
const m1 = this.map;
91+
const m2 = other.map;
10292

10393
// combine sources and names
10494
const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(m1.sources, m2.sources);
@@ -109,20 +99,20 @@ export class StringWithSourcemap {
10999

110100
// unswitched loops are faster
111101
if (sources_idx_changed && names_idx_changed) {
112-
m2.forEach(line => {
102+
m2.mappings.forEach(line => {
113103
line.forEach(seg => {
114104
if (seg[1]) seg[1] = new_source_idx[seg[1]];
115105
if (seg[4]) seg[4] = new_name_idx[seg[4]];
116106
});
117107
});
118108
} else if (sources_idx_changed) {
119-
m2.forEach(line => {
109+
m2.mappings.forEach(line => {
120110
line.forEach(seg => {
121111
if (seg[1]) seg[1] = new_source_idx[seg[1]];
122112
});
123113
});
124114
} else if (names_idx_changed) {
125-
m2.forEach(line => {
115+
m2.mappings.forEach(line => {
126116
line.forEach(seg => {
127117
if (seg[4]) seg[4] = new_name_idx[seg[4]];
128118
});
@@ -137,9 +127,9 @@ export class StringWithSourcemap {
137127
// columns of 2 must be shifted
138128

139129
const column_offset = last_line_length(this.string);
140-
if (m2.length > 0 && column_offset > 0) {
130+
if (m2.mappings.length > 0 && column_offset > 0) {
141131
// shift columns in first line
142-
m2[0].forEach(seg => {
132+
m2.mappings[0].forEach(seg => {
143133
seg[0] += column_offset;
144134
});
145135
}
@@ -153,11 +143,11 @@ export class StringWithSourcemap {
153143
return this;
154144
}
155145

156-
static from_processed(string: string, map?: SourceMappings): StringWithSourcemap {
146+
static from_processed(string: string, map?: DecodedSourceMap): StringWithSourcemap {
157147
if (map) return new StringWithSourcemap(string, map);
158148
map = { version: 3, names: [], sources: [], mappings: [] };
159149
if (string == '') return new StringWithSourcemap(string, map);
160-
// add empty MappingSegment[] for every line
150+
// add empty SourceMapSegment[] for every line
161151
const lineCount = string.split('\n').length;
162152
map.mappings = Array.from({length: lineCount}).map(_ => []);
163153
return new StringWithSourcemap(string, map);
@@ -167,7 +157,7 @@ export class StringWithSourcemap {
167157
source_file: string, source: string, offset_in_source?: SourceLocation
168158
): StringWithSourcemap {
169159
const offset = offset_in_source || { line: 0, column: 0 };
170-
const map: SourceMappings = { version: 3, names: [], sources: [source_file], mappings: [] };
160+
const map: DecodedSourceMap = { version: 3, names: [], sources: [source_file], mappings: [] };
171161
if (source.length == 0) return new StringWithSourcemap(source, map);
172162

173163
// we create a high resolution identity map here,
@@ -177,7 +167,7 @@ export class StringWithSourcemap {
177167
let pos = 0;
178168
const segs = line.split(/([^\d\w\s]|\s+)/g)
179169
.filter(s => s !== "").map(s => {
180-
const seg: MappingSegment = [
170+
const seg: SourceMapSegment = [
181171
pos, 0,
182172
line_idx + offset.line,
183173
pos + (line_idx == 0 ? offset.column : 0) // shift first line

test/sourcemaps/index.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,37 @@ describe("sourcemaps", () => {
8181
);
8282
}
8383

84-
assert.deepEqual(js.map.sources, ["input.svelte"]);
85-
if (css.map) assert.deepEqual(css.map.sources, ["input.svelte"]);
84+
assert.deepEqual(
85+
js.map.sources.slice().sort(),
86+
(config.js_map_sources || ["input.svelte"]).sort()
87+
);
88+
if (css.map) {
89+
assert.deepEqual(
90+
css.map.sources.slice().sort(),
91+
(config.css_map_sources || ["input.svelte"]).sort()
92+
);
93+
};
8694

87-
preprocessed.mapConsumer = preprocessed.map && await new SourceMapConsumer(preprocessed.map);
88-
preprocessed.locate = getLocator(preprocessed.code);
95+
// use locate_1 with mapConsumer:
96+
// lines are one-based, columns are zero-based
8997

90-
js.mapConsumer = js.map && await new SourceMapConsumer(js.map);
91-
js.locate = getLocator(js.code);
98+
if (preprocessed.map) {
99+
preprocessed.mapConsumer = await new SourceMapConsumer(preprocessed.map);
100+
preprocessed.locate = getLocator(preprocessed.code);
101+
preprocessed.locate_1 = getLocator(preprocessed.code, { offsetLine: 1 });
102+
}
92103

93-
css.mapConsumer = css.map && await new SourceMapConsumer(css.map);
94-
css.locate = getLocator(css.code || "");
104+
if (js.map) {
105+
js.mapConsumer = await new SourceMapConsumer(js.map);
106+
js.locate = getLocator(js.code);
107+
js.locate_1 = getLocator(js.code, { offsetLine: 1 });
108+
}
109+
110+
if (css.map) {
111+
css.mapConsumer = await new SourceMapConsumer(css.map);
112+
css.locate = getLocator(css.code);
113+
css.locate_1 = getLocator(css.code, { offsetLine: 1 });
114+
}
95115

96116
test({ assert, input, preprocessed, js, css });
97117
});

test/sourcemaps/samples/sourcemap-sources/_config.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import MagicString, { Bundle } from 'magic-string';
33
function add(bundle, filename, source) {
44
bundle.addSource({
55
filename,
6-
content: new MagicString(source)
6+
content: new MagicString(source),
7+
separator: '\n',
8+
//separator: '', // ERROR. probably a bug in magic-string
79
});
810
}
911

@@ -12,13 +14,20 @@ function result(bundle, filename) {
1214
code: bundle.toString(),
1315
map: bundle.generateMap({
1416
file: filename,
15-
includeContent: true,
16-
hires: true
17+
includeContent: false,
18+
hires: false
1719
})
1820
};
1921
}
2022

2123
export default {
24+
js_map_sources: [
25+
'input.svelte',
26+
'foo.js',
27+
'bar.js',
28+
'foo2.js',
29+
'bar2.js',
30+
],
2231
preprocess: [
2332
{
2433
script: ({ content, filename }) => {
Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
1-
export function test({ assert, preprocessed, js, css }) {
1+
export function test({ assert, preprocessed, js }) {
22

3-
assert.notEqual(preprocessed.error, undefined, 'expected preprocessed.error');
3+
assert.equal(preprocessed.error, undefined);
44

5-
const msg_expected_prefix = 'Transformation map 0 must have exactly one source file.';
5+
// sourcemap stores location only for 'answer = 42;'
6+
// not for 'var answer = 42;'
7+
[
8+
[js, 'foo.js', 'answer = 42;'],
9+
[js, 'bar.js', 'console.log(answer);'],
10+
[js, 'foo2.js', 'answer2 = 84;'],
11+
[js, 'bar2.js', 'console.log(answer2);'],
12+
]
13+
.forEach(([where, sourcefile, content]) => {
614

7-
assert.equal(
8-
preprocessed.error.message.slice(0, msg_expected_prefix.length),
9-
msg_expected_prefix
10-
);
15+
assert.deepEqual(
16+
where.mapConsumer.originalPositionFor(
17+
where.locate_1(content)
18+
),
19+
{
20+
source: sourcefile,
21+
name: null,
22+
line: 1,
23+
column: 0
24+
},
25+
`failed to locate "${content}" from "${sourcefile}"`
26+
);
1127

28+
});
1229
}

0 commit comments

Comments
 (0)