This commit is contained in:
Christoph Haas 2026-03-21 22:41:23 +01:00
commit f4be03ceba
1826 changed files with 830924 additions and 0 deletions

View file

@ -0,0 +1,281 @@
/** @import { ComponentConstructorOptions, ComponentType, SvelteComponent, Component } from 'svelte' */
import { DIRTY, LEGACY_PROPS, MAYBE_DIRTY } from '../internal/client/constants.js';
import { user_pre_effect } from '../internal/client/reactivity/effects.js';
import { mutable_source, set } from '../internal/client/reactivity/sources.js';
import { hydrate, mount, unmount } from '../internal/client/render.js';
import { active_effect, get } from '../internal/client/runtime.js';
import { flushSync } from '../internal/client/reactivity/batch.js';
import { define_property, is_array } from '../internal/shared/utils.js';
import * as e from '../internal/client/errors.js';
import * as w from '../internal/client/warnings.js';
import { DEV } from 'esm-env';
import { FILENAME } from '../constants.js';
import { component_context, dev_current_component_function } from '../internal/client/context.js';
import { async_mode_flag } from '../internal/flags/index.js';
import { set_signal_status } from '../internal/client/reactivity/status.js';
/**
* Takes the same options as a Svelte 4 component and the component function and returns a Svelte 4 compatible component.
*
* @deprecated Use this only as a temporary solution to migrate your imperative component code to Svelte 5.
*
* @template {Record<string, any>} Props
* @template {Record<string, any>} Exports
* @template {Record<string, any>} Events
* @template {Record<string, any>} Slots
*
* @param {ComponentConstructorOptions<Props> & {
* component: ComponentType<SvelteComponent<Props, Events, Slots>> | Component<Props>;
* }} options
* @returns {SvelteComponent<Props, Events, Slots> & Exports}
*/
export function createClassComponent(options) {
// @ts-expect-error $$prop_def etc are not actually defined
return new Svelte4Component(options);
}
/**
* Takes the component function and returns a Svelte 4 compatible component constructor.
*
* @deprecated Use this only as a temporary solution to migrate your imperative component code to Svelte 5.
*
* @template {Record<string, any>} Props
* @template {Record<string, any>} Exports
* @template {Record<string, any>} Events
* @template {Record<string, any>} Slots
*
* @param {SvelteComponent<Props, Events, Slots> | Component<Props>} component
* @returns {ComponentType<SvelteComponent<Props, Events, Slots> & Exports>}
*/
export function asClassComponent(component) {
// @ts-expect-error $$prop_def etc are not actually defined
return class extends Svelte4Component {
/** @param {any} options */
constructor(options) {
super({
component,
...options
});
}
};
}
/**
* Support using the component as both a class and function during the transition period
* @typedef {{new (o: ComponentConstructorOptions): SvelteComponent;(...args: Parameters<Component<Record<string, any>>>): ReturnType<Component<Record<string, any>, Record<string, any>>>;}} LegacyComponentType
*/
class Svelte4Component {
/** @type {any} */
#events;
/** @type {Record<string, any>} */
#instance;
/**
* @param {ComponentConstructorOptions & {
* component: any;
* }} options
*/
constructor(options) {
var sources = new Map();
/**
* @param {string | symbol} key
* @param {unknown} value
*/
var add_source = (key, value) => {
var s = mutable_source(value, false, false);
sources.set(key, s);
return s;
};
// Replicate coarse-grained props through a proxy that has a version source for
// each property, which is incremented on updates to the property itself. Do not
// use our $state proxy because that one has fine-grained reactivity.
const props = new Proxy(
{ ...(options.props || {}), $$events: {} },
{
get(target, prop) {
return get(sources.get(prop) ?? add_source(prop, Reflect.get(target, prop)));
},
has(target, prop) {
// Necessary to not throw "invalid binding" validation errors on the component side
if (prop === LEGACY_PROPS) return true;
get(sources.get(prop) ?? add_source(prop, Reflect.get(target, prop)));
return Reflect.has(target, prop);
},
set(target, prop, value) {
set(sources.get(prop) ?? add_source(prop, value), value);
return Reflect.set(target, prop, value);
}
}
);
this.#instance = (options.hydrate ? hydrate : mount)(options.component, {
target: options.target,
anchor: options.anchor,
props,
context: options.context,
intro: options.intro ?? false,
recover: options.recover,
transformError: options.transformError
});
// We don't flushSync for custom element wrappers or if the user doesn't want it,
// or if we're in async mode since `flushSync()` will fail
if (!async_mode_flag && (!options?.props?.$$host || options.sync === false)) {
flushSync();
}
this.#events = props.$$events;
for (const key of Object.keys(this.#instance)) {
if (key === '$set' || key === '$destroy' || key === '$on') continue;
define_property(this, key, {
get() {
return this.#instance[key];
},
/** @param {any} value */
set(value) {
this.#instance[key] = value;
},
enumerable: true
});
}
this.#instance.$set = /** @param {Record<string, any>} next */ (next) => {
Object.assign(props, next);
};
this.#instance.$destroy = () => {
unmount(this.#instance);
};
}
/** @param {Record<string, any>} props */
$set(props) {
this.#instance.$set(props);
}
/**
* @param {string} event
* @param {(...args: any[]) => any} callback
* @returns {any}
*/
$on(event, callback) {
this.#events[event] = this.#events[event] || [];
/** @param {any[]} args */
const cb = (...args) => callback.call(this, ...args);
this.#events[event].push(cb);
return () => {
this.#events[event] = this.#events[event].filter(/** @param {any} fn */ (fn) => fn !== cb);
};
}
$destroy() {
this.#instance.$destroy();
}
}
/**
* Runs the given function once immediately on the server, and works like `$effect.pre` on the client.
*
* @deprecated Use this only as a temporary solution to migrate your component code to Svelte 5.
* @param {() => void | (() => void)} fn
* @returns {void}
*/
export function run(fn) {
user_pre_effect(() => {
fn();
var effect = /** @type {import('#client').Effect} */ (active_effect);
// If the effect is immediately made dirty again, mark it as maybe dirty to emulate legacy behaviour
if ((effect.f & DIRTY) !== 0) {
let filename = "a file (we can't know which one)";
if (DEV) {
// @ts-ignore
filename = dev_current_component_function?.[FILENAME] ?? filename;
}
w.legacy_recursive_reactive_block(filename);
set_signal_status(effect, MAYBE_DIRTY);
}
});
}
/**
* Function to mimic the multiple listeners available in svelte 4
* @deprecated
* @param {EventListener[]} handlers
* @returns {EventListener}
*/
export function handlers(...handlers) {
return function (event) {
const { stopImmediatePropagation } = event;
let stopped = false;
event.stopImmediatePropagation = () => {
stopped = true;
stopImmediatePropagation.call(event);
};
const errors = [];
for (const handler of handlers) {
try {
// @ts-expect-error `this` is not typed
handler?.call(this, event);
} catch (e) {
errors.push(e);
}
if (stopped) {
break;
}
}
for (let error of errors) {
queueMicrotask(() => {
throw error;
});
}
};
}
/**
* Function to create a `bubble` function that mimic the behavior of `on:click` without handler available in svelte 4.
* @deprecated Use this only as a temporary solution to migrate your automatically delegated events in Svelte 5.
*/
export function createBubbler() {
const active_component_context = component_context;
if (active_component_context === null) {
e.lifecycle_outside_component('createBubbler');
}
return (/**@type {string}*/ type) => (/**@type {Event}*/ event) => {
const events = /** @type {Record<string, Function | Function[]>} */ (
active_component_context.s.$$events
)?.[/** @type {any} */ (type)];
if (events) {
const callbacks = is_array(events) ? events.slice() : [events];
for (const fn of callbacks) {
fn.call(active_component_context.x, event);
}
return !event.defaultPrevented;
}
return true;
};
}
export {
once,
preventDefault,
self,
stopImmediatePropagation,
stopPropagation,
trusted,
passive,
nonpassive
} from '../internal/client/dom/legacy/event-modifiers.js';

