fix sources string, more detailed css source map

pull/10459/head
Simon Holthausen 3 years ago
parent 60771e99a7
commit e799dac5be

@ -348,7 +348,7 @@ export default class Stylesheet {
/** @type {import('#compiler').Style | null} */
ast;
/** @type {string} */
/** @type {string} Source filename (relative to the output file) */
filename;
/** @type {boolean} */
@ -510,10 +510,14 @@ export default class Stylesheet {
const code = new MagicString(source);
// Generate source mappings for the style sheet nodes we have.
// Note that resolution is a bit more coarse than in Svelte 4 because
// our own CSS AST is not as detailed with regards to the node values.
walk(/** @type {import('#compiler').Css.Node} */ (this.ast), null, {
_: (node) => {
_: (node, { next }) => {
code.addSourcemapLocation(node.start);
code.addSourcemapLocation(node.end);
next();
}
});

@ -21,6 +21,7 @@ import { regex_starts_with_newline } from '../patterns.js';
import { create_attribute, is_element_node } from '../nodes.js';
import { DelegatedEvents, namespace_svg } from '../../../constants.js';
import { should_proxy_or_freeze } from '../3-transform/client/utils.js';
import { get_source_name } from '../../utils/mapped_code.js';
/**
* @param {import('#compiler').Script | null} script
@ -343,7 +344,7 @@ export function analyze_component(root, options) {
stylesheet: new Stylesheet({
ast: root.css,
// TODO are any of these necessary or can we just pass in the whole `analysis` object later?
filename: options.filename ?? '<unknown>',
filename: get_source_name(options.filename, options.cssOutputFilename, 'output.css'),
component_name,
get_css_hash: options.cssHash
}),

@ -42,12 +42,11 @@ export function transform_component(analysis, source, options) {
];
}
const source_name = get_source_name(options) || 'input.svelte';
const js = print(program, { sourceMapSource: source_name });
const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte');
const js = print(program, { sourceMapSource: js_source_name });
if (options.sourcemap) {
js.map = apply_preprocessor_sourcemap(
source_name,
js_source_name,
js.map,
/** @type {any} */ (options.sourcemap)
);
@ -55,11 +54,11 @@ export function transform_component(analysis, source, options) {
const css =
analysis.stylesheet.has_styles && !analysis.inject_styles
? analysis.stylesheet.render(source_name, source, options.dev)
? analysis.stylesheet.render(options.cssOutputFilename ?? 'output.css', source, options.dev)
: null;
if (css && options.sourcemap) {
css.map = apply_preprocessor_sourcemap(
source_name,
analysis.stylesheet.filename,
css.map,
/** @type {any} */ (options.sourcemap)
);

@ -403,10 +403,12 @@ function get_basename(filename) {
return /** @type {string} */ (filename.split(/[/\\]/).pop());
}
/** @param {import('#compiler').ValidatedCompileOptions} compile_options */
export function get_source_name(compile_options) {
if (!compile_options.filename) return null;
return compile_options.outputFilename
? get_relative_path(compile_options.outputFilename, compile_options.filename)
: get_basename(compile_options.filename);
/**
* @param {string | undefined} filename
* @param {string | undefined} output_filename
* @param {string} fallback
*/
export function get_source_name(filename, output_filename, fallback) {
if (!filename) return fallback;
return output_filename ? get_relative_path(output_filename, filename) : get_basename(filename);
}

@ -3,7 +3,7 @@ import { test } from '../../test';
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers.js';
export default test({
skip: true, // TODO CSS map
skip: true, // TODO inline CSS map
compileOptions: {
dev: true
},
@ -24,5 +24,45 @@ export default test({
}
}
],
client: []
css: [
'--keep-me: blue',
{ str: '--replace-me-once: red', strGenerated: ' --done-replace-once: red' },
{ str: '--replace-me-twice: green', strGenerated: ' --done-replace-twice: green' }
],
async test({ assert, code_client }) {
// We check that the css source map embedded in the js is accurate
const match = code_client.match(
/\tappend_styles\(target, "svelte-.{6}", "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?"\);\n/
);
assert.notEqual(match, null);
const [mime_type, encoding, css_map_base64] = match.slice(2);
assert.equal(mime_type, 'application/json');
assert.equal(encoding, 'utf-8');
// TODO the idea is to check that the css source map is accurate; maybe do that instead by comparing with the one saved to disk?
const css_map_json = Buffer.from(css_map_base64, 'base64').toString();
css.mapConsumer = await new SourceMapConsumer(css_map_json);
// TODO make util fn + move to test index.js
const sourcefile = 'input.svelte';
[
// TODO: get line and col num from input.svelte rather than hardcoding here
[css, '--keep-me', 13, 2],
[css, '--keep-me', null, 13, 2],
[css, '--done-replace-once', '--replace-me-once', 7, 2],
[css, '--done-replace-twice', '--replace-me-twice', 10, 2]
].forEach(([where, content, name, line, column]) => {
assert.deepEqual(
where.mapConsumer.originalPositionFor(where.locate_1(content)),
{
source: sourcefile,
name,
line,
column
},
`failed to locate "${content}" from "${sourcefile}"`
);
});
}
});

@ -1,7 +1,7 @@
import { test } from '../../test';
export default test({
skip: true, // TODO no template mappings, style mappings wrong
skip: true, // TODO no template mappings
client: ['foo'],
css: ['.foo']
css: [{ str: '.foo', strGenerated: '.foo.svelte-sg04hs' }]
});

@ -3,7 +3,6 @@ import { test } from '../../test';
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers.js';
export default test({
skip: true, // TODO css map seems wrong
preprocess: {
markup: ({ content, filename = '' }) => {
const src = new MagicString(content);
@ -25,5 +24,7 @@ export default test({
}
},
client: [{ str: 'baritone', strGenerated: 'bar' }],
css: [{ str: '--bazitone', strGenerated: '--baz' }]
css: [
{ str: 'background-color: var(--bazitone)', strGenerated: 'background-color: var( --baz)' }
]
});

@ -1,32 +0,0 @@
export function test({ assert, input, js, css }) {
const expected_bar = input.locate('baritone');
const expected_baz = 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: expected_bar.line + 1,
column: expected_bar.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: expected_baz.line + 1,
column: expected_baz.column
});
}

@ -3,7 +3,6 @@ import { test } from '../../test';
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers.js';
export default test({
skip: true, // TODO css source map wrong?
preprocess: {
style: ({ content, filename = '' }) => {
const src = new MagicString(content);
@ -11,6 +10,5 @@ export default test({
return magic_string_preprocessor_result(filename, src);
}
},
client: [],
css: [{ str: 'baritone', strGenerated: 'bar' }]
css: [{ str: '--baritone: red', strGenerated: '--bar: red' }]
});

@ -3,7 +3,6 @@ import { test } from '../../test';
import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers.js';
export default test({
skip: true,
preprocess: [
{
markup: ({ content, filename = '' }) => {
@ -31,5 +30,23 @@ export default test({
return magic_string_preprocessor_result(filename, src);
}
}
]
],
client: [
{ str: 'baritone', strGenerated: 'bar' },
{ str: 'old_name_1', strGenerated: 'new_name_1' },
{ str: 'old_name_2', strGenerated: 'new_name_2' }
],
preprocessed: [
{ str: 'baritone', strGenerated: 'bar' },
{ str: 'old_name_1', strGenerated: 'new_name_1' },
{ str: 'old_name_2', strGenerated: 'new_name_2' },
{ str: '--bazitone', strGenerated: '--baz' }
],
css: [{ str: 'background-color: var(--bazitone)', strGenerated: 'background-color: var(--baz)' }],
test({ assert, map_preprocessed }) {
assert.deepEqual(
map_preprocessed.names.sort(),
['baritone', '--bazitone', 'old_name_1', 'old_name_2'].sort()
);
}
});

@ -15,5 +15,21 @@ export default test({
]);
}
}
]
],
test({ input, preprocessed }) {
// Part from component, should be with offset
assert_mapped({
code: '--component-var',
input: input.locate,
preprocessed
});
// Part from external file, should be without offset
assert_mapped({
filename: 'external.css',
code: '--external-var',
input: EXTERNAL,
preprocessed
});
}
});

@ -1,19 +0,0 @@
import { assert_mapped } from '../../helpers.js';
import { EXTERNAL } from './_config';
export function test({ input, preprocessed }) {
// Part from component, should be with offset
assert_mapped({
code: '--component-var',
input: input.locate,
preprocessed
});
// Part from external file, should be without offset
assert_mapped({
filename: 'external.css',
code: '--external-var',
input: EXTERNAL,
preprocessed
});
}

@ -1,5 +1,7 @@
import MagicString, { Bundle } from 'magic-string';
import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping';
import { test } from '../../test';
import { locate } from 'locate-character';
/**
* @param {Bundle} bundle
@ -57,5 +59,27 @@ export default test({
return result(bundle, filename);
}
}
]
],
test({ assert, map_client, code_client }) {
const map = new TraceMap(map_client);
// sourcemap stores location only for 'answer = 42;'
// not for 'var answer = 42;'
/** @type {const} */ ([
['foo.js', 'answer = 42;', 4],
['bar.js', 'console.log(answer);', 0],
['foo2.js', 'answer2 = 84;', 4],
['bar2.js', 'console.log(answer2);', 0]
]).forEach(([sourcefile, content, column]) => {
assert.deepEqual(
originalPositionFor(map, locate(code_client, content)),
{
source: sourcefile,
name: null,
line: 1,
column
},
`failed to locate "${content}" from "${sourcefile}"`
);
});
}
});

