Merge remote-tracking branch 'upstream/main' into more-precise-validate-dynamic-component

pull/12452/head
paoloricciuti 2 years ago
commit c27209cc64

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: skip pending block for already-resolved promises

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: move dev-time component properties to private symbols'

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure hydration walks all nodes

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: always pass original component to HMR wrapper

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: add ability to ignore warnings through `warningFilter` compiler option

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: prevent whitespaces merging across component boundaries

@ -58,6 +58,7 @@
"calm-ravens-sneeze",
"chatty-beans-divide",
"chatty-cups-drop",
"chatty-ghosts-unite",
"chatty-sloths-allow",
"chatty-taxis-juggle",
"chilled-pumas-invite",
@ -192,6 +193,7 @@
"fresh-impalas-bow",
"fresh-walls-bathe",
"fresh-weeks-trade",
"fresh-wombats-learn",
"friendly-candles-relate",
"friendly-clouds-rhyme",
"friendly-lies-camp",
@ -211,6 +213,7 @@
"gentle-trees-exercise",
"gentle-wasps-pull",
"giant-bananas-turn",
"giant-jars-applaud",
"giant-moons-own",
"giant-planets-shake",
"giant-plants-grin",
@ -274,6 +277,7 @@
"itchy-bulldogs-tan",
"itchy-eels-marry",
"itchy-kings-deliver",
"itchy-lemons-punch",
"itchy-lions-wash",
"itchy-panthers-shave",
"itchy-peaches-compare",
@ -319,6 +323,7 @@
"light-pens-watch",
"little-ligers-exist",
"little-pans-jog",
"little-seals-reflect",
"long-buckets-lay",
"long-carrots-sneeze",
"long-crews-return",
@ -410,6 +415,7 @@
"orange-yaks-protect",
"orange-zoos-heal",
"perfect-actors-bake",
"perfect-hats-dance",
"pink-bikes-agree",
"pink-goats-promise",
"pink-mayflies-tie",
@ -631,6 +637,7 @@
"thin-foxes-lick",
"thin-spoons-float",
"thin-years-rhyme",
"thirty-dogs-whisper",
"thirty-flies-push",
"thirty-flowers-sit",
"thirty-ghosts-fix",
@ -688,6 +695,7 @@
"wet-pears-remain",
"wet-wombats-repeat",
"wicked-bikes-matter",
"wicked-carrots-explain",
"wicked-clouds-exercise",
"wicked-doors-train",
"wicked-emus-drive",
@ -714,6 +722,7 @@
"witty-steaks-dream",
"witty-tomatoes-care",
"witty-years-crash",
"yellow-bananas-rhyme",
"yellow-pugs-raise",
"yellow-rockets-sit",
"yellow-taxis-double",

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure previous transitions are properly aborted

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: run animations in microtask so that deferred transitions can measure nodes correctly