View file

@ -0,0 +1,112 @@
/** @import { SvelteComponent } from '../index.js' */
/** @import { Csp } from '#server' */
import { asClassComponent as as_class_component, createClassComponent } from './legacy-client.js';
import { render } from '../internal/server/index.js';
import { async_mode_flag } from '../internal/flags/index.js';
// By having this as a separate entry point for server environments, we save the client bundle from having to include the server runtime
export { createClassComponent };
/** @typedef {{ head: string, html: string, css: { code: string, map: null }; hashes?: { script: `sha256-${string}`[] } }} LegacyRenderResult */
/**
* Takes a Svelte 5 component and returns a Svelte 4 compatible component constructor.
*
* @deprecated Use this only as a temporary solution to migrate your imperative component code to Svelte 5.
*
* @template {Record<string, any>} Props
* @template {Record<string, any>} Exports
* @template {Record<string, any>} Events
* @template {Record<string, any>} Slots
*
* @param {SvelteComponent<Props, Events, Slots>} component
* @returns {typeof SvelteComponent<Props, Events, Slots> & Exports}
*/
export function asClassComponent(component) {
const component_constructor = as_class_component(component);
/** @type {(props?: {}, opts?: { $$slots?: {}; context?: Map<any, any>; csp?: Csp; transformError?: (error: unknown) => unknown }) => LegacyRenderResult & PromiseLike<LegacyRenderResult> } */
const _render = (props, { context, csp, transformError } = {}) => {
// @ts-expect-error the typings are off, but this will work if the component is compiled in SSR mode
const result = render(component, { props, context, csp, transformError });
const munged = Object.defineProperties(
/** @type {LegacyRenderResult & PromiseLike<LegacyRenderResult>} */ ({}),
{
css: {
value: { code: '', map: null }
},
head: {
get: () => result.head
},
html: {
get: () => result.body
},
then: {
/**
* this is not type-safe, but honestly it's the best I can do right now, and it's a straightforward function.
*
* @template TResult1
* @template [TResult2=never]
* @param { (value: LegacyRenderResult) => TResult1 } onfulfilled
* @param { (reason: unknown) => TResult2 } onrejected
*/
value: (onfulfilled, onrejected) => {
if (!async_mode_flag) {
const user_result = onfulfilled({
css: munged.css,
head: munged.head,
html: munged.html
});
return Promise.resolve(user_result);
}
return result.then((result) => {
return onfulfilled({
css: munged.css,
head: result.head,
html: result.body,
hashes: result.hashes
});
}, onrejected);
}
}
}
);
return munged;
};
// @ts-expect-error this is present for SSR
component_constructor.render = _render;
// @ts-ignore
return component_constructor;
}
/**
* Runs the given function once immediately on the server, and works like `$effect.pre` on the client.
*
* @deprecated Use this only as a temporary solution to migrate your component code to Svelte 5.
* @param {() => void | (() => void)} fn
* @returns {void}
*/
export function run(fn) {
fn();
}
const noop = () => {};
// event stuff, no need to worry about it for SSR but needs to be there or it will crash
export {
noop as handlers,
noop as createBubbler,
noop as once,
noop as preventDefault,
noop as self,
noop as stopImmediatePropagation,
noop as stopPropagation,
noop as trusted,
noop as passive,
noop as nonpassive
};