Merge branch 'main' into revert-10430-export-validation

pull/10523/head
Rich Harris 3 years ago
commit 96ddefe9bc

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: improve code generation for `bind:this` in SSR mode

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: improve effect over-fire on store subscription init

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: use init properties when exporting non-state values in prod

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: improve each block item equality for immutable mode

@ -21,6 +21,7 @@
"big-eyes-carry", "big-eyes-carry",
"big-geese-act", "big-geese-act",
"blue-timers-film", "blue-timers-film",
"brave-points-sleep",
"brave-shrimps-kiss", "brave-shrimps-kiss",
"brave-walls-destroy", "brave-walls-destroy",
"breezy-carrots-flash", "breezy-carrots-flash",
@ -39,6 +40,7 @@
"cold-birds-own", "cold-birds-own",
"cool-ants-leave", "cool-ants-leave",
"cool-rabbits-tickle", "cool-rabbits-tickle",
"cool-roses-trade",
"cuddly-pianos-drop", "cuddly-pianos-drop",
"curly-lizards-dream", "curly-lizards-dream",
"curvy-cups-cough", "curvy-cups-cough",
@ -58,6 +60,7 @@
"eight-steaks-shout", "eight-steaks-shout",
"eighty-bikes-camp", "eighty-bikes-camp",
"eighty-days-cheat", "eighty-days-cheat",
"empty-bags-heal",
"empty-bulldogs-exercise", "empty-bulldogs-exercise",
"empty-crabs-think", "empty-crabs-think",
"empty-tools-share", "empty-tools-share",
@ -171,6 +174,7 @@
"popular-mangos-rest", "popular-mangos-rest",
"pretty-ties-help", "pretty-ties-help",
"purple-dragons-peel", "purple-dragons-peel",
"quiet-apricots-dream",
"quiet-camels-mate", "quiet-camels-mate",
"quiet-crabs-nail", "quiet-crabs-nail",
"quiet-timers-speak", "quiet-timers-speak",
@ -273,6 +277,7 @@
"unlucky-boxes-obey", "unlucky-boxes-obey",
"unlucky-trees-lick", "unlucky-trees-lick",
"violet-pigs-jam", "violet-pigs-jam",
"weak-terms-destroy",
"wet-games-fly", "wet-games-fly",
"wicked-clouds-exercise", "wicked-clouds-exercise",
"wicked-doors-train", "wicked-doors-train",

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: visit expression node in directives

