diff --git a/.changeset/angry-books-jam.md b/.changeset/angry-books-jam.md new file mode 100644 index 0000000000..6552a54e31 --- /dev/null +++ b/.changeset/angry-books-jam.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: improve nested effect heuristics diff --git a/.changeset/breezy-carrots-flash.md b/.changeset/breezy-carrots-flash.md new file mode 100644 index 0000000000..8d9fce63f9 --- /dev/null +++ b/.changeset/breezy-carrots-flash.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve ssr template code generation diff --git a/.changeset/bright-snakes-sing.md b/.changeset/bright-snakes-sing.md new file mode 100644 index 0000000000..23989cab63 --- /dev/null +++ b/.changeset/bright-snakes-sing.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: make `@types/estree` a dependency diff --git a/.changeset/eighty-days-cheat.md b/.changeset/eighty-days-cheat.md new file mode 100644 index 0000000000..1dab9a948f --- /dev/null +++ b/.changeset/eighty-days-cheat.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve template literal expression output generation diff --git a/.changeset/empty-bulldogs-exercise.md b/.changeset/empty-bulldogs-exercise.md new file mode 100644 index 0000000000..087a8f7775 --- /dev/null +++ b/.changeset/empty-bulldogs-exercise.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve outro behavior with transitions diff --git a/.changeset/kind-baboons-approve.md b/.changeset/kind-baboons-approve.md new file mode 100644 index 0000000000..720a71cffd --- /dev/null +++ b/.changeset/kind-baboons-approve.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve intro transitions on dynamic mount diff --git a/.changeset/lucky-toes-begin.md b/.changeset/lucky-toes-begin.md new file mode 100644 index 0000000000..07d78c0d14 --- /dev/null +++ b/.changeset/lucky-toes-begin.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: remove internal functions from `svelte/transition` exports diff --git a/.changeset/odd-buckets-lie.md b/.changeset/odd-buckets-lie.md new file mode 100644 index 0000000000..240cf02d9a --- /dev/null +++ b/.changeset/odd-buckets-lie.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve code generation diff --git a/.changeset/pre.json b/.changeset/pre.json index ea45a1e6e1..4443e2cd6f 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -10,10 +10,13 @@ }, "changesets": [ "afraid-moose-matter", + "angry-books-jam", "beige-flies-wash", "beige-rabbits-shave", "brave-walls-destroy", + "breezy-carrots-flash", "bright-peas-juggle", + "bright-snakes-sing", "brown-spoons-boil", "chatty-cups-drop", "chatty-taxis-juggle", @@ -38,6 +41,8 @@ "early-ads-tie", "eight-steaks-shout", "eighty-bikes-camp", + "eighty-days-cheat", + "empty-bulldogs-exercise", "empty-crabs-think", "fair-crabs-check", "famous-knives-sneeze", @@ -73,6 +78,7 @@ "itchy-terms-guess", "khaki-mails-draw", "khaki-moose-arrive", + "kind-baboons-approve", "kind-deers-lay", "kind-eagles-join", "large-clouds-carry", @@ -91,6 +97,7 @@ "lovely-items-turn", "lovely-rules-eat", "lucky-schools-hang", + "lucky-toes-begin", "moody-frogs-exist", "moody-owls-cry", "nasty-lions-double", @@ -98,6 +105,7 @@ "neat-dingos-clap", "new-boats-wait", "ninety-dingos-walk", + "odd-buckets-lie", "odd-needles-joke", "odd-schools-wait", "odd-shoes-cheat", @@ -120,6 +128,7 @@ "rare-pears-whisper", "real-guests-do", "red-doors-own", + "rich-cobras-exist", "rich-sheep-burn", "rich-tables-sing", "rotten-bags-type", @@ -128,6 +137,7 @@ "serious-socks-cover", "serious-zebras-scream", "seven-deers-jam", + "seven-jobs-sniff", "seven-ravens-check", "sharp-gorillas-impress", "sharp-kids-happen", @@ -137,16 +147,21 @@ "short-buses-camp", "slimy-clouds-talk", "slimy-walls-draw", + "slow-beds-shave", "slow-chefs-dream", + "slow-wombats-reply", "small-papayas-laugh", "smart-parents-swim", "smart-zebras-pay", "soft-clocks-remember", "soft-geese-learn", + "soft-tigers-wink", "sour-forks-stare", "sour-rules-march", + "sour-weeks-fix", "spicy-plums-admire", "spotty-pens-agree", + "spotty-spiders-compare", "stale-books-perform", "stale-comics-look", "strong-gifts-smoke", @@ -160,9 +175,11 @@ "tall-garlics-try", "tall-shrimps-worry", "tall-tigers-wait", + "tame-spies-drum", "tasty-numbers-perform", "ten-foxes-repeat", "ten-peaches-sleep", + "ten-ties-repair", "ten-worms-reflect", "thin-foxes-lick", "thirty-flowers-sit", diff --git a/.changeset/rich-cobras-exist.md b/.changeset/rich-cobras-exist.md new file mode 100644 index 0000000000..ca560d0e63 --- /dev/null +++ b/.changeset/rich-cobras-exist.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: simplify event delegation logic, only delegate event attributes diff --git a/.changeset/seven-jobs-sniff.md b/.changeset/seven-jobs-sniff.md new file mode 100644 index 0000000000..4faeced47b --- /dev/null +++ b/.changeset/seven-jobs-sniff.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: adjust `$inspect.with` type diff --git a/.changeset/slow-beds-shave.md b/.changeset/slow-beds-shave.md new file mode 100644 index 0000000000..793c66b6a0 --- /dev/null +++ b/.changeset/slow-beds-shave.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: further animation transition improvements diff --git a/.changeset/slow-wombats-reply.md b/.changeset/slow-wombats-reply.md new file mode 100644 index 0000000000..78b6b94861 --- /dev/null +++ b/.changeset/slow-wombats-reply.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve how transitions are handled on mount diff --git a/.changeset/soft-tigers-wink.md b/.changeset/soft-tigers-wink.md new file mode 100644 index 0000000000..561fbb69a6 --- /dev/null +++ b/.changeset/soft-tigers-wink.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve animation transition heuristics diff --git a/.changeset/sour-weeks-fix.md b/.changeset/sour-weeks-fix.md new file mode 100644 index 0000000000..5d639f3a60 --- /dev/null +++ b/.changeset/sour-weeks-fix.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: adjust `parse` return type diff --git a/.changeset/spotty-spiders-compare.md b/.changeset/spotty-spiders-compare.md new file mode 100644 index 0000000000..36b7d98f6a --- /dev/null +++ b/.changeset/spotty-spiders-compare.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent transition action overfiring diff --git a/.changeset/tame-spies-drum.md b/.changeset/tame-spies-drum.md new file mode 100644 index 0000000000..f22ed7ab62 --- /dev/null +++ b/.changeset/tame-spies-drum.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve event handling compatibility with delegation diff --git a/.changeset/ten-ties-repair.md b/.changeset/ten-ties-repair.md new file mode 100644 index 0000000000..4689ee2e07 --- /dev/null +++ b/.changeset/ten-ties-repair.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: ensure topological order for render effects diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 08aa9adcb9..d79e8b2e21 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -22,13 +22,6 @@ body: placeholder: I would like to see... validations: required: true - - type: textarea - id: alternatives - attributes: - label: Alternatives considered - description: "Please provide a clear and concise description of any alternative solutions or features you've considered." - validations: - required: true - type: dropdown id: importance attributes: diff --git a/FUNDING.json b/FUNDING.json new file mode 100644 index 0000000000..2e8beaf8cd --- /dev/null +++ b/FUNDING.json @@ -0,0 +1,7 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0x99D414693dD65E4a0664a16D155dB66283A162D1" + } + } +} diff --git a/documentation/examples/20-7guis/05-7guis-crud/App.svelte b/documentation/examples/20-7guis/05-7guis-crud/App.svelte index 15a2ee6cd0..fbae236b9f 100644 --- a/documentation/examples/20-7guis/05-7guis-crud/App.svelte +++ b/documentation/examples/20-7guis/05-7guis-crud/App.svelte @@ -16,7 +16,7 @@ ? people.filter((person) => { const name = `${person.last}, ${person.first}`; return name.toLowerCase().startsWith(prefix.toLowerCase()); - }) + }) : people; $: selected = filteredPeople[i]; diff --git a/package.json b/package.json index 9a15be62e6..0eb0bf4842 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "build:sites": "pnpm -r --filter=./sites/* build", "preview-site": "npm run build --prefix sites/svelte-5-preview", "check": "cd packages/svelte && pnpm build && cd ../../ && pnpm -r check", - "format": "prettier --write --plugin prettier-plugin-svelte .", - "lint": "prettier --check --plugin prettier-plugin-svelte . && eslint ./", + "format": "prettier --write .", + "lint": "prettier --check . && eslint ./", "test": "vitest run --coverage", "test-output": "vitest run --reporter=json --outputFile=sites/svelte-5-preview/src/routes/status/results.json", "changeset:version": "changeset version && pnpm -r generate:version && git add --all", @@ -38,9 +38,14 @@ "eslint-plugin-lube": "^0.1.7", "jsdom": "22.0.0", "playwright": "^1.35.1", - "prettier": "^3.0.1", - "prettier-plugin-svelte": "^3.0.3", + "prettier": "^3.1.1", + "prettier-plugin-svelte": "^3.1.2", "typescript": "^5.2.2", "vitest": "^0.34.6" + }, + "pnpm": { + "overrides": { + "jimp>xml2js": "^0.6.0" + } } } diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 82098fc762..077fb93953 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,55 @@ # svelte +## 5.0.0-next.35 + +### Patch Changes + +- fix: improve nested effect heuristics ([#10171](https://github.com/sveltejs/svelte/pull/10171)) + +- fix: simplify event delegation logic, only delegate event attributes ([#10169](https://github.com/sveltejs/svelte/pull/10169)) + +- fix: prevent transition action overfiring ([#10163](https://github.com/sveltejs/svelte/pull/10163)) + +- fix: improve event handling compatibility with delegation ([#10168](https://github.com/sveltejs/svelte/pull/10168)) + +- fix: ensure topological order for render effects ([#10175](https://github.com/sveltejs/svelte/pull/10175)) + +## 5.0.0-next.34 + +### Patch Changes + +- fix: make `@types/estree` a dependency ([#10150](https://github.com/sveltejs/svelte/pull/10150)) + +- fix: improve intro transitions on dynamic mount ([#10162](https://github.com/sveltejs/svelte/pull/10162)) + +- fix: improve code generation ([#10156](https://github.com/sveltejs/svelte/pull/10156)) + +- fix: adjust `$inspect.with` type ([`c7cb90c91`](https://github.com/sveltejs/svelte/commit/c7cb90c91cd3553ad59126267c9bfddecbb290b4)) + +- fix: improve how transitions are handled on mount ([#10157](https://github.com/sveltejs/svelte/pull/10157)) + +- fix: adjust `parse` return type ([`a271878ab`](https://github.com/sveltejs/svelte/commit/a271878abe7018923839401129b18082eb2c811a)) + +## 5.0.0-next.33 + +### Patch Changes + +- fix: improve ssr template code generation ([#10151](https://github.com/sveltejs/svelte/pull/10151)) + +- fix: improve template literal expression output generation ([#10147](https://github.com/sveltejs/svelte/pull/10147)) + +## 5.0.0-next.32 + +### Patch Changes + +- fix: improve outro behavior with transitions ([#10139](https://github.com/sveltejs/svelte/pull/10139)) + +- chore: remove internal functions from `svelte/transition` exports ([#10132](https://github.com/sveltejs/svelte/pull/10132)) + +- fix: further animation transition improvements ([#10138](https://github.com/sveltejs/svelte/pull/10138)) + +- fix: improve animation transition heuristics ([#10119](https://github.com/sveltejs/svelte/pull/10119)) + ## 5.0.0-next.31 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 2c7d12577f..98300d87ed 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.0.0-next.31", + "version": "5.0.0-next.35", "type": "module", "types": "./types/index.d.ts", "engines": { @@ -107,7 +107,6 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-virtual": "^3.0.2", "@types/aria-query": "^5.0.3", - "@types/estree": "^1.0.5", "dts-buddy": "^0.4.3", "esbuild": "^0.19.2", "rollup": "^4.1.5", @@ -117,6 +116,7 @@ "dependencies": { "@ampproject/remapping": "^2.2.1", "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.5", "acorn": "^8.10.0", "acorn-typescript": "^1.4.13", "aria-query": "^5.3.0", diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index c083604b87..25a21d8402 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -234,8 +234,8 @@ const attributes = { type === 'no-each' ? `An element that uses the animate directive must be the immediate child of a keyed each block` : type === 'each-key' - ? `An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?` - : `An element that uses the animate directive must be the sole child of a keyed each block`, + ? `An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?` + : `An element that uses the animate directive must be the sole child of a keyed each block`, 'duplicate-animation': () => `An element can only have one 'animate' directive`, /** @param {string[] | undefined} [modifiers] */ 'invalid-event-modifier': (modifiers) => @@ -262,7 +262,7 @@ const attributes = { ? `An element can only have one '${directive1}' directive` : `An element cannot have both ${describe(directive1)} directive and ${describe( directive2 - )} directive`; + )} directive`; }, 'invalid-let-directive-placement': () => 'let directive at invalid position' }; diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index df7034a75a..3e83a17798 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -91,7 +91,7 @@ function handle_compile_error(error, filename, source) { * https://svelte.dev/docs/svelte-compiler#svelte-parse * @param {string} source * @param {{ filename?: string; modern?: boolean }} [options] - * @returns {import('#compiler').SvelteNode | import('./types/legacy-nodes.js').LegacySvelteNode} + * @returns {import('#compiler').Root | import('./types/legacy-nodes.js').LegacyRoot} */ export function parse(source, options = {}) { /** @type {import('#compiler').Root} */ @@ -108,7 +108,7 @@ export function parse(source, options = {}) { if (options.modern) { // remove things that we don't want to treat as public API - return walk(/** @type {import('#compiler').SvelteNode} */ (ast), null, { + return walk(ast, null, { _(node, { next }) { // @ts-ignore delete node.parent; diff --git a/packages/svelte/src/compiler/legacy.js b/packages/svelte/src/compiler/legacy.js index da20b90696..e8595fd52f 100644 --- a/packages/svelte/src/compiler/legacy.js +++ b/packages/svelte/src/compiler/legacy.js @@ -33,7 +33,7 @@ function remove_surrounding_whitespace_nodes(nodes) { * Transform our nice modern AST into the monstrosity emitted by Svelte 4 * @param {string} source * @param {import('#compiler').Root} ast - * @returns {import('./types/legacy-nodes.js').LegacySvelteNode} + * @returns {import('./types/legacy-nodes.js').LegacyRoot} */ export function convert(source, ast) { const root = @@ -41,7 +41,7 @@ export function convert(source, ast) { ast ); - return /** @type {import('./types/legacy-nodes.js').LegacySvelteNode} */ ( + return /** @type {import('./types/legacy-nodes.js').LegacyRoot} */ ( walk(root, null, { _(node, { next }) { // @ts-ignore @@ -108,7 +108,7 @@ export function convert(source, ast) { // @ts-ignore delete node.parent; } - }) + }) : undefined }; }, diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index 6b38da217e..5f2eda1f17 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -108,12 +108,12 @@ export default function tag(parser) { const type = meta_tags.has(name) ? meta_tags.get(name) : regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component' - ? 'Component' - : name === 'title' && parent_is_head(parser.stack) - ? 'TitleElement' - : name === 'slot' - ? 'SlotElement' - : 'RegularElement'; + ? 'Component' + : name === 'title' && parent_is_head(parser.stack) + ? 'TitleElement' + : name === 'slot' + ? 'SlotElement' + : 'RegularElement'; /** @type {import('#compiler').ElementLike} */ // @ts-expect-error TODO can't figure out this error @@ -128,11 +128,10 @@ export default function tag(parser) { fragment: create_fragment(true), metadata: { svg: false, - has_spread: false, - can_delegate_events: null + has_spread: false }, parent: null - } + } : { type: /** @type {import('#compiler').ElementLike['type']} */ (type), start, @@ -141,7 +140,7 @@ export default function tag(parser) { attributes: [], fragment: create_fragment(true), parent: null - }; + }; parser.allow_whitespace(); diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js b/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js index 01be1b492f..95f136effa 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js @@ -275,7 +275,7 @@ export function combine_sourcemaps(filename, sourcemap_list) { sourcemap_list, () => null, true // skip optional field `sourcesContent` - ) + ) : remapping( // use loader interface sourcemap_list[0], // last map @@ -291,7 +291,7 @@ export function combine_sourcemaps(filename, sourcemap_list) { } ), true - ); + ); if (!map.file) delete map.file; // skip optional field `file` diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 3a4e323a77..0c207f9508 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -59,27 +59,23 @@ function get_component_name(filename) { } /** - * @param {Pick & { type: string }} node + * Checks if given event attribute can be delegated/hoisted and returns the corresponding info if so + * @param {string} event_name + * @param {import('estree').Expression | null} handler * @param {import('./types').Context} context * @returns {null | import('#compiler').DelegatedEvent} */ -function get_delegated_event(node, context) { - const handler = node.expression; - const event_name = node.name; - +function get_delegated_event(event_name, handler, context) { // Handle delegated event handlers. Bail-out if not a delegated event. - if (!handler || node.modifiers.includes('capture') || !DelegatedEvents.includes(event_name)) { + if (!handler || !DelegatedEvents.includes(event_name)) { return null; } + // If we are not working with a RegularElement, then bail-out. const element = context.path.at(-1); if (element?.type !== 'RegularElement') { return null; } - // If element says we can't delegate because we have multiple OnDirectives of the same type, bail-out. - if (!element.metadata.can_delegate_events) { - return null; - } /** @type {import('#compiler').DelegatedEvent} */ const non_hoistable = { type: 'non-hoistable' }; @@ -87,7 +83,7 @@ function get_delegated_event(node, context) { let target_function = null; let binding = null; - if (node.type === 'Attribute' && element.metadata.has_spread) { + if (element.metadata.has_spread) { // event attribute becomes part of the dynamic spread array return non_hoistable; } @@ -123,8 +119,7 @@ function get_delegated_event(node, context) { if (element && event_name) { if ( element.type !== 'RegularElement' || - !determine_element_spread_and_delegatable(element).metadata.can_delegate_events || - (element.metadata.has_spread && node.type === 'Attribute') || + determine_element_spread(element).metadata.has_spread || !DelegatedEvents.includes(event_name) ) { return non_hoistable; @@ -183,7 +178,8 @@ function get_delegated_event(node, context) { ) { return non_hoistable; } - // If we referebnce the index within an each block, then bail-out. + + // If we reference the index within an each block, then bail-out. if (binding !== null && binding.initial?.type === 'EachBlock') { return non_hoistable; } @@ -204,6 +200,7 @@ function get_delegated_event(node, context) { } visited_references.add(reference); } + return { type: 'hoistable', function: target_function }; } @@ -713,12 +710,12 @@ const runes_scope_tweaker = { rune === '$state' ? 'state' : rune === '$state.frozen' - ? 'frozen_state' - : rune === '$derived' - ? 'derived' - : path.is_rest - ? 'rest_prop' - : 'prop'; + ? 'frozen_state' + : rune === '$derived' + ? 'derived' + : path.is_rest + ? 'rest_prop' + : 'prop'; } if (rune === '$props') { @@ -858,21 +855,9 @@ const common_visitors = { }); if (is_event_attribute(node)) { - /** @type {string[]} */ - const modifiers = []; const expression = node.value[0].expression; - let name = node.name.slice(2); - - if (is_capture_event(name)) { - name = name.slice(0, -7); - modifiers.push('capture'); - } - - const delegated_event = get_delegated_event( - { type: node.type, name, expression, modifiers }, - context - ); + const delegated_event = get_delegated_event(node.name.slice(2), expression, context); if (delegated_event !== null) { if (delegated_event.type === 'hoistable') { @@ -1032,18 +1017,6 @@ const common_visitors = { ) }; }, - OnDirective(node, context) { - node.metadata = { delegated: null }; - context.next(); - const delegated_event = get_delegated_event(node, context); - - if (delegated_event !== null) { - if (delegated_event.type === 'hoistable') { - delegated_event.function.metadata.hoistable = true; - } - node.metadata.delegated = delegated_event; - } - }, ArrowFunctionExpression: function_visitor, FunctionExpression: function_visitor, FunctionDeclaration: function_visitor, @@ -1052,7 +1025,7 @@ const common_visitors = { node.metadata.svg = true; } - determine_element_spread_and_delegatable(node); + determine_element_spread(node); // Special case: Move the children of