@ -1,23 +0,0 @@
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}"`
);
});
}

@ -1,7 +1,6 @@
import { test } from '../../test';
export default test({
client: [],
test({ assert, map_client }) {
assert.deepEqual(map_client.sources, ['input.svelte']);
// TODO do we need to set sourcesContent? We did it in Svelte 4, but why?

@ -21,7 +21,7 @@ export default test({
}
}
],
client: ['count'],
client: ['count', 'setInterval'],
preprocessed: [
{ str: 'let count: number = 0;', strGenerated: 'let count = 0;' },
{ str: 'ITimeoutDestroyer', strGenerated: null },

@ -24,15 +24,16 @@ interface SourcemapTest extends BaseTest {
js_map_sources?: string[];
css_map_sources?: string[];
test?: (obj: {
assert: any;
assert: typeof assert;
input: any;
js: any;
css: any;
map_preprocessed: any;
code_preprocessed: string;
map_client: any;
code_client: any;
}) => void;
client: SourceMapEntry[] | null;
client?: SourceMapEntry[] | null;
server?: SourceMapEntry[];
css?: SourceMapEntry[] | null;
preprocessed?: SourceMapEntry[];
@ -143,6 +144,7 @@ const { test, run } = suite<SourcemapTest>(async (config, cwd) => {
}
let map_client = null;
let code_client = fs.readFileSync(`${cwd}/_output/client/input.svelte.js`, 'utf-8');
if (config.client === null) {
assert.equal(
@ -151,20 +153,33 @@ const { test, run } = suite<SourcemapTest>(async (config, cwd) => {
'Expected no source map'
);
} else {
const output_client = fs.readFileSync(`${cwd}/_output/client/input.svelte.js`, 'utf-8');
map_client = JSON.parse(fs.readFileSync(`${cwd}/_output/client/input.svelte.js.map`, 'utf-8'));
assert.deepEqual(
map_client.sources.slice().sort(),
(config.js_map_sources || ['input.svelte']).sort(),
'js.map.sources is wrong'
);
compare('client', output_client, map_client, config.client);
if (config.client) {
compare('client', code_client, map_client, config.client);
}
}
if (config.client || config.server) {
const output_server = fs.readFileSync(`${cwd}/_output/server/input.svelte.js`, 'utf-8');
const map_server = JSON.parse(
fs.readFileSync(`${cwd}/_output/server/input.svelte.js.map`, 'utf-8')
);
compare('server', output_server, map_server, config.server ?? config.client);
compare(
'server',
output_server,
map_server,
config.server ??
// Reuse client sourcemap test for server
config.client ??
[]
);
}
if (config.css !== undefined) {
@ -214,6 +229,7 @@ const { test, run } = suite<SourcemapTest>(async (config, cwd) => {
config.test({
assert,
map_client,
code_client,
map_preprocessed,
code_preprocessed /*, input, preprocessed: output_client, js, css*/
});

Loading…
Cancel
Save