From 00a14486f6f445a1ff058cd9d8b441cc1e387c42 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Sat, 24 Nov 2018 14:01:11 -0800 Subject: [PATCH 1/5] Generate maps while preprocessing --- src/preprocess/index.ts | 86 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/src/preprocess/index.ts b/src/preprocess/index.ts index 047b124991bc..8af069902785 100644 --- a/src/preprocess/index.ts +++ b/src/preprocess/index.ts @@ -1,4 +1,5 @@ import { SourceMap } from 'magic-string'; +import { SourceMapConsumer, SourceMapGenerator } from 'source-map'; export interface PreprocessOptions { markup?: (options: { @@ -31,6 +32,24 @@ function parseAttributes(str: string) { return attrs; } +function addMappings(generator: SourceMapConsumer, map: string | object) { + const consumer = new SourceMapConsumer(map); + consumer.eachMapping(mapping => { + generator.addMapping({ + source: mapping.source, + name: mapping.name, + original: { + line: mapping.originalLine, + column: mapping.originalColumn + }, + generated: { + line: mapping.generatedLine, + column: mapping.generatedColumn + } + }); + }); +} + async function replaceTagContents( source, type: 'script' | 'style', @@ -50,15 +69,49 @@ async function replaceTagContents( }); if (processed && processed.code) { - return ( + const code = ( source.slice(0, match.index) + `<${type}>${processed.code}` + source.slice(match.index + match[0].length) ); + + // Shift sourcemap to the appropriate line + if (processed.map) { + const consumer = new SourceMapConsumer(processed.map); + + // Line number of the match + let line = 0; + for(let i = 0; i <= match.index; i = source.indexOf('\n', i + 1)) { + line++; + } + + // Skip the + line++; + + const generator = new SourceMapGenerator({ file: options.filename }) + consumer.eachMapping(mapping => { + generator.addMapping({ + source: mapping.source, + name: mapping.name, + original: { + line: mapping.originalLine + line, + column: mapping.originalColumn + }, + generated: { + line: mapping.generatedLine + line, + column: mapping.generatedColumn + } + }); + }); + + return { code, map: generator.toJSON() }; + } + + return { code }; } } - return source; + return { code: source }; } export default async function preprocess( @@ -67,6 +120,9 @@ export default async function preprocess( ) { const { markup, style, script } = options; + + let markupMap: SourceMapGenerator; + if (!!markup) { const processed: { code: string, @@ -77,14 +133,32 @@ export default async function preprocess( }); source = processed.code; + if (processed.map) { + markupMap = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(processed.map)); + } } + let allMaps = new SourceMapGenerator({ file: options.filename }); + if (!!style) { - source = await replaceTagContents(source, 'style', style, options); + const { code, map } = await replaceTagContents(source, 'style', style, options); + source = code; + if (map) { + addMappings(allMaps, map); + } } if (!!script) { - source = await replaceTagContents(source, 'script', script, options); + const { code, map } = await replaceTagContents(source, 'script', script, options); + source = code; + if (map) { + addMappings(allMaps, map); + } + } + + if (markupMap) { + markupMap.applySourceMap(new SourceMapConsumer(allMaps.toString())); + allMaps = markupMap; } return { @@ -95,6 +169,10 @@ export default async function preprocess( toString() { return source; + }, + + getMap() { + return allMaps.toJSON(); } }; } \ No newline at end of file From f2cbf11cc20d47b9df3816b0060d3080d8026706 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Sat, 24 Nov 2018 14:01:43 -0800 Subject: [PATCH 2/5] Consume sourcemaps --- src/cli/compile.ts | 1 + src/compile/index.ts | 17 ++++++++++++++++- src/interfaces.ts | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/cli/compile.ts b/src/cli/compile.ts index 47141f234649..54f728647956 100644 --- a/src/cli/compile.ts +++ b/src/cli/compile.ts @@ -107,6 +107,7 @@ function compileFile(input, output, options) { let compiled; try { + delete options.sourceMap; // compiler doesn't use this option compiled = svelte.compile(source, options); } catch (err) { error(err); diff --git a/src/compile/index.ts b/src/compile/index.ts index 59e204b81630..4418de408138 100644 --- a/src/compile/index.ts +++ b/src/compile/index.ts @@ -1,3 +1,4 @@ +import { SourceMapGenerator, SourceMapConsumer } from 'source-map'; import { assign } from '../shared'; import Stats from '../Stats'; import parse from '../parse/index'; @@ -89,7 +90,21 @@ export default function compile(source: string, options: CompileOptions = {}) { return renderSSR(component, options); } - return renderDOM(component, options); + const result = renderDOM(component, options); + + if (options.sourceMap) { + const inputConsumer = new SourceMapConsumer(options.sourceMap); + + const jsSourceMap = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(result.js.map)); + jsSourceMap.applySourceMap(inputConsumer); + result.js.map = jsSourceMap.toJSON(); + + const cssSourceMap = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(result.css.map)); + cssSourceMap.applySourceMap(inputConsumer); + result.css.map = cssSourceMap.toJSON(); + } + + return result; } catch (err) { onerror(err); } diff --git a/src/interfaces.ts b/src/interfaces.ts index b637a727bbae..8609576841d7 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -57,6 +57,7 @@ export interface CompileOptions { legacy?: boolean; customElement?: CustomElementOptions | true; css?: boolean; + sourceMap?: string | object; preserveComments?: boolean | false; From b251c574eb40d7601df400012f15e19418e492e3 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Sat, 1 Dec 2018 15:47:55 -0800 Subject: [PATCH 3/5] Attempt to add shifted error frames --- src/preprocess/index.ts | 129 +++++++++++++++++++++++++--------------- 1 file changed, 80 insertions(+), 49 deletions(-) diff --git a/src/preprocess/index.ts b/src/preprocess/index.ts index 8af069902785..f2249f92681b 100644 --- a/src/preprocess/index.ts +++ b/src/preprocess/index.ts @@ -1,5 +1,6 @@ import { SourceMap } from 'magic-string'; import { SourceMapConsumer, SourceMapGenerator } from 'source-map'; +import getCodeFrame from '../utils/getCodeFrame.js'; export interface PreprocessOptions { markup?: (options: { @@ -62,52 +63,67 @@ async function replaceTagContents( if (match) { const attributes: Record = parseAttributes(match[1]); const content: string = match[2]; - const processed: { code: string, map?: SourceMap | string } = await preprocessor({ - content, - attributes, - filename : options.filename - }); - if (processed && processed.code) { - const code = ( - source.slice(0, match.index) + - `<${type}>${processed.code}` + - source.slice(match.index + match[0].length) - ); + // Line number of the match + let line = 0; + for (let i = 0; i <= match.index; i = source.indexOf('\n', i + 1)) { + line++; + } - // Shift sourcemap to the appropriate line - if (processed.map) { - const consumer = new SourceMapConsumer(processed.map); + line--; + + try { + const processed: { + code: string; + map?: SourceMap | string; + } = await preprocessor({ + content, + attributes, + filename: options.filename, + }); + + if (processed && processed.code) { + const code = + source.slice(0, match.index) + + `<${type}>${processed.code}` + + source.slice(match.index + match[0].length); + + // Shift sourcemap to the appropriate line + if (processed.map) { + const consumer = new SourceMapConsumer(processed.map); + + const generator = new SourceMapGenerator({ file: options.filename }); + consumer.eachMapping(mapping => { + generator.addMapping({ + source: mapping.source, + name: mapping.name, + original: { + line: mapping.originalLine + line, + column: mapping.originalColumn, + }, + generated: { + line: mapping.generatedLine + line, + column: mapping.generatedColumn, + }, + }); + }); - // Line number of the match - let line = 0; - for(let i = 0; i <= match.index; i = source.indexOf('\n', i + 1)) { - line++; + return { code, map: generator.toJSON() }; } - // Skip the - line++; - - const generator = new SourceMapGenerator({ file: options.filename }) - consumer.eachMapping(mapping => { - generator.addMapping({ - source: mapping.source, - name: mapping.name, - original: { - line: mapping.originalLine + line, - column: mapping.originalColumn - }, - generated: { - line: mapping.generatedLine + line, - column: mapping.generatedColumn - } - }); - }); - - return { code, map: generator.toJSON() }; + return { code }; + } + } catch (err) { + if (err.line && err.column) { + err.frame = getCodeFrame(source, line + err.line - 1, err.column); + } else if (err.start && err.start.line && err.start.column) { + err.frame = getCodeFrame(source, line + err.start.line - 1, err.column); + } else if (typeof err.start === 'number') { + const start = locate(contents, err.start, { offsetLine: 1 }); + err.frame = getCodeFrame(source, line + start.line - 1, start.column); } - return { code }; + throw err; } } @@ -124,17 +140,32 @@ export default async function preprocess( let markupMap: SourceMapGenerator; if (!!markup) { - const processed: { - code: string, - map?: SourceMap | string - } = await markup({ - content: source, - filename: options.filename - }); + try { + const processed: { + code: string; + map?: SourceMap | string; + } = await markup({ + content: source, + filename: options.filename, + }); + + source = processed.code; + if (processed.map) { + markupMap = SourceMapGenerator.fromSourceMap( + new SourceMapConsumer(processed.map) + ); + } + } catch (err) { + if (err.line && err.column) { + err.frame = getCodeFrame(source, err.line - 1, err.column); + } else if (err.start && err.start.line && err.start.column) { + err.frame = getCodeFrame(source, err.start.line - 1, err.column); + } else if (typeof err.start === 'number') { + const start = locate(source, err.start, { offsetLine: 1 }); + err.frame = getCodeFrame(source, start.line - 1, start.column); + } - source = processed.code; - if (processed.map) { - markupMap = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(processed.map)); + throw err; } } From 3e8452d26d19efd16ab23dd23f0a10045c331172 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Sun, 2 Dec 2018 12:05:58 -0800 Subject: [PATCH 4/5] Use relative source paths --- src/compile/index.ts | 16 ++++++++++++++-- src/preprocess/index.ts | 9 +++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/compile/index.ts b/src/compile/index.ts index 4418de408138..ad9522bf3eae 100644 --- a/src/compile/index.ts +++ b/src/compile/index.ts @@ -7,6 +7,7 @@ import renderSSR from './render-ssr/index'; import { CompileOptions, Warning, Ast } from '../interfaces'; import Component from './Component'; import deprecate from '../utils/deprecate'; +import { relative } from 'path'; function normalize_options(options: CompileOptions): CompileOptions { let normalized = assign({ generate: 'dom', dev: false }, options); @@ -93,13 +94,24 @@ export default function compile(source: string, options: CompileOptions = {}) { const result = renderDOM(component, options); if (options.sourceMap) { + const cwd = process.cwd(); const inputConsumer = new SourceMapConsumer(options.sourceMap); - const jsSourceMap = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(result.js.map)); + result.js.map.sources = result.js.map.sources.map(file => + relative(cwd, file) + ); + const jsSourceMap = SourceMapGenerator.fromSourceMap( + new SourceMapConsumer(result.js.map) + ); jsSourceMap.applySourceMap(inputConsumer); result.js.map = jsSourceMap.toJSON(); - const cssSourceMap = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(result.css.map)); + result.css.map.sources = result.css.map.sources.map(file => + relative(cwd, file) + ); + const cssSourceMap = SourceMapGenerator.fromSourceMap( + new SourceMapConsumer(result.css.map) + ); cssSourceMap.applySourceMap(inputConsumer); result.css.map = cssSourceMap.toJSON(); } diff --git a/src/preprocess/index.ts b/src/preprocess/index.ts index f2249f92681b..bc06878df221 100644 --- a/src/preprocess/index.ts +++ b/src/preprocess/index.ts @@ -1,6 +1,7 @@ import { SourceMap } from 'magic-string'; import { SourceMapConsumer, SourceMapGenerator } from 'source-map'; import getCodeFrame from '../utils/getCodeFrame.js'; +import { relative } from 'path'; export interface PreprocessOptions { markup?: (options: { @@ -92,7 +93,9 @@ async function replaceTagContents( if (processed.map) { const consumer = new SourceMapConsumer(processed.map); - const generator = new SourceMapGenerator({ file: options.filename }); + const generator = new SourceMapGenerator({ + file: relative(process.cwd(), options.filename), + }); consumer.eachMapping(mapping => { generator.addMapping({ source: mapping.source, @@ -169,7 +172,9 @@ export default async function preprocess( } } - let allMaps = new SourceMapGenerator({ file: options.filename }); + let allMaps = new SourceMapGenerator({ + file: relative(process.cwd(), options.filename), + }); if (!!style) { const { code, map } = await replaceTagContents(source, 'style', style, options); From 01bdc9ac3bb5d142bcf42e12ad35b3fcbb8fe9b7 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Sun, 2 Dec 2018 12:13:57 -0800 Subject: [PATCH 5/5] Guard against undefined filename --- src/preprocess/index.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/preprocess/index.ts b/src/preprocess/index.ts index bc06878df221..e72c1c3cecdb 100644 --- a/src/preprocess/index.ts +++ b/src/preprocess/index.ts @@ -92,10 +92,11 @@ async function replaceTagContents( // Shift sourcemap to the appropriate line if (processed.map) { const consumer = new SourceMapConsumer(processed.map); - - const generator = new SourceMapGenerator({ - file: relative(process.cwd(), options.filename), - }); + const generator = new SourceMapGenerator( + options.filename + ? { file: relative(process.cwd(), options.filename) } + : {} + ); consumer.eachMapping(mapping => { generator.addMapping({ source: mapping.source, @@ -172,9 +173,9 @@ export default async function preprocess( } } - let allMaps = new SourceMapGenerator({ - file: relative(process.cwd(), options.filename), - }); + let allMaps = new SourceMapGenerator( + options.filename ? { file: relative(process.cwd(), options.filename) } : {} + ); if (!!style) { const { code, map } = await replaceTagContents(source, 'style', style, options);