@ -1,5 +1,27 @@
# svelte # svelte
## 5.0.0-next.60
### Patch Changes
- fix: improve effect over-fire on store subscription init ([#10535](https://github.com/sveltejs/svelte/pull/10535))
- fix: use init properties when exporting non-state values in prod ([#10521](https://github.com/sveltejs/svelte/pull/10521))
## 5.0.0-next.59
### Patch Changes
- chore: improve code generation for `bind:this` in SSR mode ([#10524](https://github.com/sveltejs/svelte/pull/10524))
- fix: visit expression node in directives ([#10527](https://github.com/sveltejs/svelte/pull/10527))
## 5.0.0-next.58
### Patch Changes
- breaking: remove `createRoot`, adjust `mount`/`hydrate` APIs, introduce `unmount` ([#10516](https://github.com/sveltejs/svelte/pull/10516))
## 5.0.0-next.57 ## 5.0.0-next.57
### Patch Changes ### Patch Changes

@ -2,7 +2,7 @@
"name": "svelte", "name": "svelte",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"license": "MIT", "license": "MIT",
"version": "5.0.0-next.57", "version": "5.0.0-next.60",
"type": "module", "type": "module",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"engines": { "engines": {

@ -1060,7 +1060,9 @@ const common_visitors = {
parent.type === 'SvelteComponent' || parent.type === 'SvelteComponent' ||
parent.type === 'SvelteSelf' parent.type === 'SvelteSelf'
) { ) {
context.state.analysis.uses_component_bindings = true; if (node.name !== 'this') {
context.state.analysis.uses_component_bindings = true;
}
break; break;
} else if (is_element_node(parent)) { } else if (is_element_node(parent)) {
break; break;

@ -242,9 +242,11 @@ export function client_component(source, analysis, options) {
const binding = analysis.instance.scope.get(name); const binding = analysis.instance.scope.get(name);
const is_source = binding !== null && is_state_source(binding, state); const is_source = binding !== null && is_state_source(binding, state);
// TODO This is always a getter because the `renamed-instance-exports` test wants it that way. if (is_source || options.dev) {
// Should we for code size reasons make it an init in runes mode and/or non-dev mode? return b.get(alias ?? name, [b.return(is_source ? b.call('$.get', b.id(name)) : b.id(name))]);
return b.get(alias ?? name, [b.return(is_source ? b.call('$.get', b.id(name)) : b.id(name))]); }
return b.init(alias ?? name, b.id(name));
}); });
if (analysis.accessors) { if (analysis.accessors) {
@ -286,17 +288,12 @@ export function client_component(source, analysis, options) {
) )
: () => {}; : () => {};
if (properties.length > 0) { append_styles();
component_block.body.push( component_block.body.push(
b.var('$$accessors', b.object(properties)), properties.length > 0
b.stmt(b.call('$.pop', b.id('$$accessors'))) ? b.return(b.call('$.pop', b.object(properties)))
); : b.stmt(b.call('$.pop'))
append_styles(); );
component_block.body.push(b.return(b.id('$$accessors')));
} else {
component_block.body.push(b.stmt(b.call('$.pop')));
append_styles();
}
if (analysis.uses_rest_props) { if (analysis.uses_rest_props) {
/** @type {string[]} */ /** @type {string[]} */

@ -28,7 +28,7 @@ import {
DOMBooleanAttributes, DOMBooleanAttributes,
EACH_INDEX_REACTIVE, EACH_INDEX_REACTIVE,
EACH_IS_CONTROLLED, EACH_IS_CONTROLLED,
EACH_IS_IMMUTABLE, EACH_IS_STRICT_EQUALS,
EACH_ITEM_REACTIVE, EACH_ITEM_REACTIVE,
EACH_KEYED EACH_KEYED
} from '../../../../../constants.js'; } from '../../../../../constants.js';
@ -2259,8 +2259,8 @@ export const template_visitors = {
each_type |= EACH_IS_CONTROLLED; each_type |= EACH_IS_CONTROLLED;
} }
if (context.state.analysis.immutable) { if (context.state.analysis.runes) {
each_type |= EACH_IS_IMMUTABLE; each_type |= EACH_IS_STRICT_EQUALS;
} }
// Find the parent each blocks which contain the arrays to invalidate // Find the parent each blocks which contain the arrays to invalidate

@ -853,7 +853,7 @@ function serialize_inline_component(node, component_name, context) {
const value = serialize_attribute_value(attribute.value, context, false, true); const value = serialize_attribute_value(attribute.value, context, false, true);
push_prop(b.prop('init', b.key(attribute.name), value)); push_prop(b.prop('init', b.key(attribute.name), value));
} else if (attribute.type === 'BindDirective') { } else if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
// TODO this needs to turn the whole thing into a while loop because the binding could be mutated eagerly in the child // TODO this needs to turn the whole thing into a while loop because the binding could be mutated eagerly in the child
push_prop( push_prop(
b.get(attribute.name, [ b.get(attribute.name, [

@ -331,10 +331,14 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
} }
/** /**
* @type {import('zimmerframe').Visitor<import('#compiler').Directive, State, import('#compiler').SvelteNode>} * @type {import('zimmerframe').Visitor<import('#compiler').AnimateDirective | import('#compiler').TransitionDirective | import('#compiler').UseDirective, State, import('#compiler').SvelteNode>}
*/ */
const SvelteDirective = (node, context) => { const SvelteDirective = (node, { state, path, visit }) => {
context.state.scope.reference(b.id(node.name), context.path); state.scope.reference(b.id(node.name), path);
if (node.expression) {
visit(node.expression);
}
}; };
walk(ast, state, { walk(ast, state, {

@ -5,7 +5,7 @@ export const EACH_KEYED = 1 << 2;
/** See EachBlock interface metadata.is_controlled for an explanation what this is */ /** See EachBlock interface metadata.is_controlled for an explanation what this is */
export const EACH_IS_CONTROLLED = 1 << 3; export const EACH_IS_CONTROLLED = 1 << 3;
export const EACH_IS_ANIMATED = 1 << 4; export const EACH_IS_ANIMATED = 1 << 4;
export const EACH_IS_IMMUTABLE = 1 << 6; export const EACH_IS_STRICT_EQUALS = 1 << 6;
export const PROPS_IS_IMMUTABLE = 1; export const PROPS_IS_IMMUTABLE = 1;
export const PROPS_IS_RUNES = 1 << 1; export const PROPS_IS_RUNES = 1 << 1;

@ -32,48 +32,6 @@ export function create_root_block(intro) {
}; };
} }
/** @returns {import('./types.js').IfBlock} */
export function create_if_block() {
return {
// alternate transitions
a: null,
// alternate effect
ae: null,
// consequent transitions
c: null,
// consequent effect
ce: null,
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('./types.js').Block} */ (current_block),
// transition
r: null,
// type
t: IF_BLOCK,
// value
v: false
};
}
/** @returns {import('./types.js').KeyBlock} */
export function create_key_block() {
return {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('./types.js').Block} */ (current_block),
// transition
r: null,
// type
t: KEY_BLOCK
};
}
/** @returns {import('./types.js').HeadBlock} */ /** @returns {import('./types.js').HeadBlock} */
export function create_head_block() { export function create_head_block() {
return { return {
@ -122,82 +80,6 @@ export function create_dynamic_component_block() {
}; };
} }
/** @returns {import('./types.js').AwaitBlock} */
export function create_await_block() {
return {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('./types.js').Block} */ (current_block),
// pending
n: true,
// transition
r: null,
// type
t: AWAIT_BLOCK
};
}
/**
* @param {number} flags
* @param {Element | Comment} anchor
* @returns {import('./types.js').EachBlock}
*/
export function create_each_block(flags, anchor) {
return {
// anchor
a: anchor,
// dom
d: null,
// flags
f: flags,
// items
v: [],
// effect
e: null,
p: /** @type {import('./types.js').Block} */ (current_block),
// transition
r: null,
// transitions
s: [],
// type
t: EACH_BLOCK
};
}
/**
* @param {any | import('./types.js').Signal<any>} item
* @param {number | import('./types.js').Signal<number>} index
* @param {null | unknown} key
* @returns {import('./types.js').EachItemBlock}
*/
export function create_each_item_block(item, index, key) {
return {
// animate transition
a: null,
// dom
d: null,
// effect
e: null,
// index
i: index,
// key
k: key,
// item
v: item,
// parent
p: /** @type {import('./types.js').EachBlock} */ (current_block),
// transition
r: null,
// transitions
s: null,
// type
t: EACH_ITEM_BLOCK
};
}
/** @returns {import('./types.js').SnippetBlock} */ /** @returns {import('./types.js').SnippetBlock} */
export function create_snippet_block() { export function create_snippet_block() {
return { return {

@ -276,7 +276,7 @@ function get_custom_elements_slots(element) {
* @param {any} Component A Svelte component function * @param {any} Component A Svelte component function
* @param {Record<string, CustomElementPropDefinition>} props_definition The props to observe * @param {Record<string, CustomElementPropDefinition>} props_definition The props to observe
* @param {string[]} slots The slots to create * @param {string[]} slots The slots to create
* @param {string[]} accessors Other accessors besides the ones for props the component has * @param {string[]} exports Explicitly exported values, other than props
* @param {boolean} use_shadow_dom Whether to use shadow DOM * @param {boolean} use_shadow_dom Whether to use shadow DOM
* @param {(ce: new () => HTMLElement) => new () => HTMLElement} [extend] * @param {(ce: new () => HTMLElement) => new () => HTMLElement} [extend]
*/ */
@ -284,7 +284,7 @@ export function create_custom_element(
Component, Component,
props_definition, props_definition,
slots, slots,
accessors, exports,
use_shadow_dom, use_shadow_dom,
extend extend
) { ) {
@ -311,10 +311,10 @@ export function create_custom_element(
} }
}); });
}); });
accessors.forEach((accessor) => { exports.forEach((property) => {
define_property(Class.prototype, accessor, { define_property(Class.prototype, property, {
get() { get() {
return this.$$c?.[accessor]; return this.$$c?.[property];
} }
}); });
}); });

@ -0,0 +1,201 @@
import { is_promise } from '../../../common.js';
import { AWAIT_BLOCK } from '../../block.js';
import { hydrate_block_anchor } from '../../hydration.js';
import { remove } from '../../reconciler.js';
import {
UNINITIALIZED,
current_block,
destroy_signal,
execute_effect,
flushSync,
push_destroy_fn,
render_effect
} from '../../runtime.js';
import { trigger_transitions } from '../../transitions.js';
/** @returns {import('../../types.js').AwaitBlock} */
export function create_await_block() {
return {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('../../types.js').Block} */ (current_block),
// pending
n: true,
// transition
r: null,
// type
t: AWAIT_BLOCK
};
}
/**
* @template V
* @param {Comment} anchor_node
* @param {(() => Promise<V>)} input
* @param {null | ((anchor: Node) => void)} pending_fn
* @param {null | ((anchor: Node, value: V) => void)} then_fn
* @param {null | ((anchor: Node, error: unknown) => void)} catch_fn
* @returns {void}
*/
export function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) {
const block = create_await_block();
/** @type {null | import('../../types.js').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node);
/** @type {{}} */
let latest_token;
/** @type {typeof UNINITIALIZED | V} */
let resolved_value = UNINITIALIZED;
/** @type {unknown} */
let error = UNINITIALIZED;
let pending = false;
block.r =
/**
* @param {import('../../types.js').Transition} transition
* @returns {void}
*/
(transition) => {
const render = /** @type {import('../../types.js').Render} */ (current_render);
const transitions = render.s;
transitions.add(transition);
transition.f(() => {
transitions.delete(transition);
if (transitions.size === 0) {
// If the current render has changed since, then we can remove the old render
// effect as it's stale.
if (current_render !== render && render.e !== null) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
destroy_signal(render.e);
render.e = null;
}
}
});
};
const create_render_effect = () => {
/** @type {import('../../types.js').Render} */
const render = {
d: null,
e: null,
s: new Set(),
p: current_render
};
const effect = render_effect(
() => {
if (error === UNINITIALIZED) {
if (resolved_value === UNINITIALIZED) {
// pending = true
block.n = true;
if (pending_fn !== null) {
pending_fn(anchor_node);
}
} else if (then_fn !== null) {
// pending = false
block.n = false;
then_fn(anchor_node, resolved_value);
}
} else if (catch_fn !== null) {
// pending = false
block.n = false;
catch_fn(anchor_node, error);
}
render.d = block.d;
block.d = null;
},
block,
true,
true
);
render.e = effect;
current_render = render;
};
const render = () => {
const render = current_render;
if (render === null) {
create_render_effect();
return;
}
const transitions = render.s;
if (transitions.size === 0) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
if (render.e) {
execute_effect(render.e);
} else {
create_render_effect();
}
} else {
create_render_effect();
trigger_transitions(transitions, 'out');
}
};
const await_effect = render_effect(
() => {
const token = {};
latest_token = token;
const promise = input();
if (is_promise(promise)) {
promise.then(
/** @param {V} v */
(v) => {
if (latest_token === token) {
// Ensure UI is in sync before resolving value.
flushSync();
resolved_value = v;
pending = false;
render();
}
},
/** @param {unknown} _error */
(_error) => {
error = _error;
pending = false;
render();
}
);
if (resolved_value !== UNINITIALIZED || error !== UNINITIALIZED) {
error = UNINITIALIZED;
resolved_value = UNINITIALIZED;
}
if (!pending) {
pending = true;
render();
}
} else {
error = UNINITIALIZED;
resolved_value = promise;
pending = false;
render();
}
},
block,
false
);
push_destroy_fn(await_effect, () => {
let render = current_render;
latest_token = {};
while (render !== null) {
const dom = render.d;
if (dom !== null) {
remove(dom);
}
const effect = render.e;
if (effect !== null) {
destroy_signal(effect);
}
render = render.p;
}
});
block.e = await_effect;
}

@ -2,21 +2,23 @@ import {
EACH_INDEX_REACTIVE, EACH_INDEX_REACTIVE,
EACH_IS_ANIMATED, EACH_IS_ANIMATED,
EACH_IS_CONTROLLED, EACH_IS_CONTROLLED,
EACH_IS_IMMUTABLE, EACH_IS_STRICT_EQUALS,
EACH_ITEM_REACTIVE, EACH_ITEM_REACTIVE,
EACH_KEYED EACH_KEYED
} from '../../constants.js'; } from '../../../../constants.js';
import { create_each_block, create_each_item_block } from './block.js'; import { noop } from '../../../common.js';
import { EACH_BLOCK, EACH_ITEM_BLOCK } from '../../block.js';
import { import {
current_hydration_fragment, current_hydration_fragment,
get_hydration_fragment, get_hydration_fragment,
hydrate_block_anchor, hydrate_block_anchor,
hydrating, hydrating,
set_current_hydration_fragment set_current_hydration_fragment
} from './hydration.js'; } from '../../hydration.js';
import { clear_text_content, empty, map_get, map_set } from './operations.js'; import { clear_text_content, empty, map_get, map_set } from '../../operations.js';
import { insert, remove } from './reconciler.js'; import { insert, remove } from '../../reconciler.js';
import { import {
current_block,
destroy_signal, destroy_signal,
execute_effect, execute_effect,
mutable_source, mutable_source,
@ -24,15 +26,71 @@ import {
render_effect, render_effect,
set_signal_value, set_signal_value,
source source
} from './runtime.js'; } from '../../runtime.js';
import { trigger_transitions } from './transitions.js'; import { trigger_transitions } from '../../transitions.js';
import { is_array } from './utils.js'; import { is_array } from '../../utils.js';
const NEW_BLOCK = -1; const NEW_BLOCK = -1;
const MOVED_BLOCK = 99999999; const MOVED_BLOCK = 99999999;
const LIS_BLOCK = -2; const LIS_BLOCK = -2;
function no_op() {} /**
* @param {number} flags
* @param {Element | Comment} anchor
* @returns {import('../../types.js').EachBlock}
*/
export function create_each_block(flags, anchor) {
return {
// anchor
a: anchor,
// dom
d: null,
// flags
f: flags,
// items
v: [],
// effect
e: null,
p: /** @type {import('../../types.js').Block} */ (current_block),
// transition
r: null,
// transitions
s: [],
// type
t: EACH_BLOCK
};
}
/**
* @param {any | import('../../types.js').Signal<any>} item
* @param {number | import('../../types.js').Signal<number>} index
* @param {null | unknown} key
* @returns {import('../../types.js').EachItemBlock}
*/
export function create_each_item_block(item, index, key) {
return {
// animate transition
a: null,
// dom
d: null,
// effect
e: null,
// index
i: index,
// key
k: key,
// item
v: item,
// parent
p: /** @type {import('../../types.js').EachBlock} */ (current_block),
// transition
r: null,
// transitions
s: null,
// type
t: EACH_ITEM_BLOCK
};
}
/** /**
* @template V * @template V
@ -40,7 +98,7 @@ function no_op() {}
* @param {() => V[]} collection * @param {() => V[]} collection
* @param {number} flags * @param {number} flags
* @param {null | ((item: V) => string)} key_fn * @param {null | ((item: V) => string)} key_fn
* @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn * @param {(anchor: null, item: V, index: import('../../types.js').MaybeSignal<number>) => void} render_fn
* @param {null | ((anchor: Node) => void)} fallback_fn * @param {null | ((anchor: Node) => void)} fallback_fn
* @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn * @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn
* @returns {void} * @returns {void}
@ -49,7 +107,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
const block = create_each_block(flags, anchor_node); const block = create_each_block(flags, anchor_node);
/** @type {null | import('./types.js').Render} */ /** @type {null | import('../../types.js').Render} */
let current_fallback = null; let current_fallback = null;
hydrate_block_anchor(anchor_node, is_controlled); hydrate_block_anchor(anchor_node, is_controlled);
@ -59,7 +117,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
/** @type {Array<string> | null} */ /** @type {Array<string> | null} */
let keys = null; let keys = null;
/** @type {null | import('./types.js').EffectSignal} */ /** @type {null | import('../../types.js').EffectSignal} */
let render = null; let render = null;
/** /**
@ -69,9 +127,9 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
let mismatch = false; let mismatch = false;
block.r = block.r =
/** @param {import('./types.js').Transition} transition */ /** @param {import('../../types.js').Transition} transition */
(transition) => { (transition) => {
const fallback = /** @type {import('./types.js').Render} */ (current_fallback); const fallback = /** @type {import('../../types.js').Render} */ (current_fallback);
const transitions = fallback.s; const transitions = fallback.s;
transitions.add(transition); transitions.add(transition);
transition.f(() => { transition.f(() => {
@ -90,7 +148,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
}; };
const create_fallback_effect = () => { const create_fallback_effect = () => {
/** @type {import('./types.js').Render} */ /** @type {import('../../types.js').Render} */
const fallback = { const fallback = {
d: null, d: null,
e: null, e: null,
@ -131,7 +189,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
current_fallback = fallback; current_fallback = fallback;
}; };
/** @param {import('./types.js').EachBlock} block */ /** @param {import('../../types.js').EachBlock} block */
const render_each = (block) => { const render_each = (block) => {
const flags = block.f; const flags = block.f;
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
@ -152,7 +210,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
if (key_fn !== null) { if (key_fn !== null) {
keys = array.map(key_fn); keys = array.map(key_fn);
} else if ((flags & EACH_KEYED) === 0) { } else if ((flags & EACH_KEYED) === 0) {
array.map(no_op); array.map(noop);
} }
const length = array.length; const length = array.length;
@ -168,7 +226,9 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
mismatch = true; mismatch = true;
} else if (is_each_else_comment) { } else if (is_each_else_comment) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm // Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('./types.js').TemplateNode[]} */ (current_hydration_fragment).shift(); /** @type {import('../../types.js').TemplateNode[]} */ (
current_hydration_fragment
).shift();
} }
} }
@ -226,7 +286,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
} }
// Clear the array // Clear the array
reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys); reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys);
destroy_signal(/** @type {import('./types.js').EffectSignal} */ (render)); destroy_signal(/** @type {import('../../types.js').EffectSignal} */ (render));
}); });
block.e = each; block.e = each;
@ -238,7 +298,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
* @param {() => V[]} collection * @param {() => V[]} collection
* @param {number} flags * @param {number} flags
* @param {null | ((item: V) => string)} key_fn * @param {null | ((item: V) => string)} key_fn
* @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn * @param {(anchor: null, item: V, index: import('../../types.js').MaybeSignal<number>) => void} render_fn
* @param {null | ((anchor: Node) => void)} fallback_fn * @param {null | ((anchor: Node) => void)} fallback_fn
* @returns {void} * @returns {void}
*/ */
@ -251,7 +311,7 @@ export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fa
* @param {Element | Comment} anchor_node * @param {Element | Comment} anchor_node
* @param {() => V[]} collection * @param {() => V[]} collection
* @param {number} flags * @param {number} flags
* @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn * @param {(anchor: null, item: V, index: import('../../types.js').MaybeSignal<number>) => void} render_fn
* @param {null | ((anchor: Node) => void)} fallback_fn * @param {null | ((anchor: Node) => void)} fallback_fn
* @returns {void} * @returns {void}
*/ */
@ -262,10 +322,10 @@ export function each_indexed(anchor_node, collection, flags, render_fn, fallback
/** /**
* @template V * @template V
* @param {Array<V>} array * @param {Array<V>} array
* @param {import('./types.js').EachBlock} each_block * @param {import('../../types.js').EachBlock} each_block
* @param {Element | Comment | Text} dom * @param {Element | Comment | Text} dom
* @param {boolean} is_controlled * @param {boolean} is_controlled
* @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn * @param {(anchor: null, item: V, index: number | import('../../types.js').Signal<number>) => void} render_fn
* @param {number} flags * @param {number} flags
* @param {boolean} apply_transitions * @param {boolean} apply_transitions
* @returns {void} * @returns {void}
@ -290,7 +350,7 @@ function reconcile_indexed_array(
var length = Math.max(a, b); var length = Math.max(a, b);
var index = 0; var index = 0;
/** @type {Array<import('./types.js').EachItemBlock>} */ /** @type {Array<import('../../types.js').EachItemBlock>} */
var b_blocks; var b_blocks;
var block; var block;
@ -315,7 +375,7 @@ function reconcile_indexed_array(
b_blocks = Array(b); b_blocks = Array(b);
if (hydrating) { if (hydrating) {
// Hydrate block // Hydrate block
var hydration_list = /** @type {import('./types.js').TemplateNode[]} */ ( var hydration_list = /** @type {import('../../types.js').TemplateNode[]} */ (
current_hydration_fragment current_hydration_fragment
); );
var hydrating_node = hydration_list[0]; var hydrating_node = hydration_list[0];
@ -333,7 +393,7 @@ function reconcile_indexed_array(
block = each_item_block(item, null, index, render_fn, flags); block = each_item_block(item, null, index, render_fn, flags);
b_blocks[index] = block; b_blocks[index] = block;
hydrating_node = /** @type {import('./types.js').TemplateNode} */ ( hydrating_node = /** @type {import('../../types.js').TemplateNode} */ (
/** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling /** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling
); );
} }
@ -376,10 +436,10 @@ function reconcile_indexed_array(
* https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968 * https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968
* @template V * @template V
* @param {Array<V>} array * @param {Array<V>} array
* @param {import('./types.js').EachBlock} each_block * @param {import('../../types.js').EachBlock} each_block
* @param {Element | Comment | Text} dom * @param {Element | Comment | Text} dom
* @param {boolean} is_controlled * @param {boolean} is_controlled
* @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn * @param {(anchor: null, item: V, index: number | import('../../types.js').Signal<number>) => void} render_fn
* @param {number} flags * @param {number} flags
* @param {boolean} apply_transitions * @param {boolean} apply_transitions
* @param {Array<string> | null} keys * @param {Array<string> | null} keys
@ -405,7 +465,7 @@ function reconcile_tracked_array(
/** @type {number} */ /** @type {number} */
var b = array.length; var b = array.length;
/** @type {Array<import('./types.js').EachItemBlock>} */ /** @type {Array<import('../../types.js').EachItemBlock>} */
var b_blocks; var b_blocks;
var block; var block;
@ -435,7 +495,7 @@ function reconcile_tracked_array(
if (hydrating) { if (hydrating) {
// Hydrate block // Hydrate block
var fragment; var fragment;
var hydration_list = /** @type {import('./types.js').TemplateNode[]} */ ( var hydration_list = /** @type {import('../../types.js').TemplateNode[]} */ (
current_hydration_fragment current_hydration_fragment
); );
var hydrating_node = hydration_list[0]; var hydrating_node = hydration_list[0];
@ -457,7 +517,7 @@ function reconcile_tracked_array(
// Get the <!--ssr:..--> tag of the next item in the list // Get the <!--ssr:..--> tag of the next item in the list
// The fragment array can be empty if each block has no content // The fragment array can be empty if each block has no content
hydrating_node = /** @type {import('./types.js').TemplateNode} */ ( hydrating_node = /** @type {import('../../types.js').TemplateNode} */ (
/** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling /** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling
); );
} }
@ -610,8 +670,8 @@ function reconcile_tracked_array(
/** /**
* The server could have rendered more list items than the client specifies. * The server could have rendered more list items than the client specifies.
* In that case, we need to remove the remaining server-rendered nodes. * In that case, we need to remove the remaining server-rendered nodes.
* @param {import('./types.js').TemplateNode[]} hydration_list * @param {import('../../types.js').TemplateNode[]} hydration_list
* @param {import('./types.js').TemplateNode | null} next_node * @param {import('../../types.js').TemplateNode | null} next_node
*/ */
function remove_excess_hydration_nodes(hydration_list, next_node) { function remove_excess_hydration_nodes(hydration_list, next_node) {
if (next_node === null) return; if (next_node === null) return;
@ -695,14 +755,14 @@ function mark_lis(a) {
} }
/** /**
* @param {import('./types.js').Block} block * @param {import('../../types.js').Block} block
* @param {Element | Comment | Text} dom * @param {Element | Comment | Text} dom
* @param {boolean} is_controlled * @param {boolean} is_controlled
* @param {null | Text | Element | Comment} sibling * @param {null | Text | Element | Comment} sibling
* @returns {Text | Element | Comment} * @returns {Text | Element | Comment}
*/ */
function insert_each_item_block(block, dom, is_controlled, sibling) { function insert_each_item_block(block, dom, is_controlled, sibling) {
var current = /** @type {import('./types.js').TemplateNode} */ (block.d); var current = /** @type {import('../../types.js').TemplateNode} */ (block.d);
if (sibling === null) { if (sibling === null) {
if (is_controlled) { if (is_controlled) {
@ -716,7 +776,7 @@ function insert_each_item_block(block, dom, is_controlled, sibling) {
} }
/** /**
* @param {import('./types.js').Block} block * @param {import('../../types.js').Block} block
* @returns {Text | Element | Comment} * @returns {Text | Element | Comment}
*/ */
function get_first_child(block) { function get_first_child(block) {
@ -730,7 +790,7 @@ function get_first_child(block) {
} }
/** /**
* @param {Array<import('./types.js').EachItemBlock>} active_transitions * @param {Array<import('../../types.js').EachItemBlock>} active_transitions
* @returns {void} * @returns {void}
*/ */
function destroy_active_transition_blocks(active_transitions) { function destroy_active_transition_blocks(active_transitions) {
@ -755,7 +815,7 @@ function destroy_active_transition_blocks(active_transitions) {
} }
/** /**
* @param {import('./types.js').Block} block * @param {import('../../types.js').Block} block
* @returns {Text | Element | Comment} * @returns {Text | Element | Comment}
*/ */
export function get_first_element(block) { export function get_first_element(block) {
@ -774,7 +834,7 @@ export function get_first_element(block) {
} }
/** /**
* @param {import('./types.js').EachItemBlock} block * @param {import('../../types.js').EachItemBlock} block
* @param {any} item * @param {any} item
* @param {number} index * @param {number} index
* @param {number} type * @param {number} type
@ -793,15 +853,15 @@ function update_each_item_block(block, item, index, type) {
each_animation(block, transitions); each_animation(block, transitions);
} }
if (index_is_reactive) { if (index_is_reactive) {
set_signal_value(/** @type {import('./types.js').Signal<number>} */ (block.i), index); set_signal_value(/** @type {import('../../types.js').Signal<number>} */ (block.i), index);
} else { } else {
block.i = index; block.i = index;
} }
} }
/** /**
* @param {import('./types.js').EachItemBlock} block * @param {import('../../types.js').EachItemBlock} block
* @param {null | Array<import('./types.js').Block>} transition_block * @param {null | Array<import('../../types.js').Block>} transition_block
* @param {boolean} apply_transitions * @param {boolean} apply_transitions
* @param {any} controlled * @param {any} controlled
* @returns {void} * @returns {void}
@ -835,7 +895,7 @@ export function destroy_each_item_block(
if (!controlled && dom !== null) { if (!controlled && dom !== null) {
remove(dom); remove(dom);
} }
destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e)); destroy_signal(/** @type {import('../../types.js').EffectSignal} */ (block.e));
} }
/** /**
@ -843,24 +903,24 @@ export function destroy_each_item_block(
* @param {V} item * @param {V} item
* @param {unknown} key * @param {unknown} key
* @param {number} index * @param {number} index
* @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn * @param {(anchor: null, item: V, index: number | import('../../types.js').Signal<number>) => void} render_fn
* @param {number} flags * @param {number} flags
* @returns {import('./types.js').EachItemBlock} * @returns {import('../../types.js').EachItemBlock}
*/ */
function each_item_block(item, key, index, render_fn, flags) { function each_item_block(item, key, index, render_fn, flags) {
const each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0; const each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0;
const item_value = each_item_not_reactive const item_value = each_item_not_reactive
? item ? item
: (flags & EACH_IS_IMMUTABLE) === 0 : (flags & EACH_IS_STRICT_EQUALS) !== 0
? mutable_source(item) ? source(item)
: source(item); : mutable_source(item);
const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index); const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index);
const block = create_each_item_block(item_value, index_value, key); const block = create_each_item_block(item_value, index_value, key);
const effect = render_effect( const effect = render_effect(
/** @param {import('./types.js').EachItemBlock} block */ /** @param {import('../../types.js').EachItemBlock} block */
(block) => { (block) => {
render_fn(null, block.v, block.i); render_fn(null, block.v, block.i);
}, },

@ -0,0 +1,186 @@
import { IF_BLOCK } from '../../block.js';
import {
current_hydration_fragment,
hydrate_block_anchor,
hydrating,
set_current_hydration_fragment
} from '../../hydration.js';
import { remove } from '../../reconciler.js';
import {
current_block,
destroy_signal,
execute_effect,
push_destroy_fn,
render_effect
} from '../../runtime.js';
import { trigger_transitions } from '../../transitions.js';
/** @returns {import('../../types.js').IfBlock} */
function create_if_block() {
return {
// alternate transitions
a: null,
// alternate effect
ae: null,
// consequent transitions
c: null,
// consequent effect
ce: null,
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('../../types.js').Block} */ (current_block),
// transition
r: null,
// type
t: IF_BLOCK,
// value
v: false
};
}
/**
* @param {Comment} anchor_node
* @param {() => boolean} condition_fn
* @param {(anchor: Node) => void} consequent_fn
* @param {null | ((anchor: Node) => void)} alternate_fn
* @returns {void}
*/
export function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
const block = create_if_block();
hydrate_block_anchor(anchor_node);
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
/** @type {null | import('../../types.js').TemplateNode | Array<import('../../types.js').TemplateNode>} */
let consequent_dom = null;
/** @type {null | import('../../types.js').TemplateNode | Array<import('../../types.js').TemplateNode>} */
let alternate_dom = null;
let has_mounted = false;
/**
* @type {import('../../types.js').EffectSignal | null}
*/
let current_branch_effect = null;
const if_effect = render_effect(
() => {
const result = !!condition_fn();
if (block.v !== result || !has_mounted) {
block.v = result;
if (has_mounted) {
const consequent_transitions = block.c;
const alternate_transitions = block.a;
if (result) {
if (alternate_transitions === null || alternate_transitions.size === 0) {
execute_effect(alternate_effect);
} else {
trigger_transitions(alternate_transitions, 'out');
}
if (consequent_transitions === null || consequent_transitions.size === 0) {
execute_effect(consequent_effect);
} else {
trigger_transitions(consequent_transitions, 'in');
}
} else {
if (consequent_transitions === null || consequent_transitions.size === 0) {
execute_effect(consequent_effect);
} else {
trigger_transitions(consequent_transitions, 'out');
}
if (alternate_transitions === null || alternate_transitions.size === 0) {
execute_effect(alternate_effect);
} else {
trigger_transitions(alternate_transitions, 'in');
}
}
} else if (hydrating) {
const comment_text = /** @type {Comment} */ (current_hydration_fragment?.[0])?.data;
if (
!comment_text ||
(comment_text === 'ssr:if:true' && !result) ||
(comment_text === 'ssr:if:false' && result)
) {
// Hydration mismatch: remove everything inside the anchor and start fresh.
// This could happen using when `{#if browser} .. {/if}` in SvelteKit.
remove(current_hydration_fragment);
set_current_hydration_fragment(null);
mismatch = true;
} else {
// Remove the ssr:if comment node or else it will confuse the subsequent hydration algorithm
current_hydration_fragment.shift();
}
}
has_mounted = true;
}
},
block,
false
);
// Managed effect
const consequent_effect = render_effect(
(
/** @type {any} */ _,
/** @type {import('../../types.js').EffectSignal | null} */ consequent_effect
) => {
const result = block.v;
if (!result && consequent_dom !== null) {
remove(consequent_dom);
consequent_dom = null;
}
if (result && current_branch_effect !== consequent_effect) {
consequent_fn(anchor_node);
if (mismatch && current_branch_effect === null) {
// Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
current_branch_effect = consequent_effect;
consequent_dom = block.d;
}
block.d = null;
},
block,
true
);
block.ce = consequent_effect;
// Managed effect
const alternate_effect = render_effect(
(
/** @type {any} */ _,
/** @type {import('../../types.js').EffectSignal | null} */ alternate_effect
) => {
const result = block.v;
if (result && alternate_dom !== null) {
remove(alternate_dom);
alternate_dom = null;
}
if (!result && current_branch_effect !== alternate_effect) {
if (alternate_fn !== null) {
alternate_fn(anchor_node);
}
if (mismatch && current_branch_effect === null) {
// Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
current_branch_effect = alternate_effect;
alternate_dom = block.d;
}
block.d = null;
},
block,
true
);
block.ae = alternate_effect;
push_destroy_fn(if_effect, () => {
if (consequent_dom !== null) {
remove(consequent_dom);
}
if (alternate_dom !== null) {
remove(alternate_dom);
}
destroy_signal(consequent_effect);
destroy_signal(alternate_effect);
});
block.e = if_effect;
}

@ -0,0 +1,146 @@
import { KEY_BLOCK } from '../../block.js';
import { hydrate_block_anchor } from '../../hydration.js';
import { remove } from '../../reconciler.js';
import {
UNINITIALIZED,
current_block,
destroy_signal,
execute_effect,
push_destroy_fn,
render_effect,
safe_not_equal
} from '../../runtime.js';
import { trigger_transitions } from '../../transitions.js';
/** @returns {import('../../types.js').KeyBlock} */
function create_key_block() {
return {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('../../types.js').Block} */ (current_block),
// transition
r: null,
// type
t: KEY_BLOCK
};
}
/**
* @template V
* @param {Comment} anchor_node
* @param {() => V} key
* @param {(anchor: Node) => void} render_fn
* @returns {void}
*/
export function key_block(anchor_node, key, render_fn) {
const block = create_key_block();
/** @type {null | import('../../types.js').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node);
/** @type {V | typeof UNINITIALIZED} */
let key_value = UNINITIALIZED;
let mounted = false;
block.r =
/**
* @param {import('../../types.js').Transition} transition
* @returns {void}
*/
(transition) => {
const render = /** @type {import('../../types.js').Render} */ (current_render);
const transitions = render.s;
transitions.add(transition);
transition.f(() => {
transitions.delete(transition);
if (transitions.size === 0) {
// If the current render has changed since, then we can remove the old render
// effect as it's stale.
if (current_render !== render && render.e !== null) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
destroy_signal(render.e);
render.e = null;
}
}
});
};
const create_render_effect = () => {
/** @type {import('../../types.js').Render} */
const render = {
d: null,
e: null,
s: new Set(),
p: current_render
};
const effect = render_effect(
() => {
render_fn(anchor_node);
render.d = block.d;
block.d = null;
},
block,
true,
true
);
render.e = effect;
current_render = render;
};
const render = () => {
const render = current_render;
if (render === null) {
create_render_effect();
return;
}
const transitions = render.s;
if (transitions.size === 0) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
if (render.e) {
execute_effect(render.e);
} else {
create_render_effect();
}
} else {
trigger_transitions(transitions, 'out');
create_render_effect();
}
};
const key_effect = render_effect(
() => {
const prev_key_value = key_value;
key_value = key();
if (mounted && safe_not_equal(prev_key_value, key_value)) {
render();
}
},
block,
false
);
// To ensure topological ordering of the key effect to the render effect,
// we trigger the effect after.
render();
mounted = true;
push_destroy_fn(key_effect, () => {
let render = current_render;
while (render !== null) {
const dom = render.d;
if (dom !== null) {
remove(dom);
}
const effect = render.e;
if (effect !== null) {
destroy_signal(effect);
}
render = render.p;
}
});
block.e = key_effect;
}

@ -12,9 +12,6 @@ import {
} from './operations.js'; } from './operations.js';
import { import {
create_root_block, create_root_block,
create_if_block,
create_key_block,
create_await_block,
create_dynamic_element_block, create_dynamic_element_block,
create_head_block, create_head_block,
create_dynamic_component_block, create_dynamic_component_block,
@ -39,12 +36,9 @@ import {
is_signal, is_signal,
push_destroy_fn, push_destroy_fn,
execute_effect, execute_effect,
UNINITIALIZED,
untrack, untrack,
effect, effect,
flushSync,
flush_sync, flush_sync,
safe_not_equal,
current_block, current_block,
managed_effect, managed_effect,
push, push,
@ -1453,151 +1447,6 @@ export function slot(anchor_node, slot_fn, slot_props, fallback_fn) {
} }
} }
/**
* @param {Comment} anchor_node
* @param {() => boolean} condition_fn
* @param {(anchor: Node) => void} consequent_fn
* @param {null | ((anchor: Node) => void)} alternate_fn
* @returns {void}
*/
function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
const block = create_if_block();
hydrate_block_anchor(anchor_node);
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
/** @type {null | import('./types.js').TemplateNode | Array<import('./types.js').TemplateNode>} */
let consequent_dom = null;
/** @type {null | import('./types.js').TemplateNode | Array<import('./types.js').TemplateNode>} */
let alternate_dom = null;
let has_mounted = false;
/**
* @type {import("./types.js").EffectSignal | null}
*/
let current_branch_effect = null;
const if_effect = render_effect(
() => {
const result = !!condition_fn();
if (block.v !== result || !has_mounted) {
block.v = result;
if (has_mounted) {
const consequent_transitions = block.c;
const alternate_transitions = block.a;
if (result) {
if (alternate_transitions === null || alternate_transitions.size === 0) {
execute_effect(alternate_effect);
} else {
trigger_transitions(alternate_transitions, 'out');
}
if (consequent_transitions === null || consequent_transitions.size === 0) {
execute_effect(consequent_effect);
} else {
trigger_transitions(consequent_transitions, 'in');
}
} else {
if (consequent_transitions === null || consequent_transitions.size === 0) {
execute_effect(consequent_effect);
} else {
trigger_transitions(consequent_transitions, 'out');
}
if (alternate_transitions === null || alternate_transitions.size === 0) {
execute_effect(alternate_effect);
} else {
trigger_transitions(alternate_transitions, 'in');
}
}
} else if (hydrating) {
const comment_text = /** @type {Comment} */ (current_hydration_fragment?.[0])?.data;
if (
!comment_text ||
(comment_text === 'ssr:if:true' && !result) ||
(comment_text === 'ssr:if:false' && result)
) {
// Hydration mismatch: remove everything inside the anchor and start fresh.
// This could happen using when `{#if browser} .. {/if}` in SvelteKit.
remove(current_hydration_fragment);
set_current_hydration_fragment(null);
mismatch = true;
} else {
// Remove the ssr:if comment node or else it will confuse the subsequent hydration algorithm
current_hydration_fragment.shift();
}
}
has_mounted = true;
}
},
block,
false
);
// Managed effect
const consequent_effect = render_effect(
(
/** @type {any} */ _,
/** @type {import("./types.js").EffectSignal | null} */ consequent_effect
) => {
const result = block.v;
if (!result && consequent_dom !== null) {
remove(consequent_dom);
consequent_dom = null;
}
if (result && current_branch_effect !== consequent_effect) {
consequent_fn(anchor_node);
if (mismatch && current_branch_effect === null) {
// Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
current_branch_effect = consequent_effect;
consequent_dom = block.d;
}
block.d = null;
},
block,
true
);
block.ce = consequent_effect;
// Managed effect
const alternate_effect = render_effect(
(
/** @type {any} */ _,
/** @type {import("./types.js").EffectSignal | null} */ alternate_effect
) => {
const result = block.v;
if (result && alternate_dom !== null) {
remove(alternate_dom);
alternate_dom = null;
}
if (!result && current_branch_effect !== alternate_effect) {
if (alternate_fn !== null) {
alternate_fn(anchor_node);
}
if (mismatch && current_branch_effect === null) {
// Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
current_branch_effect = alternate_effect;
alternate_dom = block.d;
}
block.d = null;
},
block,
true
);
block.ae = alternate_effect;
push_destroy_fn(if_effect, () => {
if (consequent_dom !== null) {
remove(consequent_dom);
}
if (alternate_dom !== null) {
remove(alternate_dom);
}
destroy_signal(consequent_effect);
destroy_signal(alternate_effect);
});
block.e = if_effect;
}
export { if_block as if };
/** /**
* @param {(anchor: Node | null) => void} render_fn * @param {(anchor: Node | null) => void} render_fn
* @returns {void} * @returns {void}
@ -1876,293 +1725,6 @@ export function component(anchor_node, component_fn, render_fn) {
block.e = component_effect; block.e = component_effect;
} }
/**
* @template V
* @param {Comment} anchor_node
* @param {(() => Promise<V>)} input
* @param {null | ((anchor: Node) => void)} pending_fn
* @param {null | ((anchor: Node, value: V) => void)} then_fn
* @param {null | ((anchor: Node, error: unknown) => void)} catch_fn
* @returns {void}
*/
function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) {
const block = create_await_block();
/** @type {null | import('./types.js').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node);
/** @type {{}} */
let latest_token;
/** @type {typeof UNINITIALIZED | V} */
let resolved_value = UNINITIALIZED;
/** @type {unknown} */
let error = UNINITIALIZED;
let pending = false;
block.r =
/**
* @param {import('./types.js').Transition} transition
* @returns {void}
*/
(transition) => {
const render = /** @type {import('./types.js').Render} */ (current_render);
const transitions = render.s;
transitions.add(transition);
transition.f(() => {
transitions.delete(transition);
if (transitions.size === 0) {
// If the current render has changed since, then we can remove the old render
// effect as it's stale.
if (current_render !== render && render.e !== null) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
destroy_signal(render.e);
render.e = null;
}
}
});
};
const create_render_effect = () => {
/** @type {import('./types.js').Render} */
const render = {
d: null,
e: null,
s: new Set(),
p: current_render
};
const effect = render_effect(
() => {
if (error === UNINITIALIZED) {
if (resolved_value === UNINITIALIZED) {
// pending = true
block.n = true;
if (pending_fn !== null) {
pending_fn(anchor_node);
}
} else if (then_fn !== null) {
// pending = false
block.n = false;
then_fn(anchor_node, resolved_value);
}
} else if (catch_fn !== null) {
// pending = false
block.n = false;
catch_fn(anchor_node, error);
}
render.d = block.d;
block.d = null;
},
block,
true,
true
);
render.e = effect;
current_render = render;
};
const render = () => {
const render = current_render;
if (render === null) {
create_render_effect();
return;
}
const transitions = render.s;
if (transitions.size === 0) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
if (render.e) {
execute_effect(render.e);
} else {
create_render_effect();
}
} else {
create_render_effect();
trigger_transitions(transitions, 'out');
}
};
const await_effect = render_effect(
() => {
const token = {};
latest_token = token;
const promise = input();
if (is_promise(promise)) {
promise.then(
/** @param {V} v */
(v) => {
if (latest_token === token) {
// Ensure UI is in sync before resolving value.
flushSync();
resolved_value = v;
pending = false;
render();
}
},
/** @param {unknown} _error */
(_error) => {
error = _error;
pending = false;
render();
}
);
if (resolved_value !== UNINITIALIZED || error !== UNINITIALIZED) {
error = UNINITIALIZED;
resolved_value = UNINITIALIZED;
}
if (!pending) {
pending = true;
render();
}
} else {
error = UNINITIALIZED;
resolved_value = promise;
pending = false;
render();
}
},
block,
false
);
push_destroy_fn(await_effect, () => {
let render = current_render;
latest_token = {};
while (render !== null) {
const dom = render.d;
if (dom !== null) {
remove(dom);
}
const effect = render.e;
if (effect !== null) {
destroy_signal(effect);
}
render = render.p;
}
});
block.e = await_effect;
}
export { await_block as await };
/**
* @template V
* @param {Comment} anchor_node
* @param {() => V} key
* @param {(anchor: Node) => void} render_fn
* @returns {void}
*/
export function key(anchor_node, key, render_fn) {
const block = create_key_block();
/** @type {null | import('./types.js').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node);
/** @type {V | typeof UNINITIALIZED} */
let key_value = UNINITIALIZED;
let mounted = false;
block.r =
/**
* @param {import('./types.js').Transition} transition
* @returns {void}
*/
(transition) => {
const render = /** @type {import('./types.js').Render} */ (current_render);
const transitions = render.s;
transitions.add(transition);
transition.f(() => {
transitions.delete(transition);
if (transitions.size === 0) {
// If the current render has changed since, then we can remove the old render
// effect as it's stale.
if (current_render !== render && render.e !== null) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
destroy_signal(render.e);
render.e = null;
}
}
});
};
const create_render_effect = () => {
/** @type {import('./types.js').Render} */
const render = {
d: null,
e: null,
s: new Set(),
p: current_render
};
const effect = render_effect(
() => {
render_fn(anchor_node);
render.d = block.d;
block.d = null;
},
block,
true,
true
);
render.e = effect;
current_render = render;
};
const render = () => {
const render = current_render;
if (render === null) {
create_render_effect();
return;
}
const transitions = render.s;
if (transitions.size === 0) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
if (render.e) {
execute_effect(render.e);
} else {
create_render_effect();
}
} else {
trigger_transitions(transitions, 'out');
create_render_effect();
}
};
const key_effect = render_effect(
() => {
const prev_key_value = key_value;
key_value = key();
if (mounted && safe_not_equal(prev_key_value, key_value)) {
render();
}
},
block,
false
);
// To ensure topological ordering of the key effect to the render effect,
// we trigger the effect after.
render();
mounted = true;
push_destroy_fn(key_effect, () => {
let render = current_render;
while (render !== null) {
const dom = render.d;
if (dom !== null) {
remove(dom);
}
const effect = render.e;
if (effect !== null) {
destroy_signal(effect);
}
render = render.p;
}
});
block.e = key_effect;
}
/** /**
* @param {Element | Text | Comment} anchor * @param {Element | Text | Comment} anchor
* @param {boolean} is_html * @param {boolean} is_html
@ -2836,7 +2398,7 @@ export function createRoot() {
} }
/** /**
* Mounts a component to the given target and returns the exports and potentially the accessors (if compiled with `accessors: true`) of the component * Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
* *
* @template {Record<string, any>} Props * @template {Record<string, any>} Props
* @template {Record<string, any>} Exports * @template {Record<string, any>} Exports
@ -2860,7 +2422,7 @@ export function mount(component, options) {
} }
/** /**
* Hydrates a component on the given target and returns the exports and potentially the accessors (if compiled with `accessors: true`) of the component * Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
* *
* @template {Record<string, any>} Props * @template {Record<string, any>} Props
* @template {Record<string, any>} Exports * @template {Record<string, any>} Exports
@ -2932,7 +2494,7 @@ export function hydrate(component, options) {
* @template {Record<string, any>} Props * @template {Record<string, any>} Props
* @template {Record<string, any>} Exports * @template {Record<string, any>} Exports
* @template {Record<string, any>} Events * @template {Record<string, any>} Events
* @param {import('../../main/public.js').ComponentType<import('../../main/public.js').SvelteComponent<Props, Events>>} component * @param {import('../../main/public.js').ComponentType<import('../../main/public.js').SvelteComponent<Props, Events>>} Component
* @param {{ * @param {{
* target: Node; * target: Node;
* anchor: null | Text; * anchor: null | Text;
@ -2944,14 +2506,14 @@ export function hydrate(component, options) {
* }} options * }} options
* @returns {Exports} * @returns {Exports}
*/ */
function _mount(component, options) { function _mount(Component, options) {
const registered_events = new Set(); const registered_events = new Set();
const container = options.target; const container = options.target;
const block = create_root_block(options.intro || false); const block = create_root_block(options.intro || false);
/** @type {Exports} */ /** @type {Exports} */
// @ts-expect-error will be defined because the render effect runs synchronously // @ts-expect-error will be defined because the render effect runs synchronously
let accessors = undefined; let component = undefined;
const effect = render_effect( const effect = render_effect(
() => { () => {
@ -2961,7 +2523,7 @@ function _mount(component, options) {
options.context; options.context;
} }
// @ts-expect-error the public typings are not what the actual function looks like // @ts-expect-error the public typings are not what the actual function looks like
accessors = component(options.anchor, options.props || {}) || {}; component = Component(options.anchor, options.props || {}) || {};
if (options.context) { if (options.context) {
pop(); pop();
} }
@ -3008,7 +2570,7 @@ function _mount(component, options) {
event_handle(array_from(all_registerd_events)); event_handle(array_from(all_registerd_events));
root_event_handles.add(event_handle); root_event_handles.add(event_handle);
mounted_components.set(accessors, () => { mounted_components.set(component, () => {
for (const event_name of registered_events) { for (const event_name of registered_events) {
container.removeEventListener(event_name, bound_event_listener); container.removeEventListener(event_name, bound_event_listener);
} }
@ -3020,11 +2582,11 @@ function _mount(component, options) {
destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e)); destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e));
}); });
return accessors; return component;
} }
/** /**
* References of the accessors of all components that were `mount`ed or `hydrate`d. * References of the components that were mounted or hydrated.
* Uses a `WeakMap` to avoid memory leaks. * Uses a `WeakMap` to avoid memory leaks.
*/ */
let mounted_components = new WeakMap(); let mounted_components = new WeakMap();
@ -3034,12 +2596,12 @@ let mounted_components = new WeakMap();
* @param {Record<string, any>} component * @param {Record<string, any>} component
*/ */
export function unmount(component) { export function unmount(component) {
const destroy = mounted_components.get(component); const fn = mounted_components.get(component);
if (DEV && !destroy) { if (DEV && !fn) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.warn('Tried to unmount a component that was not mounted.'); console.warn('Tried to unmount a component that was not mounted.');
} }
destroy?.(); fn?.();
} }
/** /**

@ -1210,8 +1210,11 @@ export function set_signal_value(signal, value) {
// //
// $effect(() => x++) // $effect(() => x++)
// //
// We additionally want to skip this logic for when ignore_mutation_validation is
// true, as stores write to source signal on initialization.
if ( if (
is_runes(null) && is_runes(null) &&
!ignore_mutation_validation &&
current_effect !== null && current_effect !== null &&
current_effect.c === null && current_effect.c === null &&
(current_effect.f & CLEAN) !== 0 (current_effect.f & CLEAN) !== 0
@ -1884,8 +1887,8 @@ function on_destroy(fn) {
*/ */
export function push(props, runes = false) { export function push(props, runes = false) {
current_component_context = { current_component_context = {
// accessors // exports (and props, if `accessors: true`)
a: null, x: null,
// context // context
c: null, c: null,
// effects // effects
@ -1906,14 +1909,15 @@ export function push(props, runes = false) {
} }
/** /**
* @param {Record<string, any>} [accessors] * @template {Record<string, any>} T
* @returns {void} * @param {T} [component]
* @returns {T}
*/ */
export function pop(accessors) { export function pop(component) {
const context_stack_item = current_component_context; const context_stack_item = current_component_context;
if (context_stack_item !== null) { if (context_stack_item !== null) {
if (accessors !== undefined) { if (component !== undefined) {
context_stack_item.a = accessors; context_stack_item.x = component;
} }
const effects = context_stack_item.e; const effects = context_stack_item.e;
if (effects !== null) { if (effects !== null) {
@ -1925,6 +1929,9 @@ export function pop(accessors) {
current_component_context = context_stack_item.p; current_component_context = context_stack_item.p;
context_stack_item.m = true; context_stack_item.m = true;
} }
// Micro-optimization: Don't set .a above to the empty object
// so it can be garbage-collected when the return here is unused
return component || /** @type {T} */ ({});
} }
/** /**

@ -9,7 +9,7 @@ import {
KEY_BLOCK, KEY_BLOCK,
ROOT_BLOCK ROOT_BLOCK
} from './block.js'; } from './block.js';
import { destroy_each_item_block, get_first_element } from './each.js'; import { destroy_each_item_block, get_first_element } from './dom/blocks/each.js';
import { append_child, empty } from './operations.js'; import { append_child, empty } from './operations.js';
import { import {
current_block, current_block,

@ -40,8 +40,8 @@ export type ComponentContext = {
d: null | Signal<any>[]; d: null | Signal<any>[];
/** props */ /** props */
s: Record<string, unknown>; s: Record<string, unknown>;
/** accessors */ /** exports (and props, if `accessors: true`) */
a: Record<string, any> | null; x: Record<string, any> | null;
/** effects */ /** effects */
e: null | Array<EffectSignal>; e: null | Array<EffectSignal>;
/** mounted */ /** mounted */

@ -40,7 +40,10 @@ export {
freeze, freeze,
init init
} from './client/runtime.js'; } from './client/runtime.js';
export * from './client/each.js'; export { await_block as await } from './client/dom/blocks/await.js';
export { if_block as if } from './client/dom/blocks/if.js';
export { key_block as key } from './client/dom/blocks/key.js';
export * from './client/dom/blocks/each.js';
export * from './client/render.js'; export * from './client/render.js';
export * from './client/validate.js'; export * from './client/validate.js';
export { raf } from './client/timing.js'; export { raf } from './client/timing.js';

@ -164,7 +164,7 @@ export function createEventDispatcher() {
// in a server (non-DOM) environment? // in a server (non-DOM) environment?
const event = create_custom_event(/** @type {string} */ (type), detail, options); const event = create_custom_event(/** @type {string} */ (type), detail, options);
for (const fn of callbacks) { for (const fn of callbacks) {
fn.call(component_context.a, event); fn.call(component_context.x, event);
} }
return !event.defaultPrevented; return !event.defaultPrevented;
} }

@ -6,5 +6,5 @@
* https://svelte.dev/docs/svelte-compiler#svelte-version * https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string} * @type {string}
*/ */
export const VERSION = '5.0.0-next.57'; export const VERSION = '5.0.0-next.60';
export const PUBLIC_VERSION = '5'; export const PUBLIC_VERSION = '5';

@ -0,0 +1,16 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
immutable: true,
test({ assert, target }) {
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.htmlEqual(target.innerHTML, `<button>Update</button><ul><li>test !!!</li></ul>`);
}
});

@ -0,0 +1,17 @@
<script>
let items = [{id:1,value: "test"}]
const update = () => {
const clone = items.slice();
clone[0].value += " !!!";
items = clone;
}
</script>
<button on:click={update} >Update</button>
<ul>
{#each items as item (item.id)}
<li>{item.value}</li>
{/each}
</ul>

@ -1,7 +1,12 @@
import { test } from '../../test'; import { test } from '../../test';
export default test({ export default test({
compileOptions: {
dev: true
},
accessors: false, accessors: false,
test({ assert, component }) { test({ assert, component }) {
assert.equal(component.foo1, 42); assert.equal(component.foo1, 42);
assert.equal(component.foo2(), 42); assert.equal(component.foo2(), 42);

@ -1,6 +1,10 @@
import { test } from '../../test'; import { test } from '../../test';
export default test({ export default test({
compileOptions: {
dev: true
},
test({ assert, component }) { test({ assert, component }) {
assert.equal(component.bar1, 42); assert.equal(component.bar1, 42);
assert.equal(component.bar2, 42); assert.equal(component.bar2, 42);

@ -1,9 +1,11 @@
<script> <script>
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
let action = writable((node) => { let action = writable((node, text) => {
node.textContent = 'mounted'; node.textContent = text;
}); });
let text = writable('mounted')
</script> </script>
<div use:$action>hello</div> <div use:$action={$text}>hello</div>

@ -0,0 +1,22 @@
import { test } from '../../test';
import { log } from './log.js';
export default test({
html: `<button>increment</button>`,
before_test() {
log.length = 0;
},
async test({ assert, target }) {
const btn = target.querySelector('button');
assert.deepEqual(log, [1]);
await btn?.click();
assert.deepEqual(log, [1, 2]);
await btn?.click();
assert.deepEqual(log, [1, 2, 3]);
}
});

@ -0,0 +1,14 @@
<script>
import { writable } from 'svelte/store';
import { log } from './log';
const count = writable(0);
let ran = 0;
$effect(() => {
$count;
log.push(++ran);
});
</script>
<button on:click={() => $count++}>increment</button>

@ -0,0 +1,3 @@
import { test } from '../../test';
export default test({});

@ -0,0 +1,17 @@
// index.svelte (Svelte VERSION)
// Note: compiler output will change before 5.0 is released!
import "svelte/internal/disclose-version";
import * as $ from "svelte/internal";
export default function Bind_this($$anchor, $$props) {
$.push($$props, false);
$.init();
/* Init */
var fragment = $.comment($$anchor);
var node = $.child_frag(fragment);
$.bind_this(Foo(node, {}), ($$value) => foo = $$value, foo);
$.close_frag($$anchor, fragment);
$.pop();
}

@ -0,0 +1,14 @@
// index.svelte (Svelte VERSION)
// Note: compiler output will change before 5.0 is released!
import * as $ from "svelte/internal/server";
export default function Bind_this($$payload, $$props) {
$.push(false);
const anchor = $.create_anchor($$payload);
$$payload.out += `${anchor}`;
Foo($$payload, {});
$$payload.out += `${anchor}`;
$.pop();
}

@ -67,6 +67,10 @@ export function suite_with_variants<Test extends BaseTest, Variants extends stri
}; };
} }
// If a directory only contains these children, it's a sign that it's leftover
// from a different branch, and we can skip the test
const ignored = ['_output', '_actual.json'];
async function for_each_dir<Test extends BaseTest>( async function for_each_dir<Test extends BaseTest>(
cwd: string, cwd: string,
samples_dir = 'samples', samples_dir = 'samples',
@ -76,13 +80,14 @@ async function for_each_dir<Test extends BaseTest>(
let created_test = false; let created_test = false;
for (const dir of fs.readdirSync(`${cwd}/${samples_dir}`)) { for (const dir of fs.readdirSync(`${cwd}/${samples_dir}`)) {
if (dir[0] === '.') continue; if (dir[0] === '.' || !filter.test(dir)) continue;
const file = `${cwd}/${samples_dir}/${dir}/_config.js`; if (fs.readdirSync(`${cwd}/${samples_dir}/${dir}`).every((file) => ignored.includes(file))) {
if (!filter.test(dir)) {
continue; continue;
} }
const file = `${cwd}/${samples_dir}/${dir}/_config.js`;
created_test = true; created_test = true;
const config = (fs.existsSync(file) ? (await import(file)).default : {}) as Test; const config = (fs.existsSync(file) ? (await import(file)).default : {}) as Test;

@ -328,7 +328,7 @@ declare module 'svelte' {
*/ */
export function createRoot(): void; export function createRoot(): void;
/** /**
* Mounts a component to the given target and returns the exports and potentially the accessors (if compiled with `accessors: true`) of the component * Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
* *
* */ * */
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: { export function mount<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: {
@ -339,7 +339,7 @@ declare module 'svelte' {
intro?: boolean | undefined; intro?: boolean | undefined;
}): Exports; }): Exports;
/** /**
* Hydrates a component on the given target and returns the exports and potentially the accessors (if compiled with `accessors: true`) of the component * Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
* *
* */ * */
export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: { export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: {

@ -15,6 +15,6 @@ export default {
}, },
vitePlugin: { vitePlugin: {
inspector: true inspector: false
} }
}; };

Loading…
Cancel
Save