diff --git a/.prettierignore b/.prettierignore index d5c124353c..72cd10aca8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,6 +7,7 @@ packages/**/config/*.js # packages/svelte packages/svelte/messages/**/*.md +packages/svelte/scripts/_bundle.js packages/svelte/src/compiler/errors.js packages/svelte/src/compiler/warnings.js packages/svelte/src/internal/client/errors.js @@ -25,8 +26,7 @@ packages/svelte/tests/hydration/samples/*/_expected.html packages/svelte/tests/hydration/samples/*/_override.html packages/svelte/types packages/svelte/compiler/index.js -playgrounds/sandbox/input/**.svelte -playgrounds/sandbox/output +playgrounds/sandbox/src/* # sites/svelte.dev sites/svelte.dev/static/svelte-app.json diff --git a/documentation/docs/07-misc/03-typescript.md b/documentation/docs/07-misc/03-typescript.md index fbf8817069..ff33885fb8 100644 --- a/documentation/docs/07-misc/03-typescript.md +++ b/documentation/docs/07-misc/03-typescript.md @@ -83,7 +83,7 @@ If you're using tools like Rollup or Webpack instead, install their respective S When using TypeScript, make sure your `tsconfig.json` is setup correctly. -- Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2022`, or a `target` of at least `ES2015` alongside [`useDefineForClassFields`](https://www.typescriptlang.org/tsconfig/#useDefineForClassFields). This ensures that rune declarations on class fields are not messed with, which would break the Svelte compiler +- Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2015` so classes are not compiled to functions - Set [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax) to `true` so that imports are left as-is - Set [`isolatedModules`](https://www.typescriptlang.org/tsconfig/#isolatedModules) to `true` so that each file is looked at in isolation. TypeScript has a few features which require cross-file analysis and compilation, which the Svelte compiler and tooling like Vite don't do. diff --git a/eslint.config.js b/eslint.config.js index d6c977a36a..d7044fc9f1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -87,6 +87,7 @@ export default [ '**/*.d.ts', '**/tests', 'packages/svelte/scripts/process-messages/templates/*.js', + 'packages/svelte/scripts/_bundle.js', 'packages/svelte/src/compiler/errors.js', 'packages/svelte/src/internal/client/errors.js', 'packages/svelte/src/internal/client/warnings.js', diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index e254be754f..ef64091ca3 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.34.9 + +### Patch Changes + +- fix: ensure unowned deriveds can add themselves as reactions while connected ([#16249](https://github.com/sveltejs/svelte/pull/16249)) + ## 5.34.8 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 6933ecafbe..2d88d2a051 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.34.8", + "version": "5.34.9", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/scripts/check-treeshakeability.js b/packages/svelte/scripts/check-treeshakeability.js index 1501ee6954..e883496fe2 100644 --- a/packages/svelte/scripts/check-treeshakeability.js +++ b/packages/svelte/scripts/check-treeshakeability.js @@ -118,36 +118,40 @@ const bundle = await bundle_code( ).js.code ); -if (!bundle.includes('hydrate_node') && !bundle.includes('hydrate_next')) { - // eslint-disable-next-line no-console - console.error(`✅ Hydration code treeshakeable`); -} else { - failed = true; - // eslint-disable-next-line no-console - console.error(`❌ Hydration code not treeshakeable`); -} +/** + * @param {string} case_name + * @param {string[]} strings + */ +function check_bundle(case_name, ...strings) { + for (const string of strings) { + const index = bundle.indexOf(string); + if (index >= 0) { + // eslint-disable-next-line no-console + console.error(`❌ ${case_name} not treeshakeable`); + failed = true; -if (!bundle.includes('component_context.l')) { - // eslint-disable-next-line no-console - console.error(`✅ Legacy code treeshakeable`); -} else { - failed = true; + let lines = bundle.slice(index - 500, index + 500).split('\n'); + const target_line = lines.findIndex((line) => line.includes(string)); + // mark the failed line + lines = lines + .map((line, i) => (i === target_line ? `> ${line}` : `| ${line}`)) + .slice(target_line - 5, target_line + 6); + // eslint-disable-next-line no-console + console.error('The first failed line:\n' + lines.join('\n')); + return; + } + } // eslint-disable-next-line no-console - console.error(`❌ Legacy code not treeshakeable`); + console.error(`✅ ${case_name} treeshakeable`); } -if (!bundle.includes(`'CreatedAt'`)) { - // eslint-disable-next-line no-console - console.error(`✅ $inspect.trace code treeshakeable`); -} else { - failed = true; - // eslint-disable-next-line no-console - console.error(`❌ $inspect.trace code not treeshakeable`); -} +check_bundle('Hydration code', 'hydrate_node', 'hydrate_next'); +check_bundle('Legacy code', 'component_context.l'); +check_bundle('$inspect.trace', `'CreatedAt'`); if (failed) { // eslint-disable-next-line no-console - console.error(bundle); + console.error('Full bundle at', path.resolve('scripts/_bundle.js')); fs.writeFileSync('scripts/_bundle.js', bundle); } diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index 11db091936..2aa9a820d2 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -3,7 +3,6 @@ /** @import { AST } from './public.js' */ import { walk as zimmerframe_walk } from 'zimmerframe'; import { convert } from './legacy.js'; -import { parse as parse_acorn } from './phases/1-parse/acorn.js'; import { parse as _parse } from './phases/1-parse/index.js'; import { remove_typescript_nodes } from './phases/1-parse/remove_typescript_nodes.js'; import { analyze_component, analyze_module } from './phases/2-analyze/index.js'; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 1aefff0db0..ae8680f594 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -22,13 +22,7 @@ import { build_set_style } from './shared/element.js'; import { process_children } from './shared/fragment.js'; -import { - build_render_statement, - build_template_chunk, - build_update_assignment, - get_expression_id, - memoize_expression -} from './shared/utils.js'; +import { build_render_statement, build_template_chunk, get_expression_id } from './shared/utils.js'; import { visit_event_attribute } from './shared/events.js'; /** @@ -200,16 +194,16 @@ export function RegularElement(node, context) { const node_id = context.state.node; + /** If true, needs `__value` for inputs */ + const needs_special_value_handling = + node.name === 'option' || + node.name === 'select' || + bindings.has('group') || + bindings.has('checked'); + if (has_spread) { build_attribute_effect(attributes, class_directives, style_directives, context, node, node_id); } else { - /** If true, needs `__value` for inputs */ - const needs_special_value_handling = - node.name === 'option' || - node.name === 'select' || - bindings.has('group') || - bindings.has('checked'); - for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) { if (is_event_attribute(attribute)) { visit_event_attribute(attribute, context); @@ -217,7 +211,6 @@ export function RegularElement(node, context) { } if (needs_special_value_handling && attribute.name === 'value') { - build_element_special_value_attribute(node.name, node_id, attribute, context); continue; } @@ -392,6 +385,15 @@ export function RegularElement(node, context) { context.state.update.push(b.stmt(b.assignment('=', dir, dir))); } + if (!has_spread && needs_special_value_handling) { + for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) { + if (attribute.name === 'value') { + build_element_special_value_attribute(node.name, node_id, attribute, context); + break; + } + } + } + context.state.template.pop_element(); } @@ -622,12 +624,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont element === 'select' && attribute.value !== true && !is_text_attribute(attribute); const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => - metadata.has_call - ? // if is a select with value we will also invoke `init_select` which need a reference before the template effect so we memoize separately - is_select_with_value - ? memoize_expression(state, value) - : get_expression_id(state.expressions, value) - : value + metadata.has_call ? get_expression_id(state.expressions, value) : value ); const evaluated = context.state.scope.evaluate(value); @@ -652,23 +649,21 @@ function build_element_special_value_attribute(element, node_id, attribute, cont : inner_assignment ); - if (is_select_with_value) { - state.init.push(b.stmt(b.call('$.init_select', node_id, b.thunk(value)))); - } - if (has_state) { - const id = state.scope.generate(`${node_id.name}_value`); - build_update_assignment( - state, - id, - // `