support multiple source files, fix types

pull/5428/head
Milan Hauth 5 years ago
parent 3d053d9395
commit a0eb41f68d

@ -335,7 +335,7 @@ export default class Component {
if (compile_options.sourcemap) {
if (js.map) {
const pre_remap_sources = js.map.sources;
js.map = remapping([js.map, compile_options.sourcemap], () => null);
js.map = remapping([js.map, compile_options.sourcemap], () => null, true);
// remapper can remove our source if it isn't used (no segments map back to it). It is still handy to have a source
// so we add it back
if (js.map.sources && js.map.sources.length == 0) {
@ -357,7 +357,7 @@ export default class Component {
});
}
if (css.map) {
css.map = remapping([css.map, compile_options.sourcemap], () => null);
css.map = remapping([css.map, compile_options.sourcemap], () => null, true);
}
}
}

@ -1,12 +1,12 @@
import remapper from '@ampproject/remapping';
import { decode as sourcemap_decode } from 'sourcemap-codec';
import remapping from '@ampproject/remapping';
import { SourceMapInput, SourceMapLoader, RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
import { decode as decode_mappings } from 'sourcemap-codec';
import { getLocator } from 'locate-character';
import { StringWithSourcemap, sourcemap_add_offset } from '../utils/string_with_sourcemap';
export interface Processed {
code: string;
map?: object | string;
map?: SourceMapInput;
dependencies?: string[];
}
@ -102,7 +102,7 @@ function get_replacement(
if (processed.map) {
decoded_map = typeof processed.map === "string" ? JSON.parse(processed.map) : processed.map;
if (typeof(decoded_map.mappings) === 'string')
decoded_map.mappings = sourcemap_decode(decoded_map.mappings);
decoded_map.mappings = decode_mappings(decoded_map.mappings);
sourcemap_add_offset(decoded_map, get_location(offset + prefix.length));
}
const processed_with_map = StringWithSourcemap.from_processed(processed.code, decoded_map);
@ -130,7 +130,7 @@ export default async function preprocess(
// sourcemap_list is sorted in reverse order from last map (index 0) to first map (index -1)
// so we use sourcemap_list.unshift() to add new maps
// https://github.com/ampproject/remapping#multiple-transformations-of-a-file
const sourcemap_list: Array<Processed['map']> = [];
const sourcemap_list: (DecodedSourceMap | RawSourceMap)[] = [];
for (const fn of markup) {
@ -142,7 +142,12 @@ export default async function preprocess(
if (processed && processed.dependencies) dependencies.push(...processed.dependencies);
source = processed ? processed.code : source;
if (processed && processed.map) sourcemap_list.unshift(processed.map);
if (processed && processed.map)
sourcemap_list.unshift(
typeof(processed.map) === 'string'
? JSON.parse(processed.map) as RawSourceMap
: processed.map as (RawSourceMap | DecodedSourceMap)
);
}
for (const fn of script) {
@ -211,20 +216,31 @@ export default async function preprocess(
sourcemap_list.unshift(res.map);
}
// remapper can throw error
// `Transformation map ${i} must have exactly one source file.`
// for 0 <= i <= (sourcemap_list.length - 2)
let map: ReturnType<typeof remapper>;
let map: RawSourceMap;
let map_idx = 0;
try {
map =
sourcemap_list.length == 0
? null
: remapper(sourcemap_list as any, () => null, true); // true: skip optional field `sourcesContent`
: sourcemap_list.slice(0, -1).find(m => m.sources.length !== 1) === undefined
? remapping( // use array interface
sourcemap_list,
() => null,
true // skip optional field `sourcesContent`
)
: remapping( // use loader interface
sourcemap_list[map_idx++],
function loader(sourcefile) {
if (sourcefile === filename)
return sourcemap_list[map_idx++] || null;
// bundle file = branch node
else return null; // source file = leaf node
} as SourceMapLoader
);
} catch (error) {
throw { ...error, message: error.message +
'\n\ncould not combine sourcemaps:\n' +
JSON.stringify((sourcemap_list as any).map(m => {
JSON.stringify(sourcemap_list.map(m => {
return { ...m, mappings: JSON.stringify(m.mappings).slice(0, 100)+' ....'};
}), null, 2)
};

@ -1,14 +1,4 @@
type MappingSegment =
| [number]
| [number, number, number, number]
| [number, number, number, number, number];
type SourceMappings = {
version: number;
sources: string[];
names: string[];
mappings: MappingSegment[][];
};
import { DecodedSourceMap, SourceMapSegment } from '@ampproject/remapping/dist/types/types';
type SourceLocation = {
line: number;
@ -21,10 +11,10 @@ function last_line_length(s: string) {
// mutate map in-place
export function sourcemap_add_offset(
map: SourceMappings, offset: SourceLocation
map: DecodedSourceMap, offset: SourceLocation
) {
// shift columns in first line
const m = map.mappings as any;
const m = map.mappings;
m[0].forEach(seg => {
if (seg[3]) seg[3] += offset.column;
});
@ -69,12 +59,12 @@ function pushArray<T>(_this: T[], other: T[]) {
export class StringWithSourcemap {
string: string;
map: SourceMappings;
map: DecodedSourceMap;
constructor(string = '', map = null) {
this.string = string;
if (map)
this.map = map as SourceMappings;
this.map = map as DecodedSourceMap;
else
this.map = {
version: 3,
@ -97,8 +87,8 @@ export class StringWithSourcemap {
this.string += other.string;
const m1 = this.map as any;
const m2 = other.map as any;
const m1 = this.map;
const m2 = other.map;
// combine sources and names
const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(m1.sources, m2.sources);
@ -109,20 +99,20 @@ export class StringWithSourcemap {
// unswitched loops are faster
if (sources_idx_changed && names_idx_changed) {
m2.forEach(line => {
m2.mappings.forEach(line => {
line.forEach(seg => {
if (seg[1]) seg[1] = new_source_idx[seg[1]];
if (seg[4]) seg[4] = new_name_idx[seg[4]];
});
});
} else if (sources_idx_changed) {
m2.forEach(line => {
m2.mappings.forEach(line => {
line.forEach(seg => {
if (seg[1]) seg[1] = new_source_idx[seg[1]];
});
});
} else if (names_idx_changed) {
m2.forEach(line => {
m2.mappings.forEach(line => {
line.forEach(seg => {
if (seg[4]) seg[4] = new_name_idx[seg[4]];
});
@ -137,9 +127,9 @@ export class StringWithSourcemap {
// columns of 2 must be shifted
const column_offset = last_line_length(this.string);
if (m2.length > 0 && column_offset > 0) {
if (m2.mappings.length > 0 && column_offset > 0) {
// shift columns in first line
m2[0].forEach(seg => {
m2.mappings[0].forEach(seg => {
seg[0] += column_offset;
});
}
@ -153,11 +143,11 @@ export class StringWithSourcemap {
return this;
}
static from_processed(string: string, map?: SourceMappings): StringWithSourcemap {
static from_processed(string: string, map?: DecodedSourceMap): StringWithSourcemap {
if (map) return new StringWithSourcemap(string, map);
map = { version: 3, names: [], sources: [], mappings: [] };
if (string == '') return new StringWithSourcemap(string, map);
// add empty MappingSegment[] for every line
// add empty SourceMapSegment[] for every line
const lineCount = string.split('\n').length;
map.mappings = Array.from({length: lineCount}).map(_ => []);
return new StringWithSourcemap(string, map);
@ -167,7 +157,7 @@ export class StringWithSourcemap {
source_file: string, source: string, offset_in_source?: SourceLocation
): StringWithSourcemap {
const offset = offset_in_source || { line: 0, column: 0 };
const map: SourceMappings = { version: 3, names: [], sources: [source_file], mappings: [] };
const map: DecodedSourceMap = { version: 3, names: [], sources: [source_file], mappings: [] };
if (source.length == 0) return new StringWithSourcemap(source, map);
// we create a high resolution identity map here,
@ -177,7 +167,7 @@ export class StringWithSourcemap {
let pos = 0;
const segs = line.split(/([^\d\w\s]|\s+)/g)
.filter(s => s !== "").map(s => {
const seg: MappingSegment = [
const seg: SourceMapSegment = [
pos, 0,
line_idx + offset.line,
pos + (line_idx == 0 ? offset.column : 0) // shift first line

@ -81,17 +81,37 @@ describe("sourcemaps", () => {
);
}
assert.deepEqual(js.map.sources, ["input.svelte"]);
if (css.map) assert.deepEqual(css.map.sources, ["input.svelte"]);
assert.deepEqual(
js.map.sources.slice().sort(),
(config.js_map_sources || ["input.svelte"]).sort()
);
if (css.map) {
assert.deepEqual(
css.map.sources.slice().sort(),
(config.css_map_sources || ["input.svelte"]).sort()
);
};
preprocessed.mapConsumer = preprocessed.map && await new SourceMapConsumer(preprocessed.map);
preprocessed.locate = getLocator(preprocessed.code);
// use locate_1 with mapConsumer:
// lines are one-based, columns are zero-based
js.mapConsumer = js.map && await new SourceMapConsumer(js.map);
js.locate = getLocator(js.code);
if (preprocessed.map) {
preprocessed.mapConsumer = await new SourceMapConsumer(preprocessed.map);
preprocessed.locate = getLocator(preprocessed.code);
preprocessed.locate_1 = getLocator(preprocessed.code, { offsetLine: 1 });
}
css.mapConsumer = css.map && await new SourceMapConsumer(css.map);
css.locate = getLocator(css.code || "");
if (js.map) {
js.mapConsumer = await new SourceMapConsumer(js.map);
js.locate = getLocator(js.code);
js.locate_1 = getLocator(js.code, { offsetLine: 1 });
}
if (css.map) {
css.mapConsumer = await new SourceMapConsumer(css.map);
css.locate = getLocator(css.code);
css.locate_1 = getLocator(css.code, { offsetLine: 1 });
}
test({ assert, input, preprocessed, js, css });
});

@ -3,7 +3,9 @@ import MagicString, { Bundle } from 'magic-string';
function add(bundle, filename, source) {
bundle.addSource({
filename,
content: new MagicString(source)
content: new MagicString(source),
separator: '\n',
//separator: '', // ERROR. probably a bug in magic-string
});
}
@ -12,13 +14,20 @@ function result(bundle, filename) {
code: bundle.toString(),
map: bundle.generateMap({
file: filename,
includeContent: true,
hires: true
includeContent: false,
hires: false
})
};
}
export default {
js_map_sources: [
'input.svelte',
'foo.js',
'bar.js',
'foo2.js',
'bar2.js',
],
preprocess: [
{
script: ({ content, filename }) => {

@ -1,12 +1,29 @@
export function test({ assert, preprocessed, js, css }) {
export function test({ assert, preprocessed, js }) {
assert.notEqual(preprocessed.error, undefined, 'expected preprocessed.error');
assert.equal(preprocessed.error, undefined);
const msg_expected_prefix = 'Transformation map 0 must have exactly one source file.';
// sourcemap stores location only for 'answer = 42;'
// not for 'var answer = 42;'
[
[js, 'foo.js', 'answer = 42;'],
[js, 'bar.js', 'console.log(answer);'],
[js, 'foo2.js', 'answer2 = 84;'],
[js, 'bar2.js', 'console.log(answer2);'],
]
.forEach(([where, sourcefile, content]) => {
assert.equal(
preprocessed.error.message.slice(0, msg_expected_prefix.length),
msg_expected_prefix
);
assert.deepEqual(
where.mapConsumer.originalPositionFor(
where.locate_1(content)
),
{
source: sourcefile,
name: null,
line: 1,
column: 0
},
`failed to locate "${content}" from "${sourcefile}"`
);
});
}

Loading…
Cancel
Save