diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index caf0a42a8f..d3dd8d0a0b 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -29,8 +29,11 @@ import add_to_set from './utils/add_to_set'; import check_graph_for_cycles from './utils/check_graph_for_cycles'; import { print, x, b } from 'code-red'; import { is_reserved_keyword } from './utils/reserved_keywords'; -import remapping from '@ampproject/remapping'; +import { combine_sourcemaps, sourcemap_add_tostring_tourl, combine_sourcemaps_map_stats } from '../utils/string_with_sourcemap'; import Element from './nodes/Element'; +import { RawSourceMap } from '@ampproject/remapping/dist/types/types'; +import { encode as encode_mappings, decode as decode_mappings } from 'sourcemap-codec'; + interface ComponentOptions { namespace?: string; @@ -318,12 +321,19 @@ export default class Component { css = compile_options.customElement ? { code: null, map: null } - : result.css; + : result.css; // css.map.mappings are decoded js = print(program, { sourceMapSource: compile_options.filename }); + // TODO remove workaround + // js.map.mappings should be decoded + // https://github.com/Rich-Harris/code-red/issues/50 + if (js.map && typeof (js.map as any).mappings == 'string') { + (js.map as any).mappings = decode_mappings((js.map as any).mappings); + } + js.map.sources = [ compile_options.filename ? get_relative_path(compile_options.outputFilename || '', compile_options.filename) : null ]; @@ -332,34 +342,59 @@ export default class Component { this.source ]; + // combine sourcemaps + const map_stats: combine_sourcemaps_map_stats = { + sourcemapWarnLoss: 0, // segment loss is usually high, so we ignore + sourcemapEncodedWarn: true // TODO config + // property `result` is set by combine_sourcemaps + }; + if (compile_options.sourcemap) { if (js.map) { - const pre_remap_sources = js.map.sources; - 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) { - js.map.sources = pre_remap_sources; + js.map = combine_sourcemaps( + this.file, + [ + js.map, // idx 1: internal + compile_options.sourcemap // idx 0: external: svelte.preprocess, etc + ], + map_stats + ) as RawSourceMap; + sourcemap_add_tostring_tourl(js.map); + if (map_stats.result && map_stats.result.maps_encoded && map_stats.result.maps_encoded.length > 0) { + console.log('warning. svelte.compile received encoded script sourcemaps (index '+ + map_stats.result.maps_encoded.join(', ')+'). '+ + 'this is slow. make your sourcemap-generators return decoded mappings '+ + 'or disable this warning with svelte.compile(_, _, { sourcemapEncodedWarn: false })' + ); } - Object.defineProperties(js.map, { - toString: { - enumerable: false, - value: function toString() { - return JSON.stringify(this); - } - }, - toUrl: { - enumerable: false, - value: function toUrl() { - return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString()); - } - } - }); } if (css.map) { - css.map = remapping([css.map, compile_options.sourcemap], () => null, true); + css.map = combine_sourcemaps( + this.file, + [ + css.map, // idx 1: internal + compile_options.sourcemap // idx 0: external: svelte.preprocess, etc + ] + ) as RawSourceMap; + sourcemap_add_tostring_tourl(css.map); + if (map_stats.result && map_stats.result.maps_encoded && map_stats.result.maps_encoded.length > 0) { + console.log('warning. svelte.compile received encoded style sourcemaps (index '+ + map_stats.result.maps_encoded.join(', ')+'). '+ + 'this is slow. make your sourcemap-generators return decoded mappings '+ + 'or disable this warning with svelte.compile(_, _, { sourcemapEncodedWarn: false })' + ); + } } } + + // encode mappings only once, after all sourcemaps are combined + if (js.map && typeof(js.map.mappings) == 'object') { + (js.map as RawSourceMap).mappings = encode_mappings(js.map.mappings); + } + if (css.map && typeof(css.map.mappings) == 'object') { + (css.map as RawSourceMap).mappings = encode_mappings(css.map.mappings); + } + } return { diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index dc464d7df8..9d18d64274 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -410,7 +410,7 @@ export default class Stylesheet { return { code: code.toString(), - map: code.generateMap({ + map: code.generateDecodedMap({ includeContent: true, source: this.filename, file diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index ab4ee904e8..523141b57b 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -30,8 +30,12 @@ export default function dom( } const css = component.stylesheet.render(options.filename, !options.customElement); + + // TODO fix css.map.toUrl - stylesheet.render returns decoded mappings, map.toUrl needs encoded mappings + // TODO use combined css.map? see compile/Component.ts const styles = component.stylesheet.has_styles && options.dev - ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` + //? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` + ? `${css.code}\n/*# sourceMappingURL=TODO_FIXME */` : css.code; const add_css = component.get_unique_name('add_css'); @@ -467,12 +471,15 @@ export default function dom( } if (options.customElement) { + // TODO use combined css.map? see compile/Component.ts + // TODO css.map.toUrl needs encoded mappings + // ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} const declaration = b` class ${name} extends @SvelteElement { constructor(options) { super(); - ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} + ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} @init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); diff --git a/src/compiler/compile/render_ssr/index.ts b/src/compiler/compile/render_ssr/index.ts index ff45abd78b..62e2d18314 100644 --- a/src/compiler/compile/render_ssr/index.ts +++ b/src/compiler/compile/render_ssr/index.ts @@ -137,6 +137,7 @@ export default function ssr( main ].filter(Boolean); + // TODO use combined css.map? see compile/Component.ts const js = b` ${css.code ? b` const #css = { diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index 7843cf8938..263f25d7d7 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -1,5 +1,7 @@ import { Node, Program } from "estree"; -import { SourceMap } from 'magic-string'; + +// eslint-disable-next-line import/named +import { DecodedSourceMap } from 'magic-string'; interface BaseNode { start: number; @@ -166,5 +168,5 @@ export interface Var { export interface CssResult { code: string; - map: SourceMap; + map: DecodedSourceMap; } diff --git a/src/compiler/preprocess/index.ts b/src/compiler/preprocess/index.ts index c27c658919..30f4d08f1b 100644 --- a/src/compiler/preprocess/index.ts +++ b/src/compiler/preprocess/index.ts @@ -1,8 +1,7 @@ -import remapping from '@ampproject/remapping'; -import { SourceMapInput, SourceMapLoader, RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types'; +import { SourceMapInput, 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'; +import { StringWithSourcemap, sourcemap_add_offset, combine_sourcemaps, combine_sourcemaps_map_stats } from '../utils/string_with_sourcemap'; export interface Processed { code: string; @@ -113,10 +112,17 @@ function get_replacement( export default async function preprocess( source: string, preprocessor: PreprocessorGroup | PreprocessorGroup[], - options?: { filename?: string } + options?: { + filename?: string, + sourcemapWarnLoss?: number, // default 0.5 + sourcemapEncodedWarn?: boolean // default true + } ) { // @ts-ignore todo: doublecheck const filename = (options && options.filename) || preprocessor.filename; // legacy + const sourcemapWarnLoss = (options && options.sourcemapWarnLoss != undefined) ? options.sourcemapWarnLoss : 0.5; + const sourcemapEncodedWarn = (options && options.sourcemapEncodedWarn != undefined) ? options.sourcemapEncodedWarn : true; + const dependencies = []; const preprocessors = preprocessor @@ -130,7 +136,9 @@ 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: (DecodedSourceMap | RawSourceMap)[] = []; + const sourcemap_list: Array = []; + + // TODO keep track: what preprocessor generated what sourcemap? to make debugging easier = detect low-resolution sourcemaps in fn combine_mappings for (const fn of markup) { @@ -150,6 +158,8 @@ export default async function preprocess( ); } + // TODO run script and style in parallel + for (const fn of script) { const get_location = getLocator(source); const res = await replace_async( @@ -216,37 +226,41 @@ export default async function preprocess( sourcemap_list.unshift(res.map); } - let map: RawSourceMap; - let map_idx = 0; - try { - map = - sourcemap_list.length == 0 - ? null - : 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.map(m => { - return { ...m, mappings: JSON.stringify(m.mappings).slice(0, 100)+' ....'}; - }), null, 2) - }; + const map_stats: combine_sourcemaps_map_stats = { + sourcemapWarnLoss, + sourcemapEncodedWarn + // property `result` is set by combine_sourcemaps + }; + + const map: DecodedSourceMap = combine_sourcemaps( + filename, + sourcemap_list, + map_stats, + true // explicitly decode mappings + // TODO remove this, when `remapping` allows to return decoded mappings, so we skip the unnecessary encode + decode steps + ) as DecodedSourceMap; + + // TODO better than console.log? + + if (map_stats.result && map_stats.result.segments_lost) { + const { segment_loss_per_map, segments_per_map } = map_stats.result; + console.log('warning. svelte.preprocess seems to receive low-resolution sourcemaps. '+ + 'relative segment loss per combine_sourcemaps step: '+ + segment_loss_per_map.map(f => f.toFixed(2)).join(' -> ')+ + '. absolute number of segments per sourcemap: '+ + segments_per_map.join(' -> ')+ + '. make your preprocessors return high-resolution sourcemaps '+ + 'or increase the tolerated loss with svelte.preprocess(_, _, { sourcemapWarnLoss: 0.8 })' + ); } - if (map && !map.file) delete map.file; // skip optional field `file` + if (map_stats.result && map_stats.result.maps_encoded && map_stats.result.maps_encoded.length > 0) { + console.log('warning. svelte.preprocess received encoded sourcemaps (index '+ + map_stats.result.maps_encoded.join(', ')+'). '+ + 'this is slow. make your sourcemap-generators return decoded mappings '+ + 'or disable this warning with svelte.preprocess(_, _, { sourcemapEncodedWarn: false })' + ); + } return { // TODO return separated output, in future version where svelte.compile supports it: diff --git a/src/compiler/utils/string_with_sourcemap.ts b/src/compiler/utils/string_with_sourcemap.ts index 9dcc51afe3..0e08436092 100644 --- a/src/compiler/utils/string_with_sourcemap.ts +++ b/src/compiler/utils/string_with_sourcemap.ts @@ -1,4 +1,6 @@ -import { DecodedSourceMap, SourceMapSegment } from '@ampproject/remapping/dist/types/types'; +import { DecodedSourceMap, RawSourceMap, SourceMapSegment, SourceMapLoader } from '@ampproject/remapping/dist/types/types'; +import remapping from '@ampproject/remapping'; +import { decode as decode_mappings } from 'sourcemap-codec'; type SourceLocation = { line: number; @@ -180,3 +182,152 @@ export class StringWithSourcemap { return new StringWithSourcemap(source, map); } } + +export type combine_sourcemaps_map_stats = { + sourcemapEncodedWarn?: boolean, + sourcemapWarnLoss?: number, + result?: { + maps_encoded?: number[], + segments_lost?: boolean + segment_loss_per_map?: number[], + segments_per_map?: number[], + } +}; + +export function combine_sourcemaps( + filename: string, + sourcemap_list: Array, + map_stats?: combine_sourcemaps_map_stats, + do_decode_mappings?: boolean +): (RawSourceMap | DecodedSourceMap) { + if (sourcemap_list.length == 0) return null; + + if (map_stats) { + map_stats.result = {}; + const { result } = map_stats; + + const last_map_idx = sourcemap_list.length - 1; + + // TODO allow to set options per preprocessor -> extend preprocessor config object + // some sourcemap-generators produce ultra-high-resolution sourcemaps (1 token = 1 character), so a high segment loss can be tolerable + + // sourcemapEncodedWarn: show warning + // if preprocessors return sourcemaps with encoded mappings + // we need decoded mappings, so that is a waste of time + + if (map_stats.sourcemapEncodedWarn) { + result.maps_encoded = []; + + for (let map_idx = last_map_idx; map_idx >= 0; map_idx--) { + const map = sourcemap_list[map_idx]; + if (typeof(map) == 'string') { + sourcemap_list[map_idx] = JSON.parse(map); + } + if (typeof(map.mappings) == 'string') { + result.maps_encoded.push(last_map_idx - map_idx); // chronological index + } + } + } + + // sourcemapWarnLoss: show warning if source files were lost + // disable warning with `svelte.preprocess(_, _, { sourcemapWarnLoss: false })` + // value 1 : never warn + // value 0.8: seldom warn + // value 0.5: average warn + // value 0.2: often warn + // value 0 : nonsense -> never warn + // -Infinity <= loss <= 1 and 0 < sourcemapWarnLoss <= 1 + + if (map_stats.sourcemapWarnLoss) { + + // guess if segments were lost because of lowres sourcemaps + // assert: typeof(result) == 'object' + result.segments_per_map = []; + result.segment_loss_per_map = []; + result.segments_lost = false; + + let last_num_segments; + + for (let map_idx = last_map_idx; map_idx >= 0; map_idx--) { + + const map = sourcemap_list[map_idx]; + if (typeof(map) == 'string') { + sourcemap_list[map_idx] = JSON.parse(map); + } + if (typeof(map.mappings) == 'string') { + // do this before remapping to avoid double decoding + // remapping does not mutate its input data + map.mappings = decode_mappings(map.mappings); + } + let num_segments = 0; + for (const line of map.mappings) { + num_segments += line.length; + } + // get relative loss, compared to last map + const loss = map_idx == last_map_idx + ? 0 : (last_num_segments - num_segments) / last_num_segments; + if (loss > map_stats.sourcemapWarnLoss) { + result.segments_lost = true; + } + + // chronological index + result.segment_loss_per_map.push(loss); + result.segments_per_map.push(num_segments); + + last_num_segments = num_segments; + } + } + + } + + let map_idx = 1; + const map: RawSourceMap = + sourcemap_list.slice(0, -1) + .find(m => m.sources.length !== 1) === undefined + + ? remapping( // use array interface + // only the oldest sourcemap can have multiple sources + sourcemap_list, + () => null, + true // skip optional field `sourcesContent` + ) + + : remapping( // use loader interface + sourcemap_list[0], // last map + function loader(sourcefile) { + if (sourcefile === filename && sourcemap_list[map_idx]) { + return sourcemap_list[map_idx++]; // idx 1, 2, ... + // bundle file = branch node + } + else return null; // source file = leaf node + } as SourceMapLoader, + true + ); + + if (!map.file) delete map.file; // skip optional field `file` + + if (do_decode_mappings) { + // explicitly decode mappings + // TODO remove this, when `remapping` allows to return decoded mappings, so we skip the unnecessary encode + decode steps + (map as unknown as DecodedSourceMap).mappings = decode_mappings(map.mappings); + } + + return map; +} + +export function sourcemap_add_tostring_tourl(map) { + Object.defineProperties(map, { + toString: { + enumerable: false, + value: function toString() { + return JSON.stringify(this); + } + }, + toUrl: { + enumerable: false, + value: function toUrl() { + return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString()); + } + } + }); +} diff --git a/test/sourcemaps/index.js b/test/sourcemaps/index.js index ca0bdbfca3..b8ba4aaa2a 100644 --- a/test/sourcemaps/index.js +++ b/test/sourcemaps/index.js @@ -6,6 +6,7 @@ import { loadConfig, svelte } from "../helpers.js"; // https://github.com/mozilla/source-map/issues/400 import { SourceMapConsumer } from "source-map"; import { getLocator } from "locate-character"; +import { encode as encode_mappings, decode as decode_mappings } from 'sourcemap-codec'; describe("sourcemaps", () => { fs.readdirSync(`${__dirname}/samples`).forEach(dir => { @@ -44,6 +45,13 @@ describe("sourcemaps", () => { return test({ assert, input, preprocessed }); } + // preprocessed.map.mappings should be decoded + // to avoid unnecessary encode + decode steps + if (preprocessed.map) { + assert.equal(typeof preprocessed.map.mappings, 'object', 'preprocessed.map.mappings should be decoded'); + assert.equal(Array.isArray(preprocessed.map.mappings), true, 'preprocessed.map.mappings should be decoded'); + } + const { js, css } = svelte.compile( preprocessed.code, { filename: "input.svelte", @@ -60,7 +68,11 @@ describe("sourcemaps", () => { fs.writeFileSync(`${outputBase}.svelte`, preprocessed.code); if (preprocessed.map) { - fs.writeFileSync(`${outputBase}.svelte.map`, JSON.stringify(preprocessed.map, null, 2)); + fs.writeFileSync( + `${outputBase}.svelte.map`, + // TODO encode mappings for output - svelte.preprocess returns decoded mappings + JSON.stringify(preprocessed.map, null, 2) + ); } fs.writeFileSync( `${outputBase}.js`, @@ -92,26 +104,26 @@ describe("sourcemaps", () => { ); }; + // stupid workaround (unnecessary encode + decode steps) + // TODO find a SourceMapConsumer who also consumes decoded mappings + if (preprocessed.map) { + preprocessed.map.mappings = encode_mappings(preprocessed.map.mappings); + } + // use locate_1 with mapConsumer: // lines are one-based, columns are zero-based - if (preprocessed.map) { - preprocessed.mapConsumer = await new SourceMapConsumer(preprocessed.map); - preprocessed.locate = getLocator(preprocessed.code); - preprocessed.locate_1 = getLocator(preprocessed.code, { offsetLine: 1 }); - } + preprocessed.mapConsumer = preprocessed.map && await new SourceMapConsumer(preprocessed.map); + preprocessed.locate = getLocator(preprocessed.code); + preprocessed.locate_1 = getLocator(preprocessed.code, { offsetLine: 1 }); - if (js.map) { - js.mapConsumer = await new SourceMapConsumer(js.map); - js.locate = getLocator(js.code); - js.locate_1 = getLocator(js.code, { offsetLine: 1 }); - } + js.mapConsumer = js.map && 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 }); - } + css.mapConsumer = css.map && await new SourceMapConsumer(css.map); + css.locate = getLocator(css.code || ''); + css.locate_1 = getLocator(css.code || '', { offsetLine: 1 }); test({ assert, input, preprocessed, js, css }); }); diff --git a/test/sourcemaps/samples/decoded-sourcemap/_config.js b/test/sourcemaps/samples/decoded-sourcemap/_config.js index bc0db984b2..fc4d2a03c9 100644 --- a/test/sourcemaps/samples/decoded-sourcemap/_config.js +++ b/test/sourcemaps/samples/decoded-sourcemap/_config.js @@ -19,6 +19,9 @@ function result(src, filename) { } export default { + + js_map_sources: [], // test component has no scripts + preprocess: { markup: ({ content, filename }) => { const src = new MagicString(content); diff --git a/test/sourcemaps/samples/detect-lowres-sourcemaps/_config.js b/test/sourcemaps/samples/detect-lowres-sourcemaps/_config.js new file mode 100644 index 0000000000..b11594dd85 --- /dev/null +++ b/test/sourcemaps/samples/detect-lowres-sourcemaps/_config.js @@ -0,0 +1,54 @@ +import MagicString from 'magic-string'; + +// TODO move util fns to test index.js + +function result(filename, src, extraOptions = {}) { + return { + code: src.toString(), + map: src.generateDecodedMap({ + source: filename, + hires: true, + includeContent: false, + ...extraOptions + }) + }; +} + +function replace_all(src, search, replace) { + let idx = src.original.indexOf(search); + if (idx == -1) throw new Error('search not found in src'); + do { + src.overwrite(idx, idx + search.length, replace); + } while ((idx = src.original.indexOf(search, idx + 1)) != -1); +} + +function replace_first(src, search, replace) { + const idx = src.original.indexOf(search); + if (idx == -1) throw new Error('search not found in src'); + src.overwrite(idx, idx + search.length, replace); +} + +export default { + + preprocess_options: { + sourcemapLossWarn: 0.9 // warn often + }, + + js_map_sources: [], // test component has no scripts + + preprocess: [ + { markup: ({ content, filename }) => { + const src = new MagicString(content); + replace_all(src, 'replace_me', 'done_replace'); + return result(filename, src, { hires: true }); + } }, + { markup: ({ content, filename }) => { + const src = new MagicString(content); + replace_first(src, 'done_replace', 'version_3'); + // return low-resolution sourcemap + // this should make previous mappings unreachable + return result(filename, src, { hires: false }); + } } + ] + +}; diff --git a/test/sourcemaps/samples/detect-lowres-sourcemaps/input.svelte b/test/sourcemaps/samples/detect-lowres-sourcemaps/input.svelte new file mode 100644 index 0000000000..2b3afd881b --- /dev/null +++ b/test/sourcemaps/samples/detect-lowres-sourcemaps/input.svelte @@ -0,0 +1,10 @@ +replace_me +replace_me +replace_me +replace_me +replace_me +replace_me +replace_me +replace_me +replace_me +replace_me diff --git a/test/sourcemaps/samples/detect-lowres-sourcemaps/test.js b/test/sourcemaps/samples/detect-lowres-sourcemaps/test.js new file mode 100644 index 0000000000..0f63efb358 --- /dev/null +++ b/test/sourcemaps/samples/detect-lowres-sourcemaps/test.js @@ -0,0 +1,10 @@ +export function test({ assert, preprocessed, js }) { + + assert.equal(preprocessed.error, undefined); + + // TODO can we automate this test? + // we need the output of console.log + // to test the warning message. + // or use a different method for warnings? + +} diff --git a/test/sourcemaps/samples/preprocessed-markup/_config.js b/test/sourcemaps/samples/preprocessed-markup/_config.js index f515cc6d12..cb3eb90e01 100644 --- a/test/sourcemaps/samples/preprocessed-markup/_config.js +++ b/test/sourcemaps/samples/preprocessed-markup/_config.js @@ -8,7 +8,7 @@ export default { src.overwrite(idx, idx+"baritone".length, "bar"); return { code: src.toString(), - map: src.generateMap({ + map: src.generateDecodedMap({ source: filename, includeContent: false }) diff --git a/test/sourcemaps/samples/preprocessed-multiple/_config.js b/test/sourcemaps/samples/preprocessed-multiple/_config.js index c587a71bbd..d4c1e6cdbe 100644 --- a/test/sourcemaps/samples/preprocessed-multiple/_config.js +++ b/test/sourcemaps/samples/preprocessed-multiple/_config.js @@ -11,7 +11,7 @@ export default { src.overwrite(css_idx, css_idx + "--bazitone".length, "--baz"); return { code: src.toString(), - map: src.generateMap({ + map: src.generateDecodedMap({ source: filename, hires: true, includeContent: false @@ -24,7 +24,7 @@ export default { src.prependLeft(idx, " "); return { code: src.toString(), - map: src.generateMap({ + map: src.generateDecodedMap({ source: filename, hires: true, includeContent: false @@ -37,7 +37,7 @@ export default { src.prependLeft(idx, " "); return { code: src.toString(), - map: src.generateMap({ + map: src.generateDecodedMap({ source: filename, hires: true, includeContent: false diff --git a/test/sourcemaps/samples/sourcemap-names/_config.js b/test/sourcemaps/samples/sourcemap-names/_config.js index 96c9698132..35c7badb29 100644 --- a/test/sourcemaps/samples/sourcemap-names/_config.js +++ b/test/sourcemaps/samples/sourcemap-names/_config.js @@ -10,7 +10,7 @@ function replace(search, replace, content, src, options = { storeName: true }) { function result(src, filename) { return { code: src.toString(), - map: src.generateMap({ + map: src.generateDecodedMap({ source: filename, hires: true, includeContent: false diff --git a/test/sourcemaps/samples/sourcemap-sources/_config.js b/test/sourcemaps/samples/sourcemap-sources/_config.js index 2c3e66c3b4..999fb20dfe 100644 --- a/test/sourcemaps/samples/sourcemap-sources/_config.js +++ b/test/sourcemaps/samples/sourcemap-sources/_config.js @@ -4,8 +4,8 @@ function add(bundle, filename, source) { bundle.addSource({ filename, content: new MagicString(source), - separator: '\n', - //separator: '', // ERROR. probably a bug in magic-string + separator: '\n' + //separator: '' // ERROR. probably a bug in magic-string }); } @@ -15,7 +15,7 @@ function result(bundle, filename) { map: bundle.generateMap({ file: filename, includeContent: false, - hires: false + hires: true // required for remapping }) }; } @@ -26,7 +26,7 @@ export default { 'foo.js', 'bar.js', 'foo2.js', - 'bar2.js', + 'bar2.js' ], preprocess: [ { @@ -34,8 +34,8 @@ export default { const bundle = new Bundle(); add(bundle, filename, content); - add(bundle, 'foo.js', 'var answer = 42;'); - add(bundle, 'bar.js', 'console.log(answer);'); + add(bundle, 'foo.js', 'var answer = 42; // foo.js\n'); + add(bundle, 'bar.js', 'console.log(answer); // bar.js\n'); return result(bundle, filename); } @@ -45,8 +45,8 @@ export default { const bundle = new Bundle(); add(bundle, filename, content); - add(bundle, 'foo2.js', 'var answer2 = 84;'); - add(bundle, 'bar2.js', 'console.log(answer2);'); + add(bundle, 'foo2.js', 'var answer2 = 84; // foo2.js\n'); + add(bundle, 'bar2.js', 'console.log(answer2); // bar2.js\n'); return result(bundle, filename); } diff --git a/test/sourcemaps/samples/sourcemap-sources/test.js b/test/sourcemaps/samples/sourcemap-sources/test.js index 98fb64fc6a..78a4c80a17 100644 --- a/test/sourcemaps/samples/sourcemap-sources/test.js +++ b/test/sourcemaps/samples/sourcemap-sources/test.js @@ -5,12 +5,12 @@ export function test({ assert, preprocessed, js }) { // 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);'], + [js, 'foo.js', 'answer = 42;', 4], + [js, 'bar.js', 'console.log(answer);', 0], + [js, 'foo2.js', 'answer2 = 84;', 4], + [js, 'bar2.js', 'console.log(answer2);', 0] ] - .forEach(([where, sourcefile, content]) => { + .forEach(([where, sourcefile, content, column]) => { assert.deepEqual( where.mapConsumer.originalPositionFor( @@ -20,7 +20,7 @@ export function test({ assert, preprocessed, js }) { source: sourcefile, name: null, line: 1, - column: 0 + column }, `failed to locate "${content}" from "${sourcefile}"` ); diff --git a/test/sourcemaps/samples/warn-on-encoded-mappings/_config.js b/test/sourcemaps/samples/warn-on-encoded-mappings/_config.js new file mode 100644 index 0000000000..d33573e27d --- /dev/null +++ b/test/sourcemaps/samples/warn-on-encoded-mappings/_config.js @@ -0,0 +1,58 @@ +import MagicString from 'magic-string'; + +// TODO move util fns to test index.js + +function result(filename, src, options = {}) { + const map_fn = options.encodeMappings ? src.generateMap : src.generateDecodedMap; + delete options.encodeMappings; + return { + code: src.toString(), + map: map_fn.apply(src, [{ + source: filename, + hires: true, + includeContent: false, + ...options + }]) + }; +} + +function replace_all(src, search, replace) { + let idx = src.original.indexOf(search); + if (idx == -1) throw new Error('search not found in src'); + do { + src.overwrite(idx, idx + search.length, replace); + } while ((idx = src.original.indexOf(search, idx + 1)) != -1); +} + +export default { + + js_map_sources: [], // test component has no scripts + + preprocess: [ + // preprocessor 0 + { markup: ({ content, filename }) => { + const src = new MagicString(content); + replace_all(src, 'replace_me', 'version_1'); + return result(filename, src, { encodeMappings: true }); + } }, + // 1 + { markup: ({ content, filename }) => { + const src = new MagicString(content); + replace_all(src, 'version_1', 'version_2'); + return result(filename, src); + } }, + // 2 + { markup: ({ content, filename }) => { + const src = new MagicString(content); + replace_all(src, 'version_2', 'version_3'); + return result(filename, src, { encodeMappings: true }); + } }, + // 3 + { markup: ({ content, filename }) => { + const src = new MagicString(content); + replace_all(src, 'version_3', 'version_4'); + return result(filename, src); + } } + ] + +}; diff --git a/test/sourcemaps/samples/warn-on-encoded-mappings/input.svelte b/test/sourcemaps/samples/warn-on-encoded-mappings/input.svelte new file mode 100644 index 0000000000..ac8f9b30ab --- /dev/null +++ b/test/sourcemaps/samples/warn-on-encoded-mappings/input.svelte @@ -0,0 +1,2 @@ +replace_me +replace_me diff --git a/test/sourcemaps/samples/warn-on-encoded-mappings/test.js b/test/sourcemaps/samples/warn-on-encoded-mappings/test.js new file mode 100644 index 0000000000..9e25f21e55 --- /dev/null +++ b/test/sourcemaps/samples/warn-on-encoded-mappings/test.js @@ -0,0 +1,13 @@ +export function test({ assert, preprocessed, js }) { + + assert.equal(preprocessed.error, undefined); + + // TODO can we automate this test? + // we need the output of console.log + // to test the warning message. + // or use a different method for warnings? + + // expected warning message: + // warning. svelte.preprocess received encoded sourcemaps (index 0, 2). [....] + +}