@ -1,5 +1,35 @@
# svelte
## 5.0.0-next.187
### Patch Changes
- fix: always pass original component to HMR wrapper ([#12454](https://github.com/sveltejs/svelte/pull/12454))
- fix: ensure previous transitions are properly aborted ([#12460](https://github.com/sveltejs/svelte/pull/12460))
## 5.0.0-next.186
### Patch Changes
- feat: skip pending block for already-resolved promises ([#12274](https://github.com/sveltejs/svelte/pull/12274))
- feat: add ability to ignore warnings through `warningFilter` compiler option ([#12296](https://github.com/sveltejs/svelte/pull/12296))
- fix: run animations in microtask so that deferred transitions can measure nodes correctly ([#12453](https://github.com/sveltejs/svelte/pull/12453))
## 5.0.0-next.185
### Patch Changes
- fix: allow leading and trailing comments in mustache expression ([#11866](https://github.com/sveltejs/svelte/pull/11866))
- fix: ensure hydration walks all nodes ([#12448](https://github.com/sveltejs/svelte/pull/12448))
- fix: prevent whitespaces merging across component boundaries ([#12449](https://github.com/sveltejs/svelte/pull/12449))
- fix: detect mutations within assignment expressions ([#12429](https://github.com/sveltejs/svelte/pull/12429))
## 5.0.0-next.184
### Patch Changes

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

@ -1,4 +1,4 @@
import { warnings, ignore_stack, ignore_map } from './state.js';
import { warnings, ignore_stack, ignore_map, warning_filter } from './state.js';
import { CompileDiagnostic } from './utils/compile_diagnostic.js';
/** @typedef {{ start?: number, end?: number }} NodeLike */
@ -28,13 +28,15 @@ function w(node, code, message) {
}
if (stack && stack.at(-1)?.has(code)) return;
warnings.push(
new InternalCompileWarning(
code,
message,
node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined
)
const warning = new InternalCompileWarning(
code,
message,
node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined
);
if (!warning_filter(warning)) return;
warnings.push(warning);
}
export const codes = CODES;

@ -20,6 +20,7 @@ export { default as preprocess } from './preprocess/index.js';
* @returns {CompileResult}
*/
export function compile(source, options) {
state.reset_warning_filter(options.warningFilter);
const validated = validate_component_options(options, '');
state.reset(source, validated);
@ -58,6 +59,7 @@ export function compile(source, options) {
* @returns {CompileResult}
*/
export function compileModule(source, options) {
state.reset_warning_filter(options.warningFilter);
const validated = validate_module_options(options, '');
state.reset(source, validated);
@ -103,6 +105,7 @@ export function compileModule(source, options) {
* @returns {Root | LegacyRoot}
*/
export function parse(source, { filename, rootDir, modern } = {}) {
state.reset_warning_filter(() => false);
state.reset(source, { filename, rootDir }); // TODO it's weird to require filename/rootDir here. reconsider the API
const ast = _parse(source);

@ -10,7 +10,7 @@ import { parse } from '../phases/1-parse/index.js';
import { analyze_component } from '../phases/2-analyze/index.js';
import { validate_component_options } from '../validate-options.js';
import { get_rune } from '../phases/scope.js';
import { reset } from '../state.js';
import { reset, reset_warning_filter } from '../state.js';
import { extract_identifiers } from '../utils/ast.js';
import { regex_is_valid_identifier } from '../phases/patterns.js';
import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js';
@ -24,6 +24,7 @@ import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js';
*/
export function migrate(source) {
try {
reset_warning_filter(() => false);
reset(source, { filename: 'migrate.svelte' });
let parsed = parse(source);

@ -418,7 +418,7 @@ export function client_component(source, analysis, options) {
if (options.hmr) {
const accept_fn_body = [
b.stmt(b.call('$.set', b.id('s'), b.member(b.id('module'), b.id('default'))))
b.stmt(b.call('$.set', b.id('s'), b.member(b.id('module.default'), b.id('$.ORIGINAL'), true)))
];
if (analysis.css.hash) {
@ -440,8 +440,18 @@ export function client_component(source, analysis, options) {
const hmr = b.block([
b.const(b.id('s'), b.call('$.source', b.id(analysis.name))),
b.const(b.id('filename'), b.member(b.id(analysis.name), b.id('filename'))),
b.const(b.id('$$original'), b.id(analysis.name)),
b.stmt(b.assignment('=', b.id(analysis.name), b.call('$.hmr', b.id('s')))),
b.stmt(b.assignment('=', b.member(b.id(analysis.name), b.id('filename')), b.id('filename'))),
// Assign the original component to the wrapper so we can use it on hot reload patching,
// else we would call the HMR function two times
b.stmt(
b.assignment(
'=',
b.member(b.id(analysis.name), b.id('$.ORIGINAL'), true),
b.id('$$original')
)
),
b.stmt(b.call('import.meta.hot.accept', b.arrow([b.id('module')], b.block(accept_fn_body))))
]);
@ -452,10 +462,14 @@ export function client_component(source, analysis, options) {
if (options.dev) {
if (filename) {
// add `App.filename = 'App.svelte'` so that we can print useful messages later
// add `App[$.FILENAME] = 'App.svelte'` so that we can print useful messages later
body.unshift(
b.stmt(
b.assignment('=', b.member(b.id(analysis.name), b.id('filename')), b.literal(filename))
b.assignment(
'=',
b.member(b.id(analysis.name), b.id('$.FILENAME'), true),
b.literal(filename)
)
)
);
}

@ -1640,7 +1640,7 @@ export const template_visitors = {
call = b.call(
'$.add_locations',
call,
b.member(b.id(context.state.analysis.name), b.id('filename')),
b.member(b.id(context.state.analysis.name), b.id('$.FILENAME'), true),
serialize_locations(state.locations)
);
}

@ -2249,10 +2249,14 @@ export function server_component(analysis, options) {
}
if (options.dev && filename) {
// add `App.filename = 'App.svelte'` so that we can print useful messages later
// add `App[$.FILENAME] = 'App.svelte'` so that we can print useful messages later
body.unshift(
b.stmt(
b.assignment('=', b.member(b.id(analysis.name), b.id('filename')), b.literal(filename))
b.assignment(
'=',
b.member(b.id(analysis.name), b.id('$.FILENAME'), true),
b.literal(filename)
)
)
);
}

@ -288,7 +288,11 @@ export function clean_nodes(
))),
/** if a component or snippet starts with text, we need to add an anchor comment so that its text node doesn't get fused with its surroundings */
is_text_first:
(parent.type === 'Fragment' || parent.type === 'SnippetBlock') &&
(parent.type === 'Fragment' ||
parent.type === 'SnippetBlock' ||
parent.type === 'SvelteComponent' ||
parent.type === 'Component' ||
parent.type === 'SvelteSelf') &&
first &&
(first?.type === 'Text' || first?.type === 'ExpressionTag')
};

@ -1,4 +1,4 @@
/** @import { SvelteNode } from './types' */
/** @import { CompileOptions, SvelteNode } from './types' */
/** @import { Warning } from '#compiler' */
import { getLocator } from 'locate-character';
@ -22,6 +22,9 @@ export let source;
export let locator = getLocator('', { offsetLine: 1 });
/** @type {NonNullable<CompileOptions['warningFilter']>} */
export let warning_filter;
/**
* The current stack of ignored warnings
* @type {Set<string>[]}
@ -48,6 +51,14 @@ export function pop_ignore() {
ignore_stack.pop();
}
/**
*
* @param {(warning: Warning) => boolean} fn
*/
export function reset_warning_filter(fn = () => true) {
warning_filter = fn;
}
/**
* @param {string} _source
* @param {{ filename?: string, rootDir?: string }} options

@ -203,12 +203,16 @@ export interface ModuleCompileOptions {
* Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically.
*/
filename?: string;
/**
* Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically.
* @default process.cwd() on node-like environments, undefined elsewhere
*/
rootDir?: string;
/**
* A function that gets a `Warning` as an argument and returns a boolean.
* Use this to filter out warnings. Return `true` to keep the warning, `false` to discard it.
*/
warningFilter?: (warning: Warning) => boolean;
}
// The following two somewhat scary looking types ensure that certain types are required but can be undefined still

@ -104,6 +104,8 @@ export const validate_component_options =
hmr: boolean(false),
warningFilter: fun(() => true),
sourcemap: validator(undefined, (input) => {
// Source maps can take on a variety of values, including string, JSON, map objects from magic-string and source-map,
// so there's no good way to check type validity here
@ -259,6 +261,20 @@ function string(fallback, allow_empty = true) {
});
}
/**
* @param {string[]} fallback
* @returns {Validator}
*/
function string_array(fallback) {
return validator(fallback, (input, keypath) => {
if (input && !Array.isArray(input)) {
throw_error(`${keypath} should be a string array, if specified`);
}
return input;
});
}
/**
* @param {boolean | undefined} fallback
* @returns {Validator}

@ -1,6 +1,12 @@
/* This file is generated by scripts/process-messages/index.js. Do not edit! */
import { warnings, ignore_stack, ignore_map } from './state.js';
import {
warnings,
ignore_stack,
ignore_map,
warning_filter
} from './state.js';
import { CompileDiagnostic } from './utils/compile_diagnostic.js';
/** @typedef {{ start?: number, end?: number }} NodeLike */
@ -30,7 +36,11 @@ function w(node, code, message) {
}
if (stack && stack.at(-1)?.has(code)) return;
warnings.push(new InternalCompileWarning(code, message, node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined));
const warning = new InternalCompileWarning(code, message, node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined);
if (!warning_filter(warning)) return;
warnings.push(warning);
}
export const codes = [

@ -30,6 +30,10 @@ export const ELEMENT_PRESERVE_ATTRIBUTE_CASE = 1 << 1;
export const UNINITIALIZED = Symbol();
// Dev-time component properties
export const FILENAME = Symbol('filename');
export const ORIGINAL = Symbol('original');
/** List of elements that require raw contents and should not have SSR comments put in them */
export const RawTextElements = ['textarea', 'script', 'style', 'title'];

@ -1,11 +1,12 @@
import * as e from '../errors.js';
import { current_component_context } from '../runtime.js';
import { FILENAME } from '../../../constants.js';
import { get_component } from './ownership.js';
/** @param {Function & { filename: string }} target */
/** @param {Function & { [FILENAME]: string }} target */
export function check_target(target) {
if (target) {
e.component_api_invalid_new(target.filename ?? 'a component', target.name);
e.component_api_invalid_new(target[FILENAME] ?? 'a component', target.name);
}
}
@ -15,8 +16,8 @@ export function legacy_api() {
/** @param {string} method */
function error(method) {
// @ts-expect-error
const parent = get_component()?.filename ?? 'Something';
e.component_api_changed(parent, method, component.filename);
const parent = get_component()?.[FILENAME] ?? 'Something';
e.component_api_changed(parent, method, component[FILENAME]);
}
return {

@ -6,6 +6,7 @@ import { render_effect, user_pre_effect } from '../reactivity/effects.js';
import { dev_current_component_function } from '../runtime.js';
import { get_prototype_of } from '../../shared/utils.js';
import * as w from '../warnings.js';
import { FILENAME } from '../../../constants.js';
/** @type {Record<string, Array<{ start: Location, end: Location, component: Function }>>} */
const boundaries = {};
@ -115,8 +116,8 @@ export function add_owner(object, owner, global = false) {
if (metadata && !has_owner(metadata, component)) {
let original = get_owner(metadata);
if (owner.filename !== component.filename) {
w.ownership_invalid_binding(component.filename, owner.filename, original.filename);
if (owner[FILENAME] !== component[FILENAME]) {
w.ownership_invalid_binding(component[FILENAME], owner[FILENAME], original[FILENAME]);
}
}
}
@ -236,9 +237,9 @@ export function check_ownership(metadata) {
let original = get_owner(metadata);
// @ts-expect-error
if (original.filename !== component.filename) {
if (original[FILENAME] !== component[FILENAME]) {
// @ts-expect-error
w.ownership_invalid_mutation(component.filename, original.filename);
w.ownership_invalid_mutation(component[FILENAME], original[FILENAME]);
} else {
w.ownership_invalid_mutation();
}

@ -139,7 +139,7 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
} else {
// Wait a microtask before checking if we should show the pending state as
// the promise might have resolved by the next microtask.
queue_micro_task(() => {
Promise.resolve().then(() => {
if (!resolved) update(PENDING, true);
});
}

@ -1,5 +1,5 @@
/** @import { Effect, TemplateNode } from '#client' */
import { HYDRATION_ERROR } from '../../../../constants.js';
import { FILENAME, HYDRATION_ERROR } from '../../../../constants.js';
import { block, branch, destroy_effect } from '../../reactivity/effects.js';
import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from '../hydration.js';
import { create_fragment_from_html } from '../reconciler.js';
@ -23,8 +23,8 @@ function check_hash(element, server_hash, value) {
const loc = element.__svelte_meta?.loc;
if (loc) {
location = `near ${loc.file}:${loc.line}:${loc.column}`;
} else if (dev_current_component_function?.filename) {
location = `in ${dev_current_component_function.filename}`;
} else if (dev_current_component_function?.[FILENAME]) {
location = `in ${dev_current_component_function[FILENAME]}`;
}
w.hydration_html_changed(

@ -1,5 +1,5 @@
/** @import { Effect, EffectNodes, TemplateNode } from '#client' */
import { namespace_svg } from '../../../../constants.js';
import { FILENAME, namespace_svg } from '../../../../constants.js';
import {
hydrate_next,
hydrate_node,
@ -38,7 +38,7 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
hydrate_next();
}
var filename = DEV && location && current_component_context?.function.filename;
var filename = DEV && location && current_component_context?.function[FILENAME];
/** @type {string | null} */
var tag;

@ -96,10 +96,17 @@ export function animation(element, get_fn, get_params) {
) {
const options = get_fn()(this.element, { from, to }, get_params?.());
animation = animate(this.element, options, undefined, 1, () => {
animation?.abort();
animation = undefined;
});
animation = animate(
this.element,
options,
undefined,
1,
() => {
animation?.abort();
animation = undefined;
},
undefined
);
}
},
fix() {
@ -157,10 +164,11 @@ export function animation(element, get_fn, get_params) {
export function transition(flags, element, get_fn, get_params) {
var is_intro = (flags & TRANSITION_IN) !== 0;
var is_outro = (flags & TRANSITION_OUT) !== 0;
var is_both = is_intro && is_outro;
var is_global = (flags & TRANSITION_GLOBAL) !== 0;
/** @type {'in' | 'out' | 'both'} */
var direction = is_intro && is_outro ? 'both' : is_intro ? 'in' : 'out';
var direction = is_both ? 'both' : is_intro ? 'in' : 'out';
/** @type {import('#client').AnimationConfig | ((opts: { direction: 'in' | 'out' }) => import('#client').AnimationConfig) | undefined} */
var current_options;
@ -191,27 +199,54 @@ export function transition(flags, element, get_fn, get_params) {
// abort the outro to prevent overlap with the intro
outro?.abort();
// abort previous intro (can happen if an element is intro'd, then outro'd, then intro'd again)
intro?.abort();
if (is_intro) {
dispatch_event(element, 'introstart');
intro = animate(element, get_options(), outro, 1, () => {
dispatch_event(element, 'introend');
intro = current_options = undefined;
});
intro = animate(
element,
get_options(),
outro,
1,
() => {
dispatch_event(element, 'introend');
intro = current_options = undefined;
},
is_both
? undefined
: () => {
intro = current_options = undefined;
}
);
} else {
reset?.();
}
},
out(fn) {
// abort previous outro (can happen if an element is outro'd, then intro'd, then outro'd again)
outro?.abort();
if (is_outro) {
element.inert = true;
dispatch_event(element, 'outrostart');
outro = animate(element, get_options(), intro, 0, () => {
dispatch_event(element, 'outroend');
outro = current_options = undefined;
fn?.();
});
outro = animate(
element,
get_options(),
intro,
0,
() => {
dispatch_event(element, 'outroend');
outro = current_options = undefined;
fn?.();
},
is_both
? undefined
: () => {
outro = current_options = undefined;
}
);
// TODO arguably the outro should never null itself out until _all_ outros for this effect have completed...
// in that case we wouldn't need to store `reset` separately
@ -263,10 +298,11 @@ export function transition(flags, element, get_fn, get_params) {
* @param {import('#client').AnimationConfig | ((opts: { direction: 'in' | 'out' }) => import('#client').AnimationConfig)} options
* @param {import('#client').Animation | undefined} counterpart The corresponding intro/outro to this outro/intro
* @param {number} t2 The target `t` value `1` for intro, `0` for outro
* @param {(() => void) | undefined} callback
* @param {(() => void) | undefined} on_finish Called after successfully completing the animation
* @param {(() => void) | undefined} on_abort Called if the animation is aborted
* @returns {import('#client').Animation}
*/
function animate(element, options, counterpart, t2, callback) {
function animate(element, options, counterpart, t2, on_finish, on_abort) {
var is_intro = t2 === 1;
if (is_function(options)) {
@ -278,7 +314,7 @@ function animate(element, options, counterpart, t2, callback) {
queue_micro_task(() => {
var o = options({ direction: is_intro ? 'in' : 'out' });
a = animate(element, o, counterpart, t2, callback);
a = animate(element, o, counterpart, t2, on_finish, on_abort);
});
// ...but we want to do so without using `async`/`await` everywhere, so
@ -294,7 +330,7 @@ function animate(element, options, counterpart, t2, callback) {
counterpart?.deactivate();
if (!options?.duration) {
callback?.();
on_finish?.();
return {
abort: noop,
deactivate: noop,
@ -303,13 +339,13 @@ function animate(element, options, counterpart, t2, callback) {
};
}
var { delay = 0, duration, css, tick, easing = linear } = options;
const { delay = 0, css, tick, easing = linear } = options;
var start = raf.now() + delay;
var t1 = counterpart?.t(start) ?? 1 - t2;
var delta = t2 - t1;
duration *= Math.abs(delta);
var duration = options.duration * Math.abs(delta);
var end = start + duration;
/** @type {Animation} */
@ -319,52 +355,55 @@ function animate(element, options, counterpart, t2, callback) {
var task;
if (css) {
// WAAPI
var keyframes = [];
var n = Math.ceil(duration / (1000 / 60)); // `n` must be an integer, or we risk missing the `t2` value
// In case of a delayed intro, apply the initial style for the duration of the delay;
// else in case of a fade-in for example the element would be visible until the animation starts
if (is_intro && delay > 0) {
let m = Math.ceil(delay / (1000 / 60));
let keyframe = css_to_keyframe(css(0, 1));
for (let i = 0; i < m; i += 1) {
keyframes.push(keyframe);
// run after a micro task so that all transitions that are lining up and are about to run can correctly measure the DOM
queue_micro_task(() => {
// WAAPI
var keyframes = [];
var n = Math.ceil(duration / (1000 / 60)); // `n` must be an integer, or we risk missing the `t2` value
// In case of a delayed intro, apply the initial style for the duration of the delay;
// else in case of a fade-in for example the element would be visible until the animation starts
if (is_intro && delay > 0) {
let m = Math.ceil(delay / (1000 / 60));
let keyframe = css_to_keyframe(css(0, 1));
for (let i = 0; i < m; i += 1) {
keyframes.push(keyframe);
}
}
}
for (var i = 0; i <= n; i += 1) {
var t = t1 + delta * easing(i / n);
var styles = css(t, 1 - t);
keyframes.push(css_to_keyframe(styles));
}
animation = element.animate(keyframes, {
delay: is_intro ? 0 : delay,
duration: duration + (is_intro ? delay : 0),
easing: 'linear',
fill: 'forwards'
});
animation.finished
.then(() => {
callback?.();
for (var i = 0; i <= n; i += 1) {
var t = t1 + delta * easing(i / n);
var styles = css(t, 1 - t);
keyframes.push(css_to_keyframe(styles));
}
if (t2 === 1) {
animation.cancel();
}
})
.catch((e) => {
// Error for DOMException: The user aborted a request. This results in two things:
// - startTime is `null`
// - currentTime is `null`
// We can't use the existence of an AbortError as this error and error code is shared
// with other Web APIs such as fetch().
if (animation.startTime !== null && animation.currentTime !== null) {
throw e;
}
animation = element.animate(keyframes, {
delay: is_intro ? 0 : delay,
duration: duration + (is_intro ? delay : 0),
easing: 'linear',
fill: 'forwards'
});
animation.finished
.then(() => {
on_finish?.();
if (t2 === 1) {
animation.cancel();
}
})
.catch((e) => {
// Error for DOMException: The user aborted a request. This results in two things:
// - startTime is `null`
// - currentTime is `null`
// We can't use the existence of an AbortError as this error and error code is shared
// with other Web APIs such as fetch().
if (animation.startTime !== null && animation.currentTime !== null) {
throw e;
}
});
});
} else {
// Timer
if (t1 === 0) {
@ -374,7 +413,7 @@ function animate(element, options, counterpart, t2, callback) {
task = loop((now) => {
if (now >= end) {
tick?.(t2, 1 - t2);
callback?.();
on_finish?.();
return false;
}
@ -391,9 +430,11 @@ function animate(element, options, counterpart, t2, callback) {
abort: () => {
animation?.cancel();
task?.abort();
on_abort?.();
},
deactivate: () => {
callback = undefined;
on_finish = undefined;
on_abort = undefined;
},
reset: () => {
if (t2 === 0) {

@ -51,11 +51,11 @@ export function empty() {
}
/**
* Don't mark this as side-effect-free, hydration needs to walk all nodes
* @template {Node} N
* @param {N} node
* @returns {Node | null}
*/
/*#__NO_SIDE_EFFECTS__*/
export function child(node) {
if (!hydrating) {
return node.firstChild;
@ -73,11 +73,11 @@ export function child(node) {
}
/**
* Don't mark this as side-effect-free, hydration needs to walk all nodes
* @param {DocumentFragment | TemplateNode[]} fragment
* @param {boolean} is_text
* @returns {Node | null}
*/
/*#__NO_SIDE_EFFECTS__*/
export function first_child(fragment, is_text) {
if (!hydrating) {
// when not hydrating, `fragment` is a `DocumentFragment` (the result of calling `open_frag`)
@ -103,12 +103,12 @@ export function first_child(fragment, is_text) {
}
/**
* Don't mark this as side-effect-free, hydration needs to walk all nodes
* @template {Node} N
* @param {N} node
* @param {boolean} is_text
* @returns {Node | null}
*/
/*#__NO_SIDE_EFFECTS__*/
export function sibling(node, is_text = false) {
if (!hydrating) {
return /** @type {TemplateNode} */ (node.nextSibling);

@ -207,7 +207,9 @@ function run_scripts(node) {
}
}
/*#__NO_SIDE_EFFECTS__*/
/**
* Don't mark this as side-effect-free, hydration needs to walk all nodes
*/
export function text() {
if (!hydrating) {
var t = empty();

@ -1,3 +1,4 @@
export { FILENAME, ORIGINAL } from '../../constants.js';
export { add_locations } from './dev/elements.js';
export { hmr } from './dev/hmr.js';
export {

@ -29,6 +29,7 @@ import { mutate, set, source } from './reactivity/sources.js';
import { update_derived } from './reactivity/deriveds.js';
import * as e from './errors.js';
import { lifecycle_outside_component } from '../shared/errors.js';
import { FILENAME } from '../../constants.js';
const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1;
@ -226,7 +227,7 @@ function handle_error(error, effect, component_context) {
while (current_context !== null) {
/** @type {string} */
var filename = current_context.function?.filename;
var filename = current_context.function?.[FILENAME];
if (filename) {
const file = filename.split('/').pop();

@ -1,7 +1,7 @@
import { untrack } from './runtime.js';
import { get_descriptor, is_array } from '../shared/utils.js';
import * as e from './errors.js';
import { validate_component } from '../shared/validate.js';
import { FILENAME } from '../../constants.js';
/** regex of all html void element names */
const void_element_names =
@ -74,7 +74,7 @@ export function validate_each_keys(collection, key_fn) {
* @param {Record<string, any>} $$props
* @param {string[]} bindable
* @param {string[]} exports
* @param {Function & { filename: string }} component
* @param {Function & { [FILENAME]: string }} component
*/
export function validate_prop_bindings($$props, bindable, exports, component) {
for (const key in $$props) {
@ -83,11 +83,11 @@ export function validate_prop_bindings($$props, bindable, exports, component) {
if (setter) {
if (exports.includes(key)) {
e.bind_invalid_export(component.filename, key, name);
e.bind_invalid_export(component[FILENAME], key, name);
}
if (!bindable.includes(key)) {
e.bind_not_bindable(key, component.filename, name);
e.bind_not_bindable(key, component[FILENAME], name);
}
}
}

@ -1,4 +1,5 @@
import {
FILENAME,
disallowed_paragraph_contents,
interactive_elements,
is_tag_valid_with_parent
@ -56,7 +57,7 @@ function print_error(payload, parent, child) {
* @param {number} column
*/
export function push_element(payload, tag, line, column) {
var filename = /** @type {import('#server').Component} */ (current_component).function.filename;
var filename = /** @type {import('#server').Component} */ (current_component).function[FILENAME];
var child = { tag, parent, filename, line, column };
if (parent !== null && !is_tag_valid_with_parent(tag, parent.tag)) {

@ -1,5 +1,6 @@
/** @import { Component, Payload, RenderOutput } from '#server' */
/** @import { Store } from '#shared' */
export { FILENAME, ORIGINAL } from '../../constants.js';
import { is_promise, noop } from '../shared/utils.js';
import { subscribe_to_store } from '../../store/utils.js';
import {

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

@ -1,4 +1,4 @@
<div class="a svelte-xyz"></div>
<div class="d svelte-xyz"></div>
<div class="f svelte-xyz"></div>
<div class="b svelte-xyz"></div>
<div class="g svelte-xyz"></div>
<div class="h svelte-xyz"></div>

@ -1,7 +1,3 @@
<script>
let promise = Promise.resolve();
</script>
<style>
.a ~ .b { color: green; }
.a ~ .c { color: green; }
@ -20,19 +16,20 @@
<div class="a"></div>
{#await promise then value}
<!-- non-promise, so that something renders initially -->
{#await true then value}
<div class="b"></div>
{:catch error}
<div class="c"></div>
{/await}
{#await promise}
{#await true}
<div class="d"></div>
{:catch error}
<div class="e"></div>
{/await}
{#await promise}
{#await true}
<div class="f"></div>
{:then error}
<div class="g"></div>

@ -5,20 +5,20 @@ export default test({
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b ~ .c"',
start: { character: 269, column: 1, line: 15 },
end: { character: 276, column: 8, line: 15 }
start: { character: 217, column: 1, line: 13 },
end: { character: 224, column: 8, line: 13 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".c ~ .d"',
start: { character: 296, column: 1, line: 16 },
end: { character: 303, column: 8, line: 16 }
start: { character: 242, column: 1, line: 14 },
end: { character: 249, column: 8, line: 14 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b ~ .d"',
start: { character: 323, column: 1, line: 17 },
end: { character: 330, column: 8, line: 17 }
start: { character: 267, column: 1, line: 15 },
end: { character: 274, column: 8, line: 15 }
}
]
});

@ -7,6 +7,6 @@
.a.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .b ~ .c { color: green; }*/
/* (unused) .c ~ .d { color: green; }*/
/* (unused) .b ~ .d { color: green; }*/
/* (unused) .b ~ .c { color: red; }*/
/* (unused) .c ~ .d { color: red; }*/
/* (unused) .b ~ .d { color: red; }*/

@ -1,3 +1,3 @@
<div class="a svelte-xyz"></div>
<div class="b svelte-xyz"></div>
<div class="c svelte-xyz"></div>
<div class="e svelte-xyz"></div>

@ -1,6 +1,4 @@
<script>
let promise = Promise.resolve();
</script>
<style>
.a ~ .b { color: green; }
@ -12,14 +10,15 @@
.a ~ .e { color: green; }
/* no match */
.b ~ .c { color: green; }
.c ~ .d { color: green; }
.b ~ .d { color: green; }
.b ~ .c { color: red; }
.c ~ .d { color: red; }
.b ~ .d { color: red; }
</style>
<div class="a"></div>
{#await promise}
<!-- non-promise, so that something renders initially -->
{#await true}
<div class="b"></div>
{:then value}
<div class="c"></div>

@ -1,4 +1,4 @@
<div class="a svelte-xyz"></div>
<div class="d svelte-xyz"></div>
<div class="f svelte-xyz"></div>
<div class="b svelte-xyz"></div>
<div class="g svelte-xyz"></div>
<div class="h svelte-xyz"></div>

@ -1,7 +1,3 @@
<script>
let promise = Promise.resolve();
</script>
<style>
.a + .b { color: green; }
.a + .c { color: green; }
@ -20,21 +16,22 @@
<div class="a"></div>
{#await promise then value}
<!-- non-promise, so that something renders initially -->
{#await true then value}
<div class="b"></div>
{:catch error}
<div class="c"></div>
{/await}
{#await promise}
{#await true}
<div class="d"></div>
{:catch error}
<div class="e"></div>
{/await}
{#await promise}
{#await true}
<div class="f"></div>
{:then error}
{:then value}
<div class="g"></div>
{/await}

@ -5,26 +5,26 @@ export default test({
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".a + .e"',
start: { character: 242, column: 1, line: 14 },
end: { character: 249, column: 8, line: 14 }
start: { character: 188, column: 1, line: 10 },
end: { character: 195, column: 8, line: 10 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b + .c"',
start: { character: 269, column: 1, line: 15 },
end: { character: 276, column: 8, line: 15 }
start: { character: 213, column: 1, line: 11 },
end: { character: 220, column: 8, line: 11 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".c + .d"',
start: { character: 296, column: 1, line: 16 },
end: { character: 303, column: 8, line: 16 }
start: { character: 238, column: 1, line: 12 },
end: { character: 245, column: 8, line: 12 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b + .d"',
start: { character: 323, column: 1, line: 17 },
end: { character: 330, column: 8, line: 17 }
start: { character: 263, column: 1, line: 13 },
end: { character: 270, column: 8, line: 13 }
}
]
});

@ -6,7 +6,7 @@
.d.svelte-xyz + .e:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .a + .e { color: green; }*/
/* (unused) .b + .c { color: green; }*/
/* (unused) .c + .d { color: green; }*/
/* (unused) .b + .d { color: green; }*/
/* (unused) .a + .e { color: red; }*/
/* (unused) .b + .c { color: red; }*/
/* (unused) .c + .d { color: red; }*/
/* (unused) .b + .d { color: red; }*/

@ -1,3 +1,3 @@
<div class="a svelte-xyz"></div>
<div class="b svelte-xyz"></div>
<div class="c svelte-xyz"></div>
<div class="e svelte-xyz"></div>

@ -1,7 +1,3 @@
<script>
let promise = Promise.resolve();
</script>
<style>
.a + .b { color: green; }
.a + .c { color: green; }
@ -11,15 +7,16 @@
.d + .e { color: green; }
/* no match */
.a + .e { color: green; }
.b + .c { color: green; }
.c + .d { color: green; }
.b + .d { color: green; }
.a + .e { color: red; }
.b + .c { color: red; }
.c + .d { color: red; }
.b + .d { color: red; }
</style>
<div class="a"></div>
{#await promise}
<!-- non-promise, so that something renders initially -->
{#await true}
<div class="b"></div>
{:then value}
<div class="c"></div>

@ -7,10 +7,6 @@ export default test({
};
},
html: `
Waiting...
`,
async test({ assert, component, target }) {
await (component.thePromise = Promise.resolve({ func: 12345 }));

@ -13,11 +13,6 @@ export default test({
return { thePromise: deferred.promise };
},
html: `
<br />
<p>the promise is pending</p>
`,
async test({ assert, component, target }) {
deferred.resolve(42);
@ -27,6 +22,7 @@ export default test({
deferred = create_deferred();
component.thePromise = deferred.promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '<br /><p>the promise is pending</p>');

@ -2,10 +2,6 @@ import { test } from '../../test';
import { sleep } from './sleep.js';
export default test({
html: `
<p>loading...</p>
`,
test({ assert, target }) {
return sleep(50).then(() => {
assert.htmlEqual(

@ -13,10 +13,6 @@ export default test({
return { thePromise: deferred.promise, show: true };
},
html: `
<div><p>loading...</p></div>
`,
test({ assert, component, target }) {
deferred.resolve(42);

@ -19,10 +19,6 @@ export default test({
return { items };
},
html: `
<p>a title: loading...</p>
`,
test({ assert, target }) {
fulfil(42);

@ -1,7 +1,6 @@
import { test } from '../../test';
export default test({
html: 'Loading...',
async test({ assert, component, target }) {
await component.test();

@ -1,9 +1,8 @@
import { test } from '../../test';
export default test({
html: '<p>wait for it...</p>',
test({ assert, component, target }) {
return component.promise.then(async () => {
return component.promise.then(() => {
assert.htmlEqual(
target.innerHTML,
`

@ -7,6 +7,7 @@ export default test({
});
component.promise = promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '<p>wait for it...</p>');

@ -3,8 +3,6 @@ import { ok, test } from '../../test';
export default test({
async test({ assert, component, target }) {
assert.htmlEqual(target.innerHTML, 'Loading...');
await component.promise;
await Promise.resolve();
const span = target.querySelector('span');

@ -13,15 +13,11 @@ export default test({
return { thePromise: deferred.promise };
},
html: `
<div><p>loading...</p></div>
`,
test({ assert, component, target }) {
deferred.resolve(42);
return deferred.promise
.then(() => {
.then(async () => {
assert.htmlEqual(
target.innerHTML,
`
@ -32,6 +28,7 @@ export default test({
deferred = create_deferred();
component.thePromise = deferred.promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '<div><p>loading...</p></div>');

@ -15,10 +15,6 @@ export default test({
};
},
html: `
<p>loading...</p>
`,
test({ assert, component, target, window }) {
deferred.resolve(42);

@ -12,10 +12,6 @@ export default test({
return { show: true, thePromise };
},
html: `
<p>loading...</p>
`,
test({ assert, component, target }) {
fulfil(42);

@ -13,10 +13,6 @@ export default test({
return { thePromise: deferred.promise };
},
html: `
<p>loading...</p>
`,
async test({ assert, component, target }) {
deferred.resolve(42);
@ -25,6 +21,7 @@ export default test({
deferred = create_deferred();
component.thePromise = deferred.promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '<p>loading...</p>');
deferred.reject(new Error('something broke'));

@ -13,16 +13,11 @@ export default test({
return { thePromise: deferred.promise };
},
html: `
<p>loading...</p>
<p>loading...</p>
`,
test({ assert, component, target }) {
deferred.resolve(42);
return deferred.promise
.then(() => {
.then(async () => {
assert.htmlEqual(
target.innerHTML,
`
@ -34,6 +29,7 @@ export default test({
deferred = create_deferred();
component.thePromise = deferred.promise;
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,

@ -13,18 +13,17 @@ export default test({
return { thePromise: deferred.promise };
},
html: 'waiting',
test({ assert, component, target }) {
deferred.resolve(9000);
return deferred.promise
.then(() => {
.then(async () => {
assert.htmlEqual(target.innerHTML, 'resolved');
deferred = create_deferred();
component.thePromise = deferred.promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, 'waiting');

@ -12,10 +12,6 @@ export default test({
return { thePromise };
},
html: `
<p>loading...</p><p>true!</p>
`,
test({ assert, target }) {
fulfil(42);

@ -12,15 +12,11 @@ export default test({
return { promise };
},
html: `
<p>loading...</p>
`,
test({ assert, component, target }) {
fulfil(42);
return promise
.then(() => {
.then(async () => {
assert.htmlEqual(
target.innerHTML,
`
@ -33,6 +29,7 @@ export default test({
});
component.promise = promise;
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,

@ -13,15 +13,11 @@ export default test({
return { thePromise: deferred.promise };
},
html: `
<p>loading...</p>
`,
test({ assert, component, target }) {
deferred.resolve(42);
return deferred.promise
.then(() => {
.then(async () => {
assert.htmlEqual(
target.innerHTML,
`
@ -32,6 +28,7 @@ export default test({
deferred = create_deferred();
component.thePromise = deferred.promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '<p>loading...</p>');

@ -7,10 +7,6 @@ export default test({
};
},
html: `
loading...
`,
async test({ assert, component, target }) {
await (component.thePromise = Promise.resolve([1, 2, 3, 4, 5, 6, 7, 8]));

@ -7,10 +7,6 @@ export default test({
};
},
html: `
loading...
`,
async test({ assert, component, target }) {
await (component.thePromise = Promise.resolve([1, 2]));

@ -7,10 +7,6 @@ export default test({
};
},
html: `
loading...
`,
async test({ assert, component, target }) {
await (component.thePromise = Promise.resolve([10, 11, 12, 13, 14, 15]));

@ -7,10 +7,6 @@ export default test({
};
},
html: `
loading...
`,
async test({ assert, component, target }) {
await (component.thePromise = Promise.resolve({ error: 'error message' }));
assert.htmlEqual(

@ -12,10 +12,6 @@ export default test({
return { thePromise };
},
html: `
loading...
`,
async test({ assert, target }) {
fulfil([]);

@ -13,12 +13,6 @@ export default test({
return { thePromise: deferred.promise };
},
html: `
<br>
<br>
<p>the promise is pending</p>
`,
expect_unhandled_rejections: true,
async test({ assert, component, target }) {
deferred.resolve();
@ -39,6 +33,7 @@ export default test({
const local = (deferred = create_deferred());
component.thePromise = local.promise;
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,

@ -19,7 +19,7 @@ export default test({
deferred.resolve(42);
return deferred.promise
.then(() => {
.then(async () => {
assert.htmlEqual(
target.innerHTML,
`
@ -30,6 +30,7 @@ export default test({
deferred = create_deferred();
component.thePromise = deferred.promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '');

@ -11,6 +11,7 @@ export default test({
let promise = new Promise((ok) => (resolve = ok));
component.promise = promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, 'Loading...');
resolve(42);
@ -19,6 +20,7 @@ export default test({
promise = new Promise((ok, fail) => (reject = fail));
component.promise = promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, 'Loading...');
reject(99);
@ -27,6 +29,7 @@ export default test({
promise = new Promise((ok) => (resolve = ok));
component.promise = promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, 'Loading...');
resolve(1);

@ -8,10 +8,6 @@ export default test({
};
},
html: `
<div><p>loading...</p></div>
`,
async test({ assert, component, target }) {
await (component.thePromise = Promise.resolve({
value: 'success',

@ -8,10 +8,6 @@ export default test({
};
},
html: `
<div><p>loading...</p></div>
`,
async test({ assert, component, target }) {
await (component.thePromise = Promise.resolve(component.Component));

@ -13,21 +13,18 @@ export default test({
return { promise: deferred.promise };
},
html: `
<p>loading...</p>
`,
expect_unhandled_rejections: true,
test({ assert, component, target }) {
deferred.resolve(42);
return deferred.promise
.then(() => {
.then(async () => {
assert.htmlEqual(target.innerHTML, '<p>loaded</p>');
deferred = create_deferred();
component.promise = deferred.promise;
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '<p>loading...</p>');

@ -1,10 +1,6 @@
import { test } from '../../test';
export default test({
html: `
<p>...waiting</p>
`,
async test({ assert, component, target }) {
await component.promise;

@ -6,16 +6,19 @@ export default test({
const div = target.querySelector('div');
ok(div);
assert.equal(div.style.opacity, '0');
assert.equal(div.style.color, 'blue');
component.visible = false;
assert.equal(div.style.opacity, '1');
assert.equal(div.style.color, 'yellow');
// change param
raf.tick(1);
component.param = true;
component.visible = true;
assert.equal(div.style.opacity, '1');
assert.equal(div.style.color, 'red');
component.visible = false;
assert.equal(div.style.color, 'green');
}
});

@ -4,18 +4,18 @@
function getInParam() {
return {
duration: param ? 20 : 10,
css: t => {
return `opacity: ${t}`;
duration: 100,
css: (t) => {
return `color: ${param ? 'red' : 'blue'}`;
}
};
}
function getOutParam() {
return {
duration: param ? 15 : 5,
css: t => {
return `opacity: ${t}`;
duration: 100,
css: (t) => {
return `color: ${param ? 'green' : 'yellow'}`;
}
};
}

@ -0,0 +1,27 @@
import { test } from '../../test';
export default test({
get props() {
return { visible: false };
},
test({ assert, component, target, raf, logs }) {
component.visible = true;
const span = /** @type {HTMLSpanElement & { foo: number }} */ (target.querySelector('span'));
raf.tick(50);
assert.equal(span.foo, 0.5);
component.visible = false;
assert.equal(span.foo, 0.5);
raf.tick(75);
assert.equal(span.foo, 0.25);
component.visible = true;
raf.tick(100);
assert.equal(span.foo, 0.5);
assert.deepEqual(logs, ['transition']); // should only run once
}
});

@ -0,0 +1,18 @@
<script>
export let visible;
function foo(node) {
console.log('transition');
return {
duration: 100,
tick: (t) => {
node.foo = t;
}
};
}
</script>
{#if visible}
<span transition:foo>hello</span>
{/if}

@ -0,0 +1,31 @@
import { test } from '../../test';
export default test({
get props() {
return { visible: false };
},
test({ assert, component, target, raf, logs }) {
component.visible = true;
const span = /** @type {HTMLSpanElement & { foo: number, bar: number }} */ (
target.querySelector('span')
);
raf.tick(50);
assert.equal(span.foo, 0.5);
component.visible = false;
assert.equal(span.foo, 0.5);
raf.tick(75);
assert.equal(span.foo, 0.75);
assert.equal(span.bar, 0.75);
component.visible = true;
raf.tick(100);
assert.equal(span.foo, 0.25);
assert.equal(span.bar, 1);
assert.deepEqual(logs, ['in', 'out', 'in']);
}
});

@ -0,0 +1,29 @@
<script>
export let visible;
function foo(node) {
console.log('in');
return {
duration: 100,
tick: (t) => {
node.foo = t;
}
};
}
function bar(node) {
console.log('out');
return {
duration: 100,
tick: (t) => {
node.bar = t;
}
};
}
</script>
{#if visible}
<span in:foo out:bar>hello</span>
{/if}

@ -14,6 +14,7 @@ export default test({
intro: true,
async test({ assert, target, component, raf }) {
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '<p class="pending" foo="0.0">loading...</p>');
let time = 0;

@ -16,7 +16,8 @@ export default test({
intro: true,
test({ assert, target, raf }) {
async test({ assert, target, raf }) {
await Promise.resolve();
const p = /** @type {HTMLParagraphElement & { foo: number }} */ (target.querySelector('p'));
raf.tick(0);

@ -1,7 +1,7 @@
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
async test({ assert, target, logs, variant }) {
const [b1, b2, b3, b4] = target.querySelectorAll('button');
b1.click();
await Promise.resolve();
@ -16,6 +16,9 @@ export default test({
b4.click();
await Promise.resolve();
await Promise.resolve();
assert.deepEqual(logs, ['pending', 'a', 'b', 'c', 'pending']);
assert.deepEqual(
logs,
variant === 'hydrate' ? ['pending', 'a', 'b', 'c', 'pending'] : ['a', 'b', 'c', 'pending']
);
}
});

@ -1,7 +1,7 @@
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
async test({ assert, target, logs, variant }) {
const [b1, b2] = target.querySelectorAll('button');
b1.click();
await Promise.resolve();
@ -22,6 +22,11 @@ export default test({
`<p>then b</p><button>Show Promise A</button><button>Show Promise B</button>`
);
assert.deepEqual(logs, ['rendering pending block', 'rendering then block']);
assert.deepEqual(
logs,
variant === 'hydrate'
? ['rendering pending block', 'rendering then block']
: ['rendering then block']
);
}
});

@ -0,0 +1,5 @@
<script>
const { children } = $props();
</script>
<p>text before the render tag {@render children()}</p>

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
html: `<p>text before the render tag dont fuse this text with the one from the child</p>`
});

@ -0,0 +1,6 @@
<script>
import Child from './Child.svelte';
let text = $state('dont fuse this text with the one from the child');
</script>
<Child>{text}</Child>

@ -144,6 +144,38 @@ describe('signals', () => {
};
});
test('state reset', () => {
const log: number[] = [];
let count = source(0);
let double = derived(() => $.get(count) * 2);
effect(() => {
log.push($.get(double));
});
return () => {
flushSync();
log.length = 0;
set(count, 1);
set(count, 0);
flushSync();
assert.deepEqual(log, []);
set(count, 1);
$.get(double);
set(count, 0);
flushSync();
// TODO: in an ideal world, the effect wouldn't fire here
assert.deepEqual(log, [0]);
};
});
test('https://perf.js.hyoo.ru/#!bench=9h2as6_u0mfnn', () => {
let res: number[] = [];

@ -15,6 +15,8 @@ export default function Function_prop_no_getter($$anchor) {
onmouseup,
onmouseenter: () => $.set(count, $.proxy(plusOne($.get(count)))),
children: ($$anchor, $$slotProps) => {
$.next();
var text = $.text();
$.template_effect(() => $.set_text(text, `clicks: ${$.get(count) ?? ""}`));

@ -14,7 +14,7 @@ export default function Function_prop_no_getter($$payload) {
onmouseup,
onmouseenter: () => count = plusOne(count),
children: ($$payload, $$slotProps) => {
$$payload.out += `clicks: ${$.escape(count)}`;
$$payload.out += `<!---->clicks: ${$.escape(count)}`;
},
$$slots: { default: true }
});

@ -12,12 +12,14 @@ function Hmr($$anchor) {
if (import.meta.hot) {
const s = $.source(Hmr);
const filename = Hmr.filename;
const $$original = Hmr;
Hmr = $.hmr(s);
Hmr.filename = filename;
Hmr[$.ORIGINAL] = $$original;
import.meta.hot.accept((module) => {
$.set(s, module.default);
$.set(s, module.default[$.ORIGINAL]);
});
}

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
compileOptions: {
warningFilter: (warning) =>
!['a11y_missing_attribute', 'a11y_misplaced_scope'].includes(warning.code)
}
});

@ -0,0 +1,8 @@
<div>
<img src="this-is-fine.jpg" />
<marquee>but this is still discouraged</marquee>
</div>
<div scope></div>
<img src="potato.jpg" />

@ -0,0 +1,14 @@
[
{
"code": "a11y_distracting_elements",
"end": {
"column": 49,
"line": 3
},
"message": "Avoid `<marquee>` elements",
"start": {
"column": 1,
"line": 3
}
}
]

@ -864,12 +864,16 @@ declare module 'svelte/compiler' {
* Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically.
*/
filename?: string;
/**
* Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically.
* @default process.cwd() on node-like environments, undefined elsewhere
*/
rootDir?: string;
/**
* A function that gets a `Warning` as an argument and returns a boolean.
* Use this to filter out warnings. Return `true` to keep the warning, `false` to discard it.
*/
warningFilter?: (warning: Warning) => boolean;
}
type DeclarationKind =
@ -2673,12 +2677,16 @@ declare module 'svelte/types/compiler/interfaces' {
* Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically.
*/
filename?: string;
/**
* Used for ensuring filenames don't leak filesystem information. Your bundler plugin will set it automatically.
* @default process.cwd() on node-like environments, undefined elsewhere
*/
rootDir?: string;
/**
* A function that gets a `Warning` as an argument and returns a boolean.
* Use this to filter out warnings. Return `true` to keep the warning, `false` to discard it.
*/
warningFilter?: (warning: Warning_1) => boolean;
}
/**
* - `html` the default, for e.g. `<div>` or `<span>`

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save