mirror of https://github.com/sveltejs/svelte
Preprocessor sourcemap support (#5584)
Co-authored-by: Milan Hauth <milahu@gmail.com> Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>pull/5698/head
parent
1fa46fde4f
commit
dcfbd69516
@ -0,0 +1,275 @@
|
|||||||
|
import { DecodedSourceMap, RawSourceMap, SourceMapLoader } from '@ampproject/remapping/dist/types/types';
|
||||||
|
import remapping from '@ampproject/remapping';
|
||||||
|
import { SourceMap } from 'magic-string';
|
||||||
|
|
||||||
|
type SourceLocation = {
|
||||||
|
line: number;
|
||||||
|
column: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function last_line_length(s: string) {
|
||||||
|
return s.length - s.lastIndexOf('\n') - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutate map in-place
|
||||||
|
export function sourcemap_add_offset(
|
||||||
|
map: DecodedSourceMap, offset: SourceLocation
|
||||||
|
) {
|
||||||
|
if (map.mappings.length == 0) return map;
|
||||||
|
// shift columns in first line
|
||||||
|
const segment_list = map.mappings[0];
|
||||||
|
for (let segment = 0; segment < segment_list.length; segment++) {
|
||||||
|
const seg = segment_list[segment];
|
||||||
|
if (seg[3]) seg[3] += offset.column;
|
||||||
|
}
|
||||||
|
// shift lines
|
||||||
|
for (let line = 0; line < map.mappings.length; line++) {
|
||||||
|
const segment_list = map.mappings[line];
|
||||||
|
for (let segment = 0; segment < segment_list.length; segment++) {
|
||||||
|
const seg = segment_list[segment];
|
||||||
|
if (seg[2]) seg[2] += offset.line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function merge_tables<T>(this_table: T[], other_table: T[]): [T[], number[], boolean, boolean] {
|
||||||
|
const new_table = this_table.slice();
|
||||||
|
const idx_map = [];
|
||||||
|
other_table = other_table || [];
|
||||||
|
let val_changed = false;
|
||||||
|
for (const [other_idx, other_val] of other_table.entries()) {
|
||||||
|
const this_idx = this_table.indexOf(other_val);
|
||||||
|
if (this_idx >= 0) {
|
||||||
|
idx_map[other_idx] = this_idx;
|
||||||
|
} else {
|
||||||
|
const new_idx = new_table.length;
|
||||||
|
new_table[new_idx] = other_val;
|
||||||
|
idx_map[other_idx] = new_idx;
|
||||||
|
val_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let idx_changed = val_changed;
|
||||||
|
if (val_changed) {
|
||||||
|
if (idx_map.find((val, idx) => val != idx) === undefined) {
|
||||||
|
// idx_map is identity map [0, 1, 2, 3, 4, ....]
|
||||||
|
idx_changed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [new_table, idx_map, val_changed, idx_changed];
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushArray<T>(_this: T[], other: T[]) {
|
||||||
|
// We use push to mutate in place for memory and perf reasons
|
||||||
|
// We use the for loop instead of _this.push(...other) to avoid the JS engine's function argument limit (65,535 in JavascriptCore)
|
||||||
|
for (let i = 0; i < other.length; i++) {
|
||||||
|
_this.push(other[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StringWithSourcemap {
|
||||||
|
string: string;
|
||||||
|
map: DecodedSourceMap;
|
||||||
|
|
||||||
|
constructor(string = '', map: DecodedSourceMap = null) {
|
||||||
|
this.string = string;
|
||||||
|
if (map) {
|
||||||
|
this.map = map as DecodedSourceMap;
|
||||||
|
} else {
|
||||||
|
this.map = {
|
||||||
|
version: 3,
|
||||||
|
mappings: [],
|
||||||
|
sources: [],
|
||||||
|
names: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* concat in-place (mutable), return this (chainable)
|
||||||
|
* will also mutate the `other` object
|
||||||
|
*/
|
||||||
|
concat(other: StringWithSourcemap): StringWithSourcemap {
|
||||||
|
// noop: if one is empty, return the other
|
||||||
|
if (other.string == '') return this;
|
||||||
|
if (this.string == '') {
|
||||||
|
this.string = other.string;
|
||||||
|
this.map = other.map;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.string += other.string;
|
||||||
|
|
||||||
|
const m1 = this.map;
|
||||||
|
const m2 = other.map;
|
||||||
|
|
||||||
|
if (m2.mappings.length == 0) return this;
|
||||||
|
|
||||||
|
// combine sources and names
|
||||||
|
const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(m1.sources, m2.sources);
|
||||||
|
const [names, new_name_idx, names_changed, names_idx_changed] = merge_tables(m1.names, m2.names);
|
||||||
|
|
||||||
|
if (sources_changed) m1.sources = sources;
|
||||||
|
if (names_changed) m1.names = names;
|
||||||
|
|
||||||
|
// unswitched loops are faster
|
||||||
|
if (sources_idx_changed && names_idx_changed) {
|
||||||
|
for (let line = 0; line < m2.mappings.length; line++) {
|
||||||
|
const segment_list = m2.mappings[line];
|
||||||
|
for (let segment = 0; segment < segment_list.length; segment++) {
|
||||||
|
const seg = segment_list[segment];
|
||||||
|
if (seg[1]) seg[1] = new_source_idx[seg[1]];
|
||||||
|
if (seg[4]) seg[4] = new_name_idx[seg[4]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (sources_idx_changed) {
|
||||||
|
for (let line = 0; line < m2.mappings.length; line++) {
|
||||||
|
const segment_list = m2.mappings[line];
|
||||||
|
for (let segment = 0; segment < segment_list.length; segment++) {
|
||||||
|
const seg = segment_list[segment];
|
||||||
|
if (seg[1]) seg[1] = new_source_idx[seg[1]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (names_idx_changed) {
|
||||||
|
for (let line = 0; line < m2.mappings.length; line++) {
|
||||||
|
const segment_list = m2.mappings[line];
|
||||||
|
for (let segment = 0; segment < segment_list.length; segment++) {
|
||||||
|
const seg = segment_list[segment];
|
||||||
|
if (seg[4]) seg[4] = new_name_idx[seg[4]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// combine the mappings
|
||||||
|
|
||||||
|
// combine
|
||||||
|
// 1. last line of first map
|
||||||
|
// 2. first line of second map
|
||||||
|
// columns of 2 must be shifted
|
||||||
|
|
||||||
|
const column_offset = last_line_length(this.string);
|
||||||
|
if (m2.mappings.length > 0 && column_offset > 0) {
|
||||||
|
const first_line = m2.mappings[0];
|
||||||
|
for (let i = 0; i < first_line.length; i++) {
|
||||||
|
first_line[i][0] += column_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// combine last line + first line
|
||||||
|
pushArray(m1.mappings[m1.mappings.length - 1], m2.mappings.shift());
|
||||||
|
|
||||||
|
// append other lines
|
||||||
|
pushArray(m1.mappings, m2.mappings);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
static from_processed(string: string, map?: DecodedSourceMap): StringWithSourcemap {
|
||||||
|
if (map) return new StringWithSourcemap(string, map);
|
||||||
|
if (string == '') return new StringWithSourcemap();
|
||||||
|
map = { version: 3, names: [], sources: [], mappings: [] };
|
||||||
|
|
||||||
|
// add empty SourceMapSegment[] for every line
|
||||||
|
const line_count = (string.match(/\n/g) || '').length;
|
||||||
|
for (let i = 0; i < line_count; i++) map.mappings.push([]);
|
||||||
|
return new StringWithSourcemap(string, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
static from_source(
|
||||||
|
source_file: string, source: string, offset?: SourceLocation
|
||||||
|
): StringWithSourcemap {
|
||||||
|
if (!offset) offset = { line: 0, column: 0 };
|
||||||
|
const map: DecodedSourceMap = { version: 3, names: [], sources: [source_file], mappings: [] };
|
||||||
|
if (source == '') return new StringWithSourcemap(source, map);
|
||||||
|
|
||||||
|
// we create a high resolution identity map here,
|
||||||
|
// we know that it will eventually be merged with svelte's map,
|
||||||
|
// at which stage the resolution will decrease.
|
||||||
|
const line_list = source.split('\n');
|
||||||
|
for (let line = 0; line < line_list.length; line++) {
|
||||||
|
map.mappings.push([]);
|
||||||
|
const token_list = line_list[line].split(/([^\d\w\s]|\s+)/g);
|
||||||
|
for (let token = 0, column = 0; token < token_list.length; token++) {
|
||||||
|
if (token_list[token] == '') continue;
|
||||||
|
map.mappings[line].push([column, 0, offset.line + line, column]);
|
||||||
|
column += token_list[token].length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// shift columns in first line
|
||||||
|
const segment_list = map.mappings[0];
|
||||||
|
for (let segment = 0; segment < segment_list.length; segment++) {
|
||||||
|
segment_list[segment][3] += offset.column;
|
||||||
|
}
|
||||||
|
return new StringWithSourcemap(source, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function combine_sourcemaps(
|
||||||
|
filename: string,
|
||||||
|
sourcemap_list: Array<DecodedSourceMap | RawSourceMap>
|
||||||
|
): RawSourceMap {
|
||||||
|
if (sourcemap_list.length == 0) return null;
|
||||||
|
|
||||||
|
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`
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// browser vs node.js
|
||||||
|
const b64enc = typeof btoa == 'function' ? btoa : b => Buffer.from(b).toString('base64');
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const preprocessor_map = typeof preprocessor_map_input === 'string' ? JSON.parse(preprocessor_map_input) : preprocessor_map_input;
|
||||||
|
|
||||||
|
const result_map = combine_sourcemaps(
|
||||||
|
filename,
|
||||||
|
[
|
||||||
|
svelte_map as RawSourceMap,
|
||||||
|
preprocessor_map
|
||||||
|
]
|
||||||
|
) as RawSourceMap;
|
||||||
|
|
||||||
|
// Svelte expects a SourceMap which includes toUrl and toString. Instead of wrapping our output in a class,
|
||||||
|
// we just tack on the extra properties.
|
||||||
|
Object.defineProperties(result_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,' + b64enc(this.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result_map as SourceMap;
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
import MagicString from 'magic-string';
|
||||||
|
|
||||||
|
export function magic_string_preprocessor_result(filename: string, src: MagicString) {
|
||||||
|
return {
|
||||||
|
code: src.toString(),
|
||||||
|
map: src.generateMap({
|
||||||
|
source: filename,
|
||||||
|
hires: true,
|
||||||
|
includeContent: false
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magic_string_replace_all(src: MagicString, search: string, replace: string) {
|
||||||
|
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, { storeName: true });
|
||||||
|
} while ((idx = src.original.indexOf(search, idx + 1)) != -1);
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import MagicString from 'magic-string';
|
||||||
|
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
compile_options: {
|
||||||
|
dev: true
|
||||||
|
},
|
||||||
|
preprocess: [
|
||||||
|
{ style: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
magic_string_replace_all(src, '--replace-me-once', '\n --done-replace-once');
|
||||||
|
magic_string_replace_all(src, '--replace-me-twice', '\n--almost-done-replace-twice');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
} },
|
||||||
|
{ style: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
magic_string_replace_all(src, '--almost-done-replace-twice', '\n --done-replace-twice');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
} }
|
||||||
|
]
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
<h1>Testing Styles</h1>
|
||||||
|
<h2>Testing Styles 2</h2>
|
||||||
|
<div>Testing Styles 3</div>
|
||||||
|
<script>export const b = 2;</script>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
--replace-me-once: red;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
--replace-me-twice: green;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
--keep-me: blue;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,40 @@
|
|||||||
|
import { SourceMapConsumer } from 'source-map';
|
||||||
|
|
||||||
|
const b64dec = s => Buffer.from(s, 'base64').toString();
|
||||||
|
|
||||||
|
export async function test({ assert, css, js }) {
|
||||||
|
|
||||||
|
// We check that the css source map embedded in the js is accurate
|
||||||
|
const match = js.code.match(/\tstyle\.textContent = "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?";\n/);
|
||||||
|
assert.notEqual(match, null);
|
||||||
|
|
||||||
|
const [mimeType, encoding, cssMapBase64] = match.slice(2);
|
||||||
|
assert.equal(mimeType, 'application/json');
|
||||||
|
assert.equal(encoding, 'utf-8');
|
||||||
|
|
||||||
|
const cssMapJson = b64dec(cssMapBase64);
|
||||||
|
css.mapConsumer = await new SourceMapConsumer(cssMapJson);
|
||||||
|
|
||||||
|
// TODO make util fn + move to test index.js
|
||||||
|
const sourcefile = 'input.svelte';
|
||||||
|
[
|
||||||
|
// TODO how to get line + column numbers?
|
||||||
|
[css, '--keep-me', 13, 2],
|
||||||
|
[css, '--done-replace-once', 6, 5],
|
||||||
|
[css, '--done-replace-twice', 9, 5]
|
||||||
|
]
|
||||||
|
.forEach(([where, content, line, column]) => {
|
||||||
|
assert.deepEqual(
|
||||||
|
where.mapConsumer.originalPositionFor(
|
||||||
|
where.locate_1(content)
|
||||||
|
),
|
||||||
|
{
|
||||||
|
source: sourcefile,
|
||||||
|
name: null,
|
||||||
|
line,
|
||||||
|
column
|
||||||
|
},
|
||||||
|
`failed to locate "${content}" from "${sourcefile}"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import MagicString from 'magic-string';
|
||||||
|
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
js_map_sources: [], // test component has no scripts
|
||||||
|
|
||||||
|
preprocess: {
|
||||||
|
markup: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
magic_string_replace_all(src, 'replace me', 'success');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,2 @@
|
|||||||
|
<h1>decoded-sourcemap</h1>
|
||||||
|
<div>replace me</div>
|
@ -0,0 +1,19 @@
|
|||||||
|
export function test({ assert, input, preprocessed }) {
|
||||||
|
|
||||||
|
const expected = input.locate('replace me');
|
||||||
|
|
||||||
|
const start = preprocessed.locate('success');
|
||||||
|
|
||||||
|
const actualbar = preprocessed.mapConsumer.originalPositionFor({
|
||||||
|
line: start.line + 1,
|
||||||
|
column: start.column
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(actualbar, {
|
||||||
|
source: 'input.svelte',
|
||||||
|
name: 'replace me',
|
||||||
|
line: expected.line + 1,
|
||||||
|
column: expected.column
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import MagicString from 'magic-string';
|
||||||
|
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
preprocess: {
|
||||||
|
markup: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
magic_string_replace_all(src, 'baritone', 'bar');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
export let foo;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{foo.baritone.baz}
|
@ -0,0 +1,32 @@
|
|||||||
|
export function test({ assert, input, js }) {
|
||||||
|
const expectedBar = input.locate('baritone.baz');
|
||||||
|
const expectedBaz = input.locate('.baz');
|
||||||
|
|
||||||
|
let start = js.locate('bar.baz');
|
||||||
|
|
||||||
|
const actualbar = js.mapConsumer.originalPositionFor({
|
||||||
|
line: start.line + 1,
|
||||||
|
column: start.column
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(actualbar, {
|
||||||
|
source: 'input.svelte',
|
||||||
|
name: 'baritone',
|
||||||
|
line: expectedBar.line + 1,
|
||||||
|
column: expectedBar.column
|
||||||
|
});
|
||||||
|
|
||||||
|
start = js.locate('.baz');
|
||||||
|
|
||||||
|
const actualbaz = js.mapConsumer.originalPositionFor({
|
||||||
|
line: start.line + 1,
|
||||||
|
column: start.column
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(actualbaz, {
|
||||||
|
source: 'input.svelte',
|
||||||
|
name: null,
|
||||||
|
line: expectedBaz.line + 1,
|
||||||
|
column: expectedBaz.column
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import MagicString from 'magic-string';
|
||||||
|
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
preprocess: {
|
||||||
|
markup: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
magic_string_replace_all(src, 'baritone', 'bar');
|
||||||
|
magic_string_replace_all(src, '--bazitone', '--baz');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
},
|
||||||
|
script: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
const idx = content.indexOf('bar');
|
||||||
|
src.prependLeft(idx, ' ');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
},
|
||||||
|
style: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
const idx = content.indexOf('--baz');
|
||||||
|
src.prependLeft(idx, ' ');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,9 @@
|
|||||||
|
<script>
|
||||||
|
export let foo = { baritone: 5 }
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
background-color: var(--bazitone);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h1>multiple {foo}</h1>
|
@ -0,0 +1,32 @@
|
|||||||
|
export function test({ assert, input, js, css }) {
|
||||||
|
const expectedBar = input.locate('baritone');
|
||||||
|
const expectedBaz = input.locate('--bazitone');
|
||||||
|
|
||||||
|
let start = js.locate('bar');
|
||||||
|
|
||||||
|
const actualbar = js.mapConsumer.originalPositionFor({
|
||||||
|
line: start.line + 1,
|
||||||
|
column: start.column
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(actualbar, {
|
||||||
|
source: 'input.svelte',
|
||||||
|
name: 'baritone',
|
||||||
|
line: expectedBar.line + 1,
|
||||||
|
column: expectedBar.column
|
||||||
|
});
|
||||||
|
|
||||||
|
start = css.locate('--baz');
|
||||||
|
|
||||||
|
const actualbaz = css.mapConsumer.originalPositionFor({
|
||||||
|
line: start.line + 1,
|
||||||
|
column: start.column
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(actualbaz, {
|
||||||
|
source: 'input.svelte',
|
||||||
|
name: '--bazitone',
|
||||||
|
line: expectedBaz.line + 1,
|
||||||
|
column: expectedBaz.column
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import MagicString from 'magic-string';
|
||||||
|
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
preprocess: {
|
||||||
|
script: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
magic_string_replace_all(src, 'baritone', 'bar');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,9 @@
|
|||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
export let foo = { baritone: { baz: 5 } }
|
||||||
|
</script>
|
||||||
|
<h1>{foo.bar.baz}</h1>
|
@ -0,0 +1,32 @@
|
|||||||
|
export function test({ assert, input, js }) {
|
||||||
|
const expectedBar = input.locate('baritone:');
|
||||||
|
const expectedBaz = input.locate('baz:');
|
||||||
|
|
||||||
|
let start = js.locate('bar:');
|
||||||
|
|
||||||
|
const actualbar = js.mapConsumer.originalPositionFor({
|
||||||
|
line: start.line + 1,
|
||||||
|
column: start.column
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(actualbar, {
|
||||||
|
source: 'input.svelte',
|
||||||
|
name: 'baritone',
|
||||||
|
line: expectedBar.line + 1,
|
||||||
|
column: expectedBar.column
|
||||||
|
}, "couldn't find bar: in source");
|
||||||
|
|
||||||
|
start = js.locate('baz:');
|
||||||
|
|
||||||
|
const actualbaz = js.mapConsumer.originalPositionFor({
|
||||||
|
line: start.line + 1,
|
||||||
|
column: start.column
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(actualbaz, {
|
||||||
|
source: 'input.svelte',
|
||||||
|
name: null,
|
||||||
|
line: expectedBaz.line + 1,
|
||||||
|
column: expectedBaz.column
|
||||||
|
}, "couldn't find baz: in source");
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import MagicString from 'magic-string';
|
||||||
|
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
preprocess: {
|
||||||
|
style: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
magic_string_replace_all(src, 'baritone', 'bar');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,12 @@
|
|||||||
|
<h1>Testing Styles</h1>
|
||||||
|
<h2>Testing Styles 2</h2>
|
||||||
|
<script>export const b = 2;</script>
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
--baritone: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
--baz: blue;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,32 @@
|
|||||||
|
export function test({ assert, input, css }) {
|
||||||
|
const expectedBar = input.locate('--baritone');
|
||||||
|
const expectedBaz = input.locate('--baz');
|
||||||
|
|
||||||
|
let start = css.locate('--bar');
|
||||||
|
|
||||||
|
const actualbar = css.mapConsumer.originalPositionFor({
|
||||||
|
line: start.line + 1,
|
||||||
|
column: start.column
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(actualbar, {
|
||||||
|
source: 'input.svelte',
|
||||||
|
name: null,
|
||||||
|
line: expectedBar.line + 1,
|
||||||
|
column: expectedBar.column
|
||||||
|
}, "couldn't find bar in source");
|
||||||
|
|
||||||
|
start = css.locate('--baz');
|
||||||
|
|
||||||
|
const actualbaz = css.mapConsumer.originalPositionFor({
|
||||||
|
line: start.line + 1,
|
||||||
|
column: start.column
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(actualbaz, {
|
||||||
|
source: 'input.svelte',
|
||||||
|
name: null,
|
||||||
|
line: expectedBaz.line + 1,
|
||||||
|
column: expectedBaz.column
|
||||||
|
}, "couldn't find baz in source");
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
import MagicString from 'magic-string';
|
||||||
|
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
preprocess: [
|
||||||
|
{
|
||||||
|
markup: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
magic_string_replace_all(src, 'baritone', 'bar');
|
||||||
|
magic_string_replace_all(src,'--bazitone', '--baz');
|
||||||
|
magic_string_replace_all(src,'old_name_1', 'temp_new_name_1');
|
||||||
|
magic_string_replace_all(src,'old_name_2', 'temp_new_name_2');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
markup: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
magic_string_replace_all(src, 'temp_new_name_1', 'temp_temp_new_name_1');
|
||||||
|
magic_string_replace_all(src, 'temp_new_name_2', 'temp_temp_new_name_2');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
markup: ({ content, filename }) => {
|
||||||
|
const src = new MagicString(content);
|
||||||
|
magic_string_replace_all(src, 'temp_temp_new_name_1', 'new_name_1');
|
||||||
|
magic_string_replace_all(src, 'temp_temp_new_name_2', 'new_name_2');
|
||||||
|
return magic_string_preprocessor_result(filename, src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
@ -0,0 +1,12 @@
|
|||||||
|
<script>
|
||||||
|
export let old_name_1 = { baritone: 5 };
|
||||||
|
let old_name_2 = 'value_2';
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
background-color: var(--bazitone);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h1>use-names</h1>
|
||||||
|
<div>{old_name_1.baritone}</div>
|
||||||
|
<pre style="color: var(--bazitone)">{old_name_2}</pre>
|
@ -0,0 +1,42 @@
|
|||||||
|
// needed for workaround, TODO remove
|
||||||
|
import { getLocator } from 'locate-character';
|
||||||
|
|
||||||
|
export function test({ assert, preprocessed, js, css }) {
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
preprocessed.map.names.sort(),
|
||||||
|
['baritone', '--bazitone', 'old_name_1', 'old_name_2'].sort()
|
||||||
|
);
|
||||||
|
|
||||||
|
function test_name(old_name, new_name, where) {
|
||||||
|
|
||||||
|
let loc = { character: -1 };
|
||||||
|
while (loc = where.locate(new_name, loc.character + 1)) {
|
||||||
|
const actualMapping = where.mapConsumer.originalPositionFor({
|
||||||
|
line: loc.line + 1, column: loc.column
|
||||||
|
});
|
||||||
|
if (actualMapping.line === null) {
|
||||||
|
// location is not mapped - ignore
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
assert.equal(actualMapping.name, old_name);
|
||||||
|
}
|
||||||
|
if (loc === undefined) {
|
||||||
|
// workaround for bug in locate-character, TODO remove
|
||||||
|
// https://github.com/Rich-Harris/locate-character/pull/5
|
||||||
|
where.locate = getLocator(where.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_name('baritone', 'bar', js);
|
||||||
|
test_name('baritone', 'bar', preprocessed);
|
||||||
|
|
||||||
|
test_name('--bazitone', '--baz', css);
|
||||||
|
test_name('--bazitone', '--baz', preprocessed);
|
||||||
|
|
||||||
|
test_name('old_name_1', 'new_name_1', js);
|
||||||
|
test_name('old_name_1', 'new_name_1', preprocessed);
|
||||||
|
|
||||||
|
test_name('old_name_2', 'new_name_2', js);
|
||||||
|
test_name('old_name_2', 'new_name_2', preprocessed);
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/* eslint-disable import/no-duplicates */
|
||||||
|
/* the code that transforms these to commonjs, can't handle "MagicString, { Bundle } from.." */
|
||||||
|
|
||||||
|
import MagicString from 'magic-string';
|
||||||
|
import { Bundle } from 'magic-string';
|
||||||
|
|
||||||
|
|
||||||
|
function add(bundle, filename, source) {
|
||||||
|
bundle.addSource({
|
||||||
|
filename,
|
||||||
|
content: new MagicString(source),
|
||||||
|
separator: '\n'
|
||||||
|
//separator: '' // ERROR. probably a bug in magic-string
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function result(bundle, filename) {
|
||||||
|
return {
|
||||||
|
code: bundle.toString(),
|
||||||
|
map: bundle.generateMap({
|
||||||
|
file: filename,
|
||||||
|
includeContent: false,
|
||||||
|
hires: true // required for remapping
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
js_map_sources: [
|
||||||
|
'input.svelte',
|
||||||
|
'foo.js',
|
||||||
|
'bar.js',
|
||||||
|
'foo2.js',
|
||||||
|
'bar2.js'
|
||||||
|
],
|
||||||
|
preprocess: [
|
||||||
|
{
|
||||||
|
script: ({ content, filename }) => {
|
||||||
|
const bundle = new Bundle();
|
||||||
|
|
||||||
|
add(bundle, filename, content);
|
||||||
|
add(bundle, 'foo.js', 'var answer = 42; // foo.js\n');
|
||||||
|
add(bundle, 'bar.js', 'console.log(answer); // bar.js\n');
|
||||||
|
|
||||||
|
return result(bundle, filename);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
script: ({ content, filename }) => {
|
||||||
|
const bundle = new Bundle();
|
||||||
|
|
||||||
|
add(bundle, filename, content);
|
||||||
|
add(bundle, 'foo2.js', 'var answer2 = 84; // foo2.js\n');
|
||||||
|
add(bundle, 'bar2.js', 'console.log(answer2); // bar2.js\n');
|
||||||
|
|
||||||
|
return result(bundle, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
@ -0,0 +1,4 @@
|
|||||||
|
<script>
|
||||||
|
export let name;
|
||||||
|
</script>
|
||||||
|
<h1>sourcemap-sources</h1>
|
@ -0,0 +1,29 @@
|
|||||||
|
export function test({ assert, preprocessed, js }) {
|
||||||
|
|
||||||
|
assert.equal(preprocessed.error, undefined);
|
||||||
|
|
||||||
|
// sourcemap stores location only for 'answer = 42;'
|
||||||
|
// not for 'var answer = 42;'
|
||||||
|
[
|
||||||
|
[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, column]) => {
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
where.mapConsumer.originalPositionFor(
|
||||||
|
where.locate_1(content)
|
||||||
|
),
|
||||||
|
{
|
||||||
|
source: sourcefile,
|
||||||
|
name: null,
|
||||||
|
line: 1,
|
||||||
|
column
|
||||||
|
},
|
||||||
|
`failed to locate "${content}" from "${sourcefile}"`
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in new issue