-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Fix some issues with preprocess source maps #5754
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
Changes from 8 commits
100eba3
5441835
7ac102a
d540800
c842d16
1c241dd
3830216
20499ee
35d7628
e8a2c28
b5ffaf3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,21 +13,22 @@ function last_line_length(s: string) { | |
|
||
// mutate map in-place | ||
export function sourcemap_add_offset( | ||
map: DecodedSourceMap, offset: SourceLocation | ||
map: DecodedSourceMap, offset: SourceLocation, source_index: number | ||
) { | ||
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 | ||
if (map.mappings.length == 0) return; | ||
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; | ||
// shift only segments that belong to component source file | ||
if (seg[1] === source_index) { // also ensures that seg.length >= 4 | ||
// shift column if it points at the first line | ||
if (seg[2] === 0) { | ||
seg[3] += offset.column; | ||
} | ||
// shift line | ||
seg[2] += offset.line; | ||
} | ||
Comment on lines
-30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be nice to have a test for "only shift lines of one source file" if your solution is right, then we get another problem: the sourcemap.names array is not sorted, so its not predictable a more radical solution: instead of file names, use file paths. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think that I have a different vision of the build pipeline:
Is there anything missing, you disagree with or that I should describe more clearly? I'd prefer not to land something cryptic or/and broken :) P.S. Just out of interest, is it synthetic or do you know setups when preprocess step combines multiple components into one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yes! sorry. ("dear mila, please just read the source")
yes .. i wrongly assumed that the basename-function is applied to the preprocessor-result
this is my problem with this solution .. short answer: for consistency with the existing svelte.compile good news: i am happy with your patch : ) long answer: [email protected] + [email protected] [email protected] + [email protected]
the actual reason here is MagicString, who turns file paths into basenames: // svelte/src/compiler/compile/render_dom/index.ts
const css = component.stylesheet.render(
options.filename, // file path
!options.customElement
);
// svelte/src/compiler/compile/css/Stylesheet.ts
// Stylesheet.render
return {
code: code.toString(),
map: code.generateMap({
includeContent: true,
source: this.filename, // file path
file
})
};
// --> result.map.sources: basename
// magic-string/src/MagicString.js
// MagicString.generateDecodedMap
return {
// path to basename
file: options.file ? options.file.split(/[/\\]/).pop() : null,
sources: [options.source ? getRelativePath(options.file || '', options.source) : null],
sourcesContent: options.includeContent ? [this.original] : [null],
names,
mappings: mappings.raw
}; so the path-to-basename conversion is done for consistency with other sourcemaps, generated by svelte.compile There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If someone needs more control we could add additional option But I'm not sure that such feature is really needed and worth complication both in usage and implementation since usually preprocess is done in-memory and can be treated as atomic operation with compile. I might be wrong here though. But then it can be implemented separately. So I would say it's more about simplicity, not consistency. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we add a test for the basename conversion? something like .... import { magic_string_bundle } from '../../helpers';
const external_filename = 'external_code.css';
export const external_code = `/* external code start */
span {
--external_code-var: 1px;
}
/* external code end */`;
const component_filepath = 'src/input.svelte';
const component_basename = 'input.svelte';
export default {
js_map_sources: [component_basename],
css_map_sources: [component_basename, external_filename],
preprocess: [
{
style: ({ content, filename }) => {
return magic_string_bundle([
{ code: external_code, filename: external_filename },
{ code: content, filename }
]);
}
}
],
options: {
filename: component_filepath
}
};
yes .. low priority stuff what i meant was: for example
when makes easier the debugging of larger projects with many files + folders There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sorry, will response ASAP. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @milahu I think debugging should be fine. Bundler (or dev server) gives us filename, usually absolute or relative to cwd. We return compiled code and source map with sources relative to given filename (as they are stored in map). I think it's up to bundler/server to correctly resolve sources in resulting map. I tried some simple cases with rollup and it seemed to work as expected. Would appreciate if you could help me finding some repro with broken debugging experience so I could play with it if you have some time :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well thats easy .. actual result: expeted result: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made the filename relative to cwd in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I cloned it, launched and console logged the same. I also did some tweak after, will describe in my next comment. But if I hover App.svelte:2, I see full url to correct source file. Clicking also navigates to where I would expect. |
||
} | ||
} | ||
} | ||
|
@@ -97,6 +98,9 @@ export class StringWithSourcemap { | |
return this; | ||
} | ||
|
||
// compute last line length before mutating | ||
const column_offset = last_line_length(this.string); | ||
|
||
this.string += other.string; | ||
|
||
const m1 = this.map; | ||
|
@@ -117,24 +121,24 @@ export class StringWithSourcemap { | |
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]]; | ||
if (seg[1] >= 0) seg[1] = new_source_idx[seg[1]]; | ||
if (seg[4] >= 0) 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]]; | ||
if (seg[1] >= 0) 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]]; | ||
if (seg[4] >= 0) seg[4] = new_name_idx[seg[4]]; | ||
} | ||
} | ||
} | ||
|
@@ -146,7 +150,6 @@ export class StringWithSourcemap { | |
// 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++) { | ||
|
@@ -164,7 +167,17 @@ export class StringWithSourcemap { | |
} | ||
|
||
static from_processed(string: string, map?: DecodedSourceMap): StringWithSourcemap { | ||
if (map) return new StringWithSourcemap(string, map); | ||
if (map) { | ||
// ensure that count of source map mappings lines | ||
// is equal to count of generated code lines | ||
// (some tools may produce less) | ||
const missing_lines = string.split('\n').length - map.mappings.length; | ||
dummdidumm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for (let i = 0; i < missing_lines; i++) { | ||
map.mappings.push([]); | ||
} | ||
return new StringWithSourcemap(string, map); | ||
} | ||
|
||
if (string == '') return new StringWithSourcemap(); | ||
map = { version: 3, names: [], sources: [], mappings: [] }; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { magic_string_bundle } from '../../helpers'; | ||
|
||
export const COMMON = ':global(html) { height: 100%; }\n'; | ||
|
||
// TODO: removing '\n' breaks test | ||
// - _actual.svelte.map looks correct | ||
// - _actual.css.map adds reference to </style> on input.svelte | ||
// - Most probably caused by bug in current magic-string version (fixed in 0.25.7) | ||
export const STYLES = '.awesome { color: orange; }\n'; | ||
|
||
export default { | ||
css_map_sources: ['common.scss', 'styles.scss'], | ||
js_map_sources: [], | ||
preprocess: [ | ||
{ | ||
style: () => { | ||
return magic_string_bundle([ | ||
{ filename: 'common.scss', code: COMMON }, | ||
{ filename: 'styles.scss', code: STYLES } | ||
]); | ||
} | ||
} | ||
] | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<style lang="scss" src="./styles.scss"></style> | ||
|
||
<div class="awesome">Divs ftw!</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { assert_mapped } from '../../helpers'; | ||
import { COMMON, STYLES } from './_config'; | ||
|
||
export function test({ input, preprocessed }) { | ||
// Transformed script, main file | ||
assert_mapped({ | ||
filename: 'input.svelte', | ||
code: 'Divs ftw!', | ||
input: input.locate, | ||
preprocessed | ||
}); | ||
|
||
// External files | ||
assert_mapped({ | ||
filename: 'common.scss', | ||
code: 'height: 100%;', | ||
input: COMMON, | ||
preprocessed | ||
}); | ||
assert_mapped({ | ||
filename: 'styles.scss', | ||
code: 'color: orange;', | ||
input: STYLES, | ||
preprocessed | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { magic_string_bundle } from '../../helpers'; | ||
|
||
export const component_filepath = 'src/input.svelte'; | ||
|
||
export const component_file_basename = 'input.svelte'; | ||
|
||
// as output by external tool for src/external_code.css (relative to src/input.svelte) | ||
export const external_relative_filename = 'external_code.css'; | ||
|
||
const external_code = ` | ||
span { | ||
--external_code-var: 1px; | ||
} | ||
`; | ||
|
||
export default { | ||
css_map_sources: [external_relative_filename], | ||
js_map_sources: [], | ||
preprocess: [ | ||
{ | ||
style: ({ content, filename }) => { | ||
const external =`/* Filename from preprocess: ${filename} */` + external_code; | ||
return magic_string_bundle([ | ||
{ code: external, filename: external_relative_filename }, | ||
{ code: content, filename } | ||
]); | ||
} | ||
} | ||
], | ||
options: { | ||
filename: component_filepath | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<style src="external_code.css"></style> | ||
|
||
<span>Hello world</span> |
Uh oh!
There was an error while loading. Please reload this page.