Skip to content

Commit f1ca551

Browse files
authored
chore: fix SSR context (#16781)
1 parent 41f0ff4 commit f1ca551

File tree

6 files changed

+59
-72
lines changed

6 files changed

+59
-72
lines changed

packages/svelte/src/index-server.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
/** @import { Component } from '#server' */
2-
import { current_component } from './internal/server/context.js';
1+
/** @import { SSRContext } from '#server' */
2+
import { ssr_context } from './internal/server/context.js';
33
import { noop } from './internal/shared/utils.js';
44
import * as e from './internal/server/errors.js';
55

66
/** @param {() => void} fn */
77
export function onDestroy(fn) {
8-
var context = /** @type {Component} */ (current_component);
8+
var context = /** @type {SSRContext} */ (ssr_context);
99
(context.d ??= []).push(fn);
1010
}
1111

packages/svelte/src/internal/server/context.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
/** @import { Component } from '#server' */
1+
/** @import { SSRContext } from '#server' */
22
import { DEV } from 'esm-env';
33
import { async_on_destroy, on_destroy } from './index.js';
44
import * as e from './errors.js';
55

6-
/** @type {Component | null} */
7-
export var current_component = null;
6+
/** @type {SSRContext | null} */
7+
export var ssr_context = null;
8+
9+
/** @param {SSRContext | null} v */
10+
export function set_ssr_context(v) {
11+
ssr_context = v;
12+
}
813

914
/**
1015
* @template T
@@ -47,40 +52,41 @@ export function getAllContexts() {
4752
* @returns {Map<unknown, unknown>}
4853
*/
4954
function get_or_init_context_map(name) {
50-
if (current_component === null) {
55+
if (ssr_context === null) {
5156
e.lifecycle_outside_component(name);
5257
}
5358

54-
return (current_component.c ??= new Map(get_parent_context(current_component) || undefined));
59+
return (ssr_context.c ??= new Map(get_parent_context(ssr_context) || undefined));
5560
}
5661

5762
/**
5863
* @param {Function} [fn]
5964
*/
6065
export function push(fn) {
61-
current_component = { p: current_component, c: null, d: null };
66+
ssr_context = { p: ssr_context, c: null, d: null };
67+
6268
if (DEV) {
63-
// component function
64-
current_component.function = fn;
69+
ssr_context.function = fn;
70+
ssr_context.element = ssr_context.p?.element;
6571
}
6672
}
6773

6874
export function pop() {
69-
var component = /** @type {Component} */ (current_component);
75+
var context = /** @type {SSRContext} */ (ssr_context);
7076

71-
var ondestroy = component.d;
77+
var ondestroy = context.d;
7278

7379
if (ondestroy) {
7480
on_destroy.push(...ondestroy);
7581
// TODO this is probably actually broken
7682
async_on_destroy.push(...ondestroy);
7783
}
7884

79-
current_component = component.p;
85+
ssr_context = context.p;
8086
}
8187

8288
/**
83-
* @param {Component} component_context
89+
* @param {SSRContext} component_context
8490
* @returns {Map<unknown, unknown> | null}
8591
*/
8692
function get_parent_context(component_context) {
@@ -107,11 +113,11 @@ function get_parent_context(component_context) {
107113
* @returns {Promise<() => T>}
108114
*/
109115
export async function save(promise) {
110-
var previous_component = current_component;
116+
var previous_context = ssr_context;
111117
var value = await promise;
112118

113119
return () => {
114-
current_component = previous_component;
120+
ssr_context = previous_context;
115121
return value;
116122
};
117123
}

packages/svelte/src/internal/server/dev.js

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
1-
/** @import { Component } from '#server' */
1+
/** @import { SSRContext } from '#server' */
22
import { FILENAME } from '../../constants.js';
33
import {
44
is_tag_valid_with_ancestor,
55
is_tag_valid_with_parent
66
} from '../../html-tree-validation.js';
7-
import { current_component } from './context.js';
7+
import { set_ssr_context, ssr_context } from './context.js';
88
import * as e from './errors.js';
99
import { Payload } from './payload.js';
1010

11+
// TODO move this
1112
/**
1213
* @typedef {{
1314
* tag: string;
14-
* parent: null | Element;
15-
* filename: null | string;
15+
* parent: undefined | Element;
16+
* filename: undefined | string;
1617
* line: number;
1718
* column: number;
1819
* }} Element
1920
*/
2021

2122
/**
22-
* @type {Element | null}
23+
* This is exported so that it can be cleared between tests
24+
* @type {Set<string>}
2325
*/
24-
let parent = null;
25-
26-
/** @type {Set<string>} */
27-
let seen;
26+
export let seen;
2827

2928
/**
3029
* @param {Payload} payload
@@ -46,25 +45,19 @@ function print_error(payload, message) {
4645
);
4746
}
4847

49-
export function reset_elements() {
50-
let old_parent = parent;
51-
parent = null;
52-
return () => {
53-
parent = old_parent;
54-
};
55-
}
56-
5748
/**
5849
* @param {Payload} payload
5950
* @param {string} tag
6051
* @param {number} line
6152
* @param {number} column
6253
*/
6354
export function push_element(payload, tag, line, column) {
64-
var filename = /** @type {Component} */ (current_component).function[FILENAME];
65-
var child = { tag, parent, filename, line, column };
55+
var context = /** @type {SSRContext} */ (ssr_context);
56+
var filename = context.function[FILENAME];
57+
var parent = context.element;
58+
var element = { tag, parent, filename, line, column };
6659

67-
if (parent !== null) {
60+
if (parent !== undefined) {
6861
var ancestor = parent.parent;
6962
var ancestors = [parent.tag];
7063

@@ -89,11 +82,11 @@ export function push_element(payload, tag, line, column) {
8982
}
9083
}
9184

92-
parent = child;
85+
set_ssr_context({ ...context, p: context, element });
9386
}
9487

9588
export function pop_element() {
96-
parent = /** @type {Element} */ (parent)?.parent;
89+
set_ssr_context(ssr_context?.p ?? null);
9790
}
9891

9992
/**

packages/svelte/src/internal/server/index.js

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** @import { ComponentType, SvelteComponent } from 'svelte' */
2-
/** @import { Component, RenderOutput } from '#server' */
2+
/** @import { RenderOutput, SSRContext } from '#server' */
33
/** @import { Store } from '#shared' */
44
/** @import { AccumulatedContent } from './payload.js' */
55
export { FILENAME, HMR } from '../../constants.js';
@@ -14,11 +14,10 @@ import {
1414
} from '../../constants.js';
1515
import { escape_html } from '../../escaping.js';
1616
import { DEV } from 'esm-env';
17-
import { current_component, pop, push } from './context.js';
17+
import { ssr_context, pop, push, set_ssr_context } from './context.js';
1818
import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN, BLOCK_OPEN_ELSE } from './hydration.js';
1919
import { validate_store } from '../shared/validate.js';
2020
import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js';
21-
import { reset_elements } from './dev.js';
2221
import { Payload, TreeState } from './payload.js';
2322
import { abort } from './abort-signal.js';
2423

@@ -69,6 +68,8 @@ export let on_destroy = [];
6968
* @returns {RenderOutput}
7069
*/
7170
export function render(component, options = {}) {
71+
var previous_context = ssr_context;
72+
7273
try {
7374
const payload = new Payload(
7475
new TreeState('sync', options.idPrefix ? options.idPrefix + '-' : '')
@@ -78,16 +79,9 @@ export function render(component, options = {}) {
7879
on_destroy = [];
7980
payload.push(BLOCK_OPEN);
8081

81-
let reset_reset_element;
82-
83-
if (DEV) {
84-
// prevent parent/child element state being corrupted by a bad render
85-
reset_reset_element = reset_elements();
86-
}
87-
8882
if (options.context) {
8983
push();
90-
/** @type {Component} */ (current_component).c = options.context;
84+
/** @type {SSRContext} */ (ssr_context).c = options.context;
9185
}
9286

9387
// @ts-expect-error
@@ -97,10 +91,6 @@ export function render(component, options = {}) {
9791
pop();
9892
}
9993

100-
if (reset_reset_element) {
101-
reset_reset_element();
102-
}
103-
10494
payload.push(BLOCK_CLOSE);
10595
for (const cleanup of on_destroy) cleanup();
10696
on_destroy = prev_on_destroy;
@@ -121,6 +111,7 @@ export function render(component, options = {}) {
121111
};
122112
} finally {
123113
abort();
114+
set_ssr_context(previous_context);
124115
}
125116
}
126117

@@ -140,6 +131,8 @@ export let async_on_destroy = [];
140131
* @returns {Promise<RenderOutput>}
141132
*/
142133
export async function render_async(component, options = {}) {
134+
var previous_context = ssr_context;
135+
143136
try {
144137
const payload = new Payload(
145138
new TreeState('async', options.idPrefix ? options.idPrefix + '-' : '')
@@ -149,16 +142,9 @@ export async function render_async(component, options = {}) {
149142
async_on_destroy = [];
150143
payload.push(BLOCK_OPEN);
151144

152-
let reset_reset_element;
153-
154-
if (DEV) {
155-
// prevent parent/child element state being corrupted by a bad render
156-
reset_reset_element = reset_elements();
157-
}
158-
159145
if (options.context) {
160146
push();
161-
/** @type {Component} */ (current_component).c = options.context;
147+
/** @type {SSRContext} */ (ssr_context).c = options.context;
162148
}
163149

164150
// @ts-expect-error
@@ -168,10 +154,6 @@ export async function render_async(component, options = {}) {
168154
pop();
169155
}
170156

171-
if (reset_reset_element) {
172-
reset_reset_element();
173-
}
174-
175157
payload.push(BLOCK_CLOSE);
176158
for (const cleanup of async_on_destroy) cleanup();
177159
async_on_destroy = prev_on_destroy;
@@ -192,6 +174,7 @@ export async function render_async(component, options = {}) {
192174
};
193175
} finally {
194176
abort();
177+
set_ssr_context(previous_context);
195178
}
196179
}
197180

packages/svelte/src/internal/server/types.d.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
export interface Component {
1+
import type { Element } from './dev';
2+
3+
export interface SSRContext {
24
/** parent */
3-
p: null | Component;
4-
/** context */
5+
p: null | SSRContext;
6+
/** component context */
57
c: null | Map<unknown, unknown>;
68
/** ondestroy */
79
d: null | Array<() => void>;
8-
/**
9-
* dev mode only: the component function
10-
*/
10+
/** dev mode only: the current component function */
1111
function?: any;
12+
/** dev mode only: the current element */
13+
element?: Element;
1214
}
1315

1416
export interface RenderOutput {

packages/svelte/tests/server-side-rendering/test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { compile_directory, should_update_expected, try_read_file } from '../hel
1111
import { assert_html_equal_with_options } from '../html_equal.js';
1212
import { suite_with_variants, type BaseTest } from '../suite.js';
1313
import type { CompileOptions } from '#compiler';
14+
import { seen } from '../../src/internal/server/dev.js';
1415

1516
interface SSRTest extends BaseTest {
1617
mode?: ('sync' | 'async')[];
@@ -69,6 +70,8 @@ const { test, run } = suite_with_variants<SSRTest, 'sync' | 'async', CompileOpti
6970
const expected_html = try_read_file(`${test_dir}/_expected.html`);
7071
const is_async = variant === 'async';
7172

73+
seen?.clear();
74+
7275
let rendered;
7376
try {
7477
rendered = is_async

0 commit comments

Comments
 (0)