Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consume and generate sourcemaps #1863

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/cli/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
29 changes: 28 additions & 1 deletion src/compile/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SourceMapGenerator, SourceMapConsumer } from 'source-map';
import { assign } from '../shared';
import Stats from '../Stats';
import parse from '../parse/index';
Expand All @@ -6,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);
Expand Down Expand Up @@ -89,7 +91,32 @@ 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 cwd = process.cwd();
const inputConsumer = new SourceMapConsumer(options.sourceMap);

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();

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();
}

return result;
} catch (err) {
onerror(err);
}
Expand Down
1 change: 1 addition & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface CompileOptions {
legacy?: boolean;
customElement?: CustomElementOptions | true;
css?: boolean;
sourceMap?: string | object;

preserveComments?: boolean | false;

Expand Down
159 changes: 137 additions & 22 deletions src/preprocess/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +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: {
Expand Down Expand Up @@ -31,6 +34,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',
Expand All @@ -43,22 +64,74 @@ async function replaceTagContents(
if (match) {
const attributes: Record<string, string | boolean> = 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) {
return (
source.slice(0, match.index) +
`<${type}>${processed.code}</${type}>` +
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++;
}

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}</${type}>` +
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(
options.filename
? { file: relative(process.cwd(), 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);
}

throw err;
}
}

return source;
return { code: source };
}

export default async function preprocess(
Expand All @@ -67,24 +140,62 @@ export default async function preprocess(
) {
const { markup, style, script } = options;


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;
throw err;
}
}

let allMaps = new SourceMapGenerator(
options.filename ? { file: relative(process.cwd(), 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 {
Expand All @@ -95,6 +206,10 @@ export default async function preprocess(

toString() {
return source;
},

getMap() {
return allMaps.toJSON();
}
};
}