diff --git a/.changeset/khaki-mails-scream.md b/.changeset/khaki-mails-scream.md new file mode 100644 index 000000000000..2591c99b08fb --- /dev/null +++ b/.changeset/khaki-mails-scream.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: introduce `rootDir` compiler option, make `filename` relative to it diff --git a/.changeset/kind-doors-grin.md b/.changeset/kind-doors-grin.md new file mode 100644 index 000000000000..7609efcb4c09 --- /dev/null +++ b/.changeset/kind-doors-grin.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: rename `__svelte_meta.filename` to `__svelte_meta.file` to align with svelte 4 diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index d5edba75107d..77338537d2b8 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -19,10 +19,10 @@ export { default as preprocess } from './preprocess/index.js'; * @returns {import('#compiler').CompileResult} */ export function compile(source, options) { - try { - state.reset({ source, filename: options.filename }); + const validated = validate_component_options(options, ''); + state.reset(source, validated); - const validated = validate_component_options(options, ''); + try { let parsed = _parse(source); const { customElement: customElementOptions, ...parsed_options } = parsed.options || {}; @@ -49,7 +49,7 @@ export function compile(source, options) { return result; } catch (e) { if (e instanceof CompileError) { - handle_compile_error(e, options.filename, source); + handle_compile_error(e); } throw e; @@ -65,16 +65,16 @@ export function compile(source, options) { * @returns {import('#compiler').CompileResult} */ export function compileModule(source, options) { - try { - state.reset({ source, filename: options.filename }); + const validated = validate_module_options(options, ''); + state.reset(source, validated); - const validated = validate_module_options(options, ''); + try { const analysis = analyze_module(parse_acorn(source, false), validated); const result = transform_module(analysis, source, validated); return result; } catch (e) { if (e instanceof CompileError) { - handle_compile_error(e, options.filename, source); + handle_compile_error(e); } throw e; @@ -83,11 +83,9 @@ export function compileModule(source, options) { /** * @param {import('#compiler').CompileError} error - * @param {string | undefined} filename - * @param {string} source */ -function handle_compile_error(error, filename, source) { - error.filename = filename; +function handle_compile_error(error) { + error.filename = state.filename; if (error.position) { const start = state.locator(error.position[0]); @@ -134,11 +132,11 @@ function handle_compile_error(error, filename, source) { * * https://svelte.dev/docs/svelte-compiler#svelte-parse * @param {string} source - * @param {{ filename?: string; modern?: boolean }} [options] + * @param {{ filename?: string; rootDir?: string; modern?: boolean }} [options] * @returns {import('#compiler').Root | import('./types/legacy-nodes.js').LegacyRoot} */ -export function parse(source, options = {}) { - state.reset({ source, filename: options.filename }); +export function parse(source, { filename, rootDir, modern } = {}) { + state.reset(source, { filename, rootDir }); // TODO it's weird to require filename/rootDir here. reconsider the API /** @type {import('#compiler').Root} */ let ast; @@ -146,13 +144,13 @@ export function parse(source, options = {}) { ast = _parse(source); } catch (e) { if (e instanceof CompileError) { - handle_compile_error(e, options.filename, source); + handle_compile_error(e); } throw e; } - return to_public_ast(source, ast, options.modern); + return to_public_ast(source, ast, modern); } /** diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js index 7d5ca365998b..8d1392f821f3 100644 --- a/packages/svelte/src/compiler/migrate/index.js +++ b/packages/svelte/src/compiler/migrate/index.js @@ -18,7 +18,7 @@ import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js'; */ export function migrate(source) { try { - reset({ source, filename: 'migrate.svelte' }); + reset(source, { filename: 'migrate.svelte' }); let parsed = parse(source); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 1be8fa7b7908..11cabf0ee58e 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -8,6 +8,7 @@ import { javascript_visitors_runes } from './visitors/javascript-runes.js'; import { javascript_visitors_legacy } from './visitors/javascript-legacy.js'; import { serialize_get_binding } from './utils.js'; import { render_stylesheet } from '../css/index.js'; +import { filename } from '../../../state.js'; /** * This function ensures visitor sets don't accidentally clobber each other @@ -471,14 +472,7 @@ export function client_component(source, analysis, options) { } if (options.dev) { - if (options.filename) { - let filename = options.filename; - if (/(\/|\w:)/.test(options.filename)) { - // filename is absolute — truncate it - const parts = filename.split(/[/\\]/); - filename = parts.length > 3 ? ['...', ...parts.slice(-3)].join('/') : filename; - } - + if (filename) { // add `App.filename = 'App.svelte'` so that we can print useful messages later body.unshift( b.stmt( diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index ab362e6a1725..6c5b22df3a88 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -27,7 +27,7 @@ import { DOMBooleanAttributes, HYDRATION_END, HYDRATION_START } from '../../../. import { escape_html } from '../../../../escaping.js'; import { sanitize_template_string } from '../../../utils/sanitize_template_string.js'; import { BLOCK_CLOSE, BLOCK_CLOSE_ELSE } from '../../../../internal/server/hydration.js'; -import { locator } from '../../../state.js'; +import { filename, locator } from '../../../state.js'; export const block_open = t_string(``); export const block_close = t_string(``); @@ -2398,14 +2398,7 @@ export function server_component(analysis, options) { body.push(b.export_default(component_function)); } - if (options.dev && options.filename) { - let filename = options.filename; - if (/(\/|\w:)/.test(options.filename)) { - // filename is absolute — truncate it - const parts = filename.split(/[/\\]/); - filename = parts.length > 3 ? ['...', ...parts.slice(-3)].join('/') : filename; - } - + if (options.dev && filename) { // add `App.filename = 'App.svelte'` so that we can print useful messages later body.unshift( b.stmt( diff --git a/packages/svelte/src/compiler/state.js b/packages/svelte/src/compiler/state.js index 0eedb04ac527..a317f74771c3 100644 --- a/packages/svelte/src/compiler/state.js +++ b/packages/svelte/src/compiler/state.js @@ -5,7 +5,11 @@ import { getLocator } from 'locate-character'; /** @type {import('#compiler').Warning[]} */ export let warnings = []; -/** @type {string | undefined} */ +/** + * The filename (if specified in the compiler options) relative to the rootDir (if specified). + * This should not be used in the compiler output except in dev mode + * @type {string | undefined} + */ export let filename; export let locator = getLocator('', { offsetLine: 1 }); @@ -26,14 +30,23 @@ export function pop_ignore() { } /** - * @param {{ - * source: string; - * filename: string | undefined; - * }} options + * @param {string} source + * @param {{ filename?: string, rootDir?: string }} options */ -export function reset(options) { - filename = options.filename; - locator = getLocator(options.source, { offsetLine: 1 }); +export function reset(source, options) { + const root_dir = options.rootDir?.replace(/\\/g, '/'); + filename = options.filename?.replace(/\\/g, '/'); + + if ( + typeof filename === 'string' && + typeof root_dir === 'string' && + filename.startsWith(root_dir) + ) { + // make filename relative to rootDir + filename = filename.replace(root_dir, '').replace(/^[/\\]/, ''); + } + + locator = getLocator(source, { offsetLine: 1 }); warnings = []; ignore_stack = []; } diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index 1b4a09d98952..79cc829d6ba2 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -216,12 +216,22 @@ export interface ModuleCompileOptions { * Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically. */ filename?: string; + + /** + * Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically. + * @default process.cwd() on node-like environments, undefined elsewhere + */ + rootDir?: string; } // The following two somewhat scary looking types ensure that certain types are required but can be undefined still -export type ValidatedModuleCompileOptions = Omit, 'filename'> & { +export type ValidatedModuleCompileOptions = Omit< + Required, + 'filename' | 'rootDir' +> & { filename: ModuleCompileOptions['filename']; + rootDir: ModuleCompileOptions['rootDir']; }; export type ValidatedCompileOptions = ValidatedModuleCompileOptions & diff --git a/packages/svelte/src/compiler/validate-options.js b/packages/svelte/src/compiler/validate-options.js index a55258550d4d..f40fab06be34 100644 --- a/packages/svelte/src/compiler/validate-options.js +++ b/packages/svelte/src/compiler/validate-options.js @@ -10,6 +10,10 @@ import * as w from './warnings.js'; const common = { filename: string(undefined), + // default to process.cwd() where it exists to replicate svelte4 behavior + // see https://github.com/sveltejs/svelte/blob/b62fc8c8fd2640c9b99168f01b9d958cb2f7574f/packages/svelte/src/compiler/compile/Component.js#L211 + rootDir: string(typeof process !== 'undefined' ? process.cwd?.() : undefined), + dev: boolean(false), generate: validator('client', (input, keypath) => { diff --git a/packages/svelte/src/internal/client/dev/elements.js b/packages/svelte/src/internal/client/dev/elements.js index b39b51b1c753..0d061f26e518 100644 --- a/packages/svelte/src/internal/client/dev/elements.js +++ b/packages/svelte/src/internal/client/dev/elements.js @@ -34,7 +34,7 @@ export function add_locations(fn, filename, locations) { function assign_location(element, filename, location) { // @ts-expect-error element.__svelte_meta = { - loc: { filename, line: location[0], column: location[1] } + loc: { file: filename, line: location[0], column: location[1] } }; if (location[2]) { diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index 3b39c91710b9..a5ba7f7dcac2 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -116,7 +116,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat // @ts-expect-error element.__svelte_meta = { loc: { - filename, + file: filename, line: location[0], column: location[1] } diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index 197e56041440..6f3a0d90485b 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -130,6 +130,7 @@ export function runtime_suite(runes: boolean) { async function common_setup(cwd: string, runes: boolean | undefined, config: RuntimeTest) { const compileOptions: CompileOptions = { generate: 'client', + rootDir: cwd, ...config.compileOptions, immutable: config.immutable, accessors: 'accessors' in config ? config.accessors : true, diff --git a/packages/svelte/tests/runtime-runes/samples/export-binding/_config.js b/packages/svelte/tests/runtime-runes/samples/export-binding/_config.js index 5f027696348d..d3b9c8e4eb69 100644 --- a/packages/svelte/tests/runtime-runes/samples/export-binding/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/export-binding/_config.js @@ -6,5 +6,5 @@ export default test({ }, error: 'bind_invalid_export\n' + - 'Component .../export-binding/counter/index.svelte has an export named `increment` that a consumer component is trying to access using `bind:increment`, which is disallowed. Instead, use `bind:this` (e.g. ``) and then access the property on the bound component instance (e.g. `component.increment`)' + 'Component counter/index.svelte has an export named `increment` that a consumer component is trying to access using `bind:increment`, which is disallowed. Instead, use `bind:this` (e.g. ``) and then access the property on the bound component instance (e.g. `component.increment`)' }); diff --git a/packages/svelte/tests/runtime-runes/samples/invalid-html-ssr/_config.js b/packages/svelte/tests/runtime-runes/samples/invalid-html-ssr/_config.js index dcddca949905..26d6b495e7a6 100644 --- a/packages/svelte/tests/runtime-runes/samples/invalid-html-ssr/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/invalid-html-ssr/_config.js @@ -32,7 +32,7 @@ export default test({ if (variant === 'hydrate') { assert.equal( log[0], - '`

` (.../samples/invalid-html-ssr/Component.svelte:1:0) cannot contain `

` (.../samples/invalid-html-ssr/main.svelte:5:0)\n\n' + + '`

` (Component.svelte:1:0) cannot contain `

` (main.svelte:5:0)\n\n' + 'This can cause content to shift around as the browser repairs the HTML, and will likely result in a `hydration_mismatch` warning.' ); } diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js index 5cef52a86bd1..3a30c2b031b3 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-discouraged/_config.js @@ -26,7 +26,7 @@ export default test({ assert.htmlEqual(target.innerHTML, ``); assert.deepEqual(warnings, [ - '.../samples/non-local-mutation-discouraged/Counter.svelte mutated a value owned by .../samples/non-local-mutation-discouraged/main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead' + 'Counter.svelte mutated a value owned by main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead' ]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js index 1aef4cc3f19a..87474a05cc33 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-2/_config.js @@ -8,6 +8,6 @@ export default test({ }, warnings: [ - '.../samples/non-local-mutation-with-binding-2/Intermediate.svelte passed a value to .../samples/non-local-mutation-with-binding-2/Counter.svelte with `bind:`, but the value is owned by .../samples/non-local-mutation-with-binding-2/main.svelte. Consider creating a binding between .../samples/non-local-mutation-with-binding-2/main.svelte and .../samples/non-local-mutation-with-binding-2/Intermediate.svelte' + 'Intermediate.svelte passed a value to Counter.svelte with `bind:`, but the value is owned by main.svelte. Consider creating a binding between main.svelte and Intermediate.svelte' ] }); diff --git a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js index fd0ace4fda14..66e51843808e 100644 --- a/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/non-local-mutation-with-binding-3/_config.js @@ -33,7 +33,7 @@ export default test({ assert.htmlEqual(target.innerHTML, ``); assert.deepEqual(warnings, [ - '.../samples/non-local-mutation-with-binding-3/Counter.svelte mutated a value owned by .../samples/non-local-mutation-with-binding-3/main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead' + 'Counter.svelte mutated a value owned by main.svelte. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead' ]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js b/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js index 3cc17bd7b386..fa0994c3704a 100644 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/props-not-bindable-spread/_config.js @@ -9,5 +9,5 @@ export default test({ error: 'bind_not_bindable\n' + - 'A component is attempting to bind to a non-bindable property `count` belonging to .../samples/props-not-bindable-spread/Counter.svelte (i.e. ``). To mark a property as bindable: `let { count = $bindable() } = $props()`' + 'A component is attempting to bind to a non-bindable property `count` belonging to Counter.svelte (i.e. ``). To mark a property as bindable: `let { count = $bindable() } = $props()`' }); diff --git a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js b/packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js index 34ab163829f8..fa0994c3704a 100644 --- a/packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/props-not-bindable/_config.js @@ -9,5 +9,5 @@ export default test({ error: 'bind_not_bindable\n' + - 'A component is attempting to bind to a non-bindable property `count` belonging to .../samples/props-not-bindable/Counter.svelte (i.e. ``). To mark a property as bindable: `let { count = $bindable() } = $props()`' + 'A component is attempting to bind to a non-bindable property `count` belonging to Counter.svelte (i.e. ``). To mark a property as bindable: `let { count = $bindable() } = $props()`' }); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-meta-dynamic/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-meta-dynamic/_config.js index 6490ddb2f49b..d145bb7cb6b1 100644 --- a/packages/svelte/tests/runtime-runes/samples/svelte-meta-dynamic/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/svelte-meta-dynamic/_config.js @@ -14,14 +14,14 @@ export default test({ // @ts-expect-error assert.deepEqual(ps[0].__svelte_meta.loc, { - filename: '.../samples/svelte-meta-dynamic/main.svelte', + file: 'main.svelte', line: 7, column: 0 }); // @ts-expect-error assert.deepEqual(ps[1].__svelte_meta.loc, { - filename: '.../samples/svelte-meta-dynamic/main.svelte', + file: 'main.svelte', line: 13, column: 0 }); @@ -32,7 +32,7 @@ export default test({ // @ts-expect-error assert.deepEqual(strong.__svelte_meta.loc, { - filename: '.../samples/svelte-meta-dynamic/main.svelte', + file: 'main.svelte', line: 10, column: 1 }); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-meta/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-meta/_config.js index 300e030c4e42..d145bb7cb6b1 100644 --- a/packages/svelte/tests/runtime-runes/samples/svelte-meta/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/svelte-meta/_config.js @@ -14,14 +14,14 @@ export default test({ // @ts-expect-error assert.deepEqual(ps[0].__svelte_meta.loc, { - filename: '.../samples/svelte-meta/main.svelte', + file: 'main.svelte', line: 7, column: 0 }); // @ts-expect-error assert.deepEqual(ps[1].__svelte_meta.loc, { - filename: '.../samples/svelte-meta/main.svelte', + file: 'main.svelte', line: 13, column: 0 }); @@ -32,7 +32,7 @@ export default test({ // @ts-expect-error assert.deepEqual(strong.__svelte_meta.loc, { - filename: '.../samples/svelte-meta/main.svelte', + file: 'main.svelte', line: 10, column: 1 }); diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 39695c7bade0..95504639862c 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -728,6 +728,12 @@ declare module 'svelte/compiler' { * Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically. */ filename?: string; + + /** + * Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically. + * @default process.cwd() on node-like environments, undefined elsewhere + */ + rootDir?: string; } type DeclarationKind = @@ -2549,6 +2555,12 @@ declare module 'svelte/types/compiler/interfaces' { * Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically. */ filename?: string; + + /** + * Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically. + * @default process.cwd() on node-like environments, undefined elsewhere + */ + rootDir?: string; } /** * - `html` — the default, for e.g. `

` or ``