Skip to content

Commit 18003d6

Browse files
committed
fix tests, use decoded mappings, show warnings
1 parent a0eb41f commit 18003d6

File tree

20 files changed

+470
-98
lines changed

20 files changed

+470
-98
lines changed

src/compiler/compile/Component.ts

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ import add_to_set from './utils/add_to_set';
2929
import check_graph_for_cycles from './utils/check_graph_for_cycles';
3030
import { print, x, b } from 'code-red';
3131
import { is_reserved_keyword } from './utils/reserved_keywords';
32-
import remapping from '@ampproject/remapping';
32+
import { combine_sourcemaps, sourcemap_add_tostring_tourl, combine_sourcemaps_map_stats } from '../utils/string_with_sourcemap';
3333
import Element from './nodes/Element';
34+
import { RawSourceMap } from '@ampproject/remapping/dist/types/types';
35+
import { encode as encode_mappings, decode as decode_mappings } from 'sourcemap-codec';
36+
3437

3538
interface ComponentOptions {
3639
namespace?: string;
@@ -318,12 +321,19 @@ export default class Component {
318321

319322
css = compile_options.customElement
320323
? { code: null, map: null }
321-
: result.css;
324+
: result.css; // css.map.mappings are decoded
322325

323326
js = print(program, {
324327
sourceMapSource: compile_options.filename
325328
});
326329

330+
// TODO remove workaround
331+
// js.map.mappings should be decoded
332+
// https://github.com/Rich-Harris/code-red/issues/50
333+
if (js.map && typeof (js.map as any).mappings == 'string') {
334+
(js.map as any).mappings = decode_mappings((js.map as any).mappings);
335+
}
336+
327337
js.map.sources = [
328338
compile_options.filename ? get_relative_path(compile_options.outputFilename || '', compile_options.filename) : null
329339
];
@@ -332,34 +342,59 @@ export default class Component {
332342
this.source
333343
];
334344

345+
// combine sourcemaps
346+
const map_stats: combine_sourcemaps_map_stats = {
347+
sourcemapWarnLoss: 0, // segment loss is usually high, so we ignore
348+
sourcemapEncodedWarn: true // TODO config
349+
// property `result` is set by combine_sourcemaps
350+
};
351+
335352
if (compile_options.sourcemap) {
336353
if (js.map) {
337-
const pre_remap_sources = js.map.sources;
338-
js.map = remapping([js.map, compile_options.sourcemap], () => null, true);
339-
// remapper can remove our source if it isn't used (no segments map back to it). It is still handy to have a source
340-
// so we add it back
341-
if (js.map.sources && js.map.sources.length == 0) {
342-
js.map.sources = pre_remap_sources;
354+
js.map = combine_sourcemaps(
355+
this.file,
356+
[
357+
js.map, // idx 1: internal
358+
compile_options.sourcemap // idx 0: external: svelte.preprocess, etc
359+
],
360+
map_stats
361+
) as RawSourceMap;
362+
sourcemap_add_tostring_tourl(js.map);
363+
if (map_stats.result && map_stats.result.maps_encoded && map_stats.result.maps_encoded.length > 0) {
364+
console.log('warning. svelte.compile received encoded script sourcemaps (index '+
365+
map_stats.result.maps_encoded.join(', ')+'). '+
366+
'this is slow. make your sourcemap-generators return decoded mappings '+
367+
'or disable this warning with svelte.compile(_, _, { sourcemapEncodedWarn: false })'
368+
);
343369
}
344-
Object.defineProperties(js.map, {
345-
toString: {
346-
enumerable: false,
347-
value: function toString() {
348-
return JSON.stringify(this);
349-
}
350-
},
351-
toUrl: {
352-
enumerable: false,
353-
value: function toUrl() {
354-
return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString());
355-
}
356-
}
357-
});
358370
}
359371
if (css.map) {
360-
css.map = remapping([css.map, compile_options.sourcemap], () => null, true);
372+
css.map = combine_sourcemaps(
373+
this.file,
374+
[
375+
css.map, // idx 1: internal
376+
compile_options.sourcemap // idx 0: external: svelte.preprocess, etc
377+
]
378+
) as RawSourceMap;
379+
sourcemap_add_tostring_tourl(css.map);
380+
if (map_stats.result && map_stats.result.maps_encoded && map_stats.result.maps_encoded.length > 0) {
381+
console.log('warning. svelte.compile received encoded style sourcemaps (index '+
382+
map_stats.result.maps_encoded.join(', ')+'). '+
383+
'this is slow. make your sourcemap-generators return decoded mappings '+
384+
'or disable this warning with svelte.compile(_, _, { sourcemapEncodedWarn: false })'
385+
);
386+
}
361387
}
362388
}
389+
390+
// encode mappings only once, after all sourcemaps are combined
391+
if (js.map && typeof(js.map.mappings) == 'object') {
392+
(js.map as RawSourceMap).mappings = encode_mappings(js.map.mappings);
393+
}
394+
if (css.map && typeof(css.map.mappings) == 'object') {
395+
(css.map as RawSourceMap).mappings = encode_mappings(css.map.mappings);
396+
}
397+
363398
}
364399

365400
return {

src/compiler/compile/css/Stylesheet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ export default class Stylesheet {
410410

411411
return {
412412
code: code.toString(),
413-
map: code.generateMap({
413+
map: code.generateDecodedMap({
414414
includeContent: true,
415415
source: this.filename,
416416
file

src/compiler/compile/render_dom/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ export default function dom(
3030
}
3131

3232
const css = component.stylesheet.render(options.filename, !options.customElement);
33+
34+
// TODO fix css.map.toUrl - stylesheet.render returns decoded mappings, map.toUrl needs encoded mappings
35+
// TODO use combined css.map? see compile/Component.ts
3336
const styles = component.stylesheet.has_styles && options.dev
34-
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
37+
//? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
38+
? `${css.code}\n/*# sourceMappingURL=TODO_FIXME */`
3539
: css.code;
3640

3741
const add_css = component.get_unique_name('add_css');
@@ -467,12 +471,15 @@ export default function dom(
467471
}
468472

469473
if (options.customElement) {
474+
// TODO use combined css.map? see compile/Component.ts
475+
// TODO css.map.toUrl needs encoded mappings
476+
// ${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
470477
const declaration = b`
471478
class ${name} extends @SvelteElement {
472479
constructor(options) {
473480
super();
474481
475-
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
482+
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=TODO_FIXME */` : ''}</style>\`;`}
476483
477484
@init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
478485

src/compiler/compile/render_ssr/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export default function ssr(
137137
main
138138
].filter(Boolean);
139139

140+
// TODO use combined css.map? see compile/Component.ts
140141
const js = b`
141142
${css.code ? b`
142143
const #css = {

src/compiler/interfaces.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Node, Program } from "estree";
2-
import { SourceMap } from 'magic-string';
2+
3+
// eslint-disable-next-line import/named
4+
import { DecodedSourceMap } from 'magic-string';
35

46
interface BaseNode {
57
start: number;
@@ -166,5 +168,5 @@ export interface Var {
166168

167169
export interface CssResult {
168170
code: string;
169-
map: SourceMap;
171+
map: DecodedSourceMap;
170172
}

src/compiler/preprocess/index.ts

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import remapping from '@ampproject/remapping';
2-
import { SourceMapInput, SourceMapLoader, RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
1+
import { SourceMapInput, RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
32
import { decode as decode_mappings } from 'sourcemap-codec';
43
import { getLocator } from 'locate-character';
5-
import { StringWithSourcemap, sourcemap_add_offset } from '../utils/string_with_sourcemap';
4+
import { StringWithSourcemap, sourcemap_add_offset, combine_sourcemaps, combine_sourcemaps_map_stats } from '../utils/string_with_sourcemap';
65

76
export interface Processed {
87
code: string;
@@ -113,10 +112,17 @@ function get_replacement(
113112
export default async function preprocess(
114113
source: string,
115114
preprocessor: PreprocessorGroup | PreprocessorGroup[],
116-
options?: { filename?: string }
115+
options?: {
116+
filename?: string,
117+
sourcemapWarnLoss?: number, // default 0.5
118+
sourcemapEncodedWarn?: boolean // default true
119+
}
117120
) {
118121
// @ts-ignore todo: doublecheck
119122
const filename = (options && options.filename) || preprocessor.filename; // legacy
123+
const sourcemapWarnLoss = (options && options.sourcemapWarnLoss != undefined) ? options.sourcemapWarnLoss : 0.5;
124+
const sourcemapEncodedWarn = (options && options.sourcemapEncodedWarn != undefined) ? options.sourcemapEncodedWarn : true;
125+
120126
const dependencies = [];
121127

122128
const preprocessors = preprocessor
@@ -130,7 +136,9 @@ export default async function preprocess(
130136
// sourcemap_list is sorted in reverse order from last map (index 0) to first map (index -1)
131137
// so we use sourcemap_list.unshift() to add new maps
132138
// https://github.com/ampproject/remapping#multiple-transformations-of-a-file
133-
const sourcemap_list: (DecodedSourceMap | RawSourceMap)[] = [];
139+
const sourcemap_list: Array<DecodedSourceMap | RawSourceMap> = [];
140+
141+
// TODO keep track: what preprocessor generated what sourcemap? to make debugging easier = detect low-resolution sourcemaps in fn combine_mappings
134142

135143
for (const fn of markup) {
136144

@@ -150,6 +158,8 @@ export default async function preprocess(
150158
);
151159
}
152160

161+
// TODO run script and style in parallel
162+
153163
for (const fn of script) {
154164
const get_location = getLocator(source);
155165
const res = await replace_async(
@@ -216,37 +226,41 @@ export default async function preprocess(
216226
sourcemap_list.unshift(res.map);
217227
}
218228

219-
let map: RawSourceMap;
220-
let map_idx = 0;
221-
try {
222-
map =
223-
sourcemap_list.length == 0
224-
? null
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-
);
240-
} catch (error) {
241-
throw { ...error, message: error.message +
242-
'\n\ncould not combine sourcemaps:\n' +
243-
JSON.stringify(sourcemap_list.map(m => {
244-
return { ...m, mappings: JSON.stringify(m.mappings).slice(0, 100)+' ....'};
245-
}), null, 2)
246-
};
229+
const map_stats: combine_sourcemaps_map_stats = {
230+
sourcemapWarnLoss,
231+
sourcemapEncodedWarn
232+
// property `result` is set by combine_sourcemaps
233+
};
234+
235+
const map: DecodedSourceMap = combine_sourcemaps(
236+
filename,
237+
sourcemap_list,
238+
map_stats,
239+
true // explicitly decode mappings
240+
// TODO remove this, when `remapping` allows to return decoded mappings, so we skip the unnecessary encode + decode steps
241+
) as DecodedSourceMap;
242+
243+
// TODO better than console.log?
244+
245+
if (map_stats.result && map_stats.result.segments_lost) {
246+
const { segment_loss_per_map, segments_per_map } = map_stats.result;
247+
console.log('warning. svelte.preprocess seems to receive low-resolution sourcemaps. '+
248+
'relative segment loss per combine_sourcemaps step: '+
249+
segment_loss_per_map.map(f => f.toFixed(2)).join(' -> ')+
250+
'. absolute number of segments per sourcemap: '+
251+
segments_per_map.join(' -> ')+
252+
'. make your preprocessors return high-resolution sourcemaps '+
253+
'or increase the tolerated loss with svelte.preprocess(_, _, { sourcemapWarnLoss: 0.8 })'
254+
);
247255
}
248256

249-
if (map && !map.file) delete map.file; // skip optional field `file`
257+
if (map_stats.result && map_stats.result.maps_encoded && map_stats.result.maps_encoded.length > 0) {
258+
console.log('warning. svelte.preprocess received encoded sourcemaps (index '+
259+
map_stats.result.maps_encoded.join(', ')+'). '+
260+
'this is slow. make your sourcemap-generators return decoded mappings '+
261+
'or disable this warning with svelte.preprocess(_, _, { sourcemapEncodedWarn: false })'
262+
);
263+
}
250264

251265
return {
252266
// TODO return separated output, in future version where svelte.compile supports it:

0 commit comments

Comments
 (0)