add: parse attached sourcemap from preprocessor

pull/5854/head
Milan Hauth 6 years ago
parent 2eda5b0bf3
commit 0b2b77cb19

@ -1,7 +1,12 @@
import { 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, combine_sourcemaps } from '../utils/string_with_sourcemap';
import {
StringWithSourcemap,
sourcemap_add_offset,
combine_sourcemaps,
parse_attached_sourcemap
} from '../utils/string_with_sourcemap';
export interface Processed {
code: string;
@ -179,6 +184,8 @@ function get_replacement(
const suffix_with_map = StringWithSourcemap.from_source(
file_basename, suffix, get_location(offset + prefix.length + original.length));
parse_attached_sourcemap(processed);
// Convert the preprocessed code and its sourcemap to a StringWithSourcemap
let decoded_map: DecodedSourceMap;
if (processed.map) {

@ -1,6 +1,7 @@
import { DecodedSourceMap, RawSourceMap, SourceMapLoader } from '@ampproject/remapping/dist/types/types';
import remapping from '@ampproject/remapping';
import { SourceMap } from 'magic-string';
import { Processed } from '../preprocess';
type SourceLocation = {
line: number;
@ -255,6 +256,7 @@ export function combine_sourcemaps(
// browser vs node.js
const b64enc = typeof btoa == 'function' ? btoa : b => Buffer.from(b).toString('base64');
const b64dec = typeof atob == 'function' ? atob : a => Buffer.from(a, 'base64').toString();
export function apply_preprocessor_sourcemap(filename: string, svelte_map: SourceMap, preprocessor_map_input: string | DecodedSourceMap | RawSourceMap): SourceMap {
if (!svelte_map || !preprocessor_map_input) return svelte_map;
@ -288,3 +290,26 @@ export function apply_preprocessor_sourcemap(filename: string, svelte_map: Sourc
return result_map as SourceMap;
}
// find attached sourcemap in processed.code
// TODO? handle multiple attached maps, combine with processed.map
export function parse_attached_sourcemap(processed: Processed): void {
const magic_prefix = '\n/*# sourceMappingURL=data:application/json;';
const cut_index = processed.code.lastIndexOf('\n');
const last_line = processed.code.slice(cut_index);
if (magic_prefix != last_line.slice(0, magic_prefix.length)) {
return; // attachment not found
}
if (processed.map) {
throw 'not implemented. '+
'found sourcemap in both processed.code and processed.map. '+
'please pass only one sourcemap.\n'+
'processed.code:\n'+
processed.code.slice(0, 100)+' [....]'; // help to find preprocessor
}
// remove last line
processed.code = processed.code.slice(0, cut_index);
// slice to -2 --> remove trailing '*/'
const b64map = last_line.slice(last_line.indexOf('base64,')+7, -2).trim();
processed.map = b64dec(b64map);
}

@ -31,7 +31,8 @@ describe('sourcemaps', () => {
const inputCode = fs.readFileSync(inputFile, 'utf-8');
const input = {
code: inputCode,
locate: getLocator(inputCode)
locate: getLocator(inputCode),
locate_1: getLocator(inputCode, { offsetLine: 1 })
};
const preprocessed = await svelte.preprocess(
@ -86,12 +87,14 @@ describe('sourcemaps', () => {
assert.deepEqual(
js.map.sources.slice().sort(),
(config.js_map_sources || ['input.svelte']).sort()
(config.js_map_sources || ['input.svelte']).sort(),
'js.map.sources is wrong'
);
if (css.map) {
assert.deepEqual(
css.map.sources.slice().sort(),
(config.css_map_sources || ['input.svelte']).sort()
(config.css_map_sources || ['input.svelte']).sort(),
'css.map.sources is wrong'
);
}

@ -0,0 +1,37 @@
import MagicString from 'magic-string';
let indent_size = 4;
function get_processor(search, replace) {
return ({ content, filename }) => {
let code = content.slice();
const ms = new MagicString(code);
const idx = ms.original.indexOf(search);
if (idx == -1) throw new Error('search not found in src');
ms.overwrite(idx, idx + search.length, replace, { storeName: true });
// change line + column
const indent = Array.from({ length: indent_size }).join(' ');
ms.prependLeft(idx, '\n'+indent);
const map_opts = { source: filename, hires: true, includeContent: false };
const map = ms.generateMap(map_opts);
const attach_line = `\n/*# sourceMappingURL=${map.toUrl()} */`;
code = ms.toString() + attach_line;
indent_size += 2;
return { code };
};
}
export default {
preprocess: [
{ script: get_processor('replace_me_script', 'done_replace_script_1') },
{ script: get_processor('done_replace_script_1', 'done_replace_script_2') },
{ style: get_processor('.replace_me_style', '.done_replace_style_1') },
{ style: get_processor('.done_replace_style_1', '.done_replace_style_2') }
]
};

@ -0,0 +1,11 @@
<style>
.replace_me_style {
color: red;
}
</style>
<script>
let
replace_me_script = 'hello'
;
</script>
<h1 class="done_replace_style_2">{done_replace_script_2}</h1>

@ -0,0 +1,45 @@
import * as assert from 'assert';
const get_line_column = obj => ({ line: obj.line, column: obj.column });
export function test({ input, css, js }) {
let out_obj, loc_output, actual, loc_input, expected;
out_obj = js;
// we need the seconds occurence in output.js
loc_output = out_obj.locate_1('done_replace_script_2');
loc_output = out_obj.locate_1('done_replace_script_2', loc_output.character + 1);
actual = out_obj.mapConsumer.originalPositionFor(loc_output);
loc_input = input.locate_1('replace_me_script');
expected = {
source: 'input.svelte',
name: 'replace_me_script',
...get_line_column(loc_input)
};
assert.deepEqual(actual, expected);
out_obj = css;
loc_output = out_obj.locate_1('.done_replace_style_2');
actual = out_obj.mapConsumer.originalPositionFor(loc_output);
// first occurence in input.svelte
loc_input = input.locate_1('.replace_me_style');
expected = {
source: 'input.svelte',
name: '.replace_me_style',
...get_line_column(loc_input)
};
assert.deepEqual(actual, expected);
assert.equal(
js.code.indexOf('\n/*# sourceMappingURL=data:application/json;base64,'),
-1,
'magic-comment attachments were NOT removed'
);
assert.equal(
css.code.indexOf('\n/*# sourceMappingURL=data:application/json;base64,'),
-1,
'magic-comment attachments were NOT removed'
);
}
Loading…
Cancel
Save