Skip to content

Commit 73d10e7

Browse files
committed
wip
2 parents 3ea7924 + 2703ac6 commit 73d10e7

File tree

13 files changed

+168
-35
lines changed

13 files changed

+168
-35
lines changed

.changeset/long-moles-join.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: ensure tracking returns true, even if in unowned

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,15 @@ export function build_component(node, component_name, context, anchor = context.
176176
// When we have a non-simple computation, anything other than an Identifier or Member expression,
177177
// then there's a good chance it needs to be memoized to avoid over-firing when read within the
178178
// child component (e.g. `active={i === index}`)
179-
const should_wrap_in_derived = get_attribute_chunks(attribute.value).some((n) => {
180-
return (
181-
n.type === 'ExpressionTag' &&
182-
n.expression.type !== 'Identifier' &&
183-
(n.expression.type !== 'MemberExpression' ||
184-
n.expression.object.type === 'AwaitExpression')
185-
);
186-
});
179+
const should_wrap_in_derived =
180+
metadata.is_async ||
181+
get_attribute_chunks(attribute.value).some((n) => {
182+
return (
183+
n.type === 'ExpressionTag' &&
184+
n.expression.type !== 'Identifier' &&
185+
n.expression.type !== 'MemberExpression'
186+
);
187+
});
187188

188189
return should_wrap_in_derived
189190
? b.call(

packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitExpression.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,17 @@ import * as b from '../../../../utils/builders.js';
77
* @param {Context} context
88
*/
99
export function AwaitExpression(node, context) {
10-
if (context.state.scope.function_depth > 1) {
10+
// if `await` is inside a function, or inside `<script module>`,
11+
// allow it, otherwise error
12+
if (
13+
context.state.scope.function_depth === 0 ||
14+
context.path.some(
15+
(node) =>
16+
node.type === 'ArrowFunctionExpression' ||
17+
node.type === 'FunctionDeclaration' ||
18+
node.type === 'FunctionExpression'
19+
)
20+
) {
1121
return context.next();
1222
}
1323

packages/svelte/src/internal/client/dom/blocks/each.js

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
142142
var boundary = find_boundary(active_effect);
143143

144144
/** @type {Map<any, EachItem>} */
145-
var pending_items = new Map();
145+
var offscreen_items = new Map();
146146

147147
// TODO: ideally we could use derived for runes mode but because of the ability
148148
// to use a store which can be mutated, we can't do that here as mutating a store
@@ -164,7 +164,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
164164
each_effect,
165165
array,
166166
state,
167-
pending_items,
167+
offscreen_items,
168168
anchor,
169169
render_fn,
170170
flags,
@@ -275,7 +275,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
275275
value = array[i];
276276
key = get_key(value, i);
277277

278-
var existing = state.items.get(key) ?? pending_items.get(key);
278+
var existing = state.items.get(key) ?? offscreen_items.get(key);
279279

280280
if (existing) {
281281
// update before reconciliation, to trigger any async updates
@@ -297,7 +297,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
297297
true
298298
);
299299

300-
pending_items.set(key, item);
300+
offscreen_items.set(key, item);
301301
}
302302
}
303303

@@ -332,7 +332,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
332332
* @param {Effect} each_effect
333333
* @param {Array<V>} array
334334
* @param {EachState} state
335-
* @param {Map<any, EachItem>} pending_items
335+
* @param {Map<any, EachItem>} offscreen_items
336336
* @param {Element | Comment | Text} anchor
337337
* @param {(anchor: Node, item: MaybeSource<V>, index: number | Source<number>, collection: () => V[]) => void} render_fn
338338
* @param {number} flags
@@ -344,7 +344,7 @@ function reconcile(
344344
each_effect,
345345
array,
346346
state,
347-
pending_items,
347+
offscreen_items,
348348
anchor,
349349
render_fn,
350350
flags,
@@ -406,10 +406,10 @@ function reconcile(
406406
item = items.get(key);
407407

408408
if (item === undefined) {
409-
var pending = pending_items.get(key);
409+
var pending = offscreen_items.get(key);
410410

411411
if (pending !== undefined) {
412-
pending_items.delete(key);
412+
offscreen_items.delete(key);
413413
items.set(key, pending);
414414

415415
var next = prev && prev.next;
@@ -566,18 +566,14 @@ function reconcile(
566566
});
567567
}
568568

569-
// TODO this seems super weird... should be `each_effect`, but that doesn't seem to work?
570-
// if (active_effect !== null) {
571-
// active_effect.first = state.first && state.first.e;
572-
// active_effect.last = prev && prev.e;
573-
// }
574-
575569
each_effect.first = state.first && state.first.e;
576570
each_effect.last = prev && prev.e;
577571

578-
for (var unused of pending_items.values()) {
572+
for (var unused of offscreen_items.values()) {
579573
destroy_effect(unused.e);
580574
}
575+
576+
offscreen_items.clear();
581577
}
582578

583579
/**

packages/svelte/src/internal/client/reactivity/effects.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,7 @@ function create_effect(type, fn, sync, push = true) {
169169
* @returns {boolean}
170170
*/
171171
export function effect_tracking() {
172-
if (active_reaction === null || untracking) {
173-
return false;
174-
}
175-
176-
// If it's skipped, that's because we're inside an unowned
177-
// that is not being tracked by another reaction
178-
return !skip_reaction;
172+
return active_reaction !== null && !untracking;
179173
}
180174

181175
/**
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target, logs }) {
6+
const b1 = target.querySelector('button');
7+
8+
b1?.click();
9+
flushSync();
10+
11+
assert.htmlEqual(
12+
target.innerHTML,
13+
`<o>Store: new</o><p>Text: new message</p><button>Change Store</button>`
14+
);
15+
}
16+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
import { writable, fromStore, toStore } from "svelte/store";
3+
4+
const store = writable("previous");
5+
let text = $derived(fromStore(store).current + " message");
6+
7+
text; // read derived in a non-tracking context
8+
</script>
9+
10+
<o>Store: {$store}</o>
11+
<p>Text: {text}</p>
12+
<button onclick={() => { store.set("new"); }}>Change Store</button>

playgrounds/sandbox/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<script type="module">
1414
import { mount, hydrate, unmount } from 'svelte';
15-
import App from '/src/main.svelte';
15+
import App from '/src/App.svelte';
1616

1717
const root = document.getElementById('root');
1818
const render = root.firstChild?.nextSibling ? hydrate : mount;

playgrounds/sandbox/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
"ssr": "node --conditions=development ./ssr-dev.js",
1010
"build": "vite build --outDir dist/client && vite build --outDir dist/server --ssr ssr-prod.js",
1111
"prod": "npm run build && node dist/server/ssr-prod",
12-
"preview": "vite preview"
12+
"preview": "vite preview",
13+
"download": "node scripts/download.js",
14+
"hash": "node scripts/hash.js | pbcopy && echo \"copied URL to clipboard\""
1315
},
1416
"devDependencies": {
1517
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import fs from 'node:fs';
2+
3+
const arg = process.argv[2];
4+
5+
/** @type {URL} */
6+
let url;
7+
8+
try {
9+
url = new URL(arg);
10+
} catch (e) {
11+
console.error(`${arg} is not a URL`);
12+
process.exit(1);
13+
}
14+
15+
if (url.origin !== 'https://svelte.dev' || !url.pathname.startsWith('/playground/')) {
16+
console.error(`${arg} is not a Svelte playground URL`);
17+
process.exit(1);
18+
}
19+
20+
let files;
21+
22+
if (url.hash.length > 1) {
23+
const decoded = atob(url.hash.slice(1).replaceAll('-', '+').replaceAll('_', '/'));
24+
// putting it directly into the blob gives a corrupted file
25+
const u8 = new Uint8Array(decoded.length);
26+
for (let i = 0; i < decoded.length; i++) {
27+
u8[i] = decoded.charCodeAt(i);
28+
}
29+
const stream = new Blob([u8]).stream().pipeThrough(new DecompressionStream('gzip'));
30+
const json = await new Response(stream).text();
31+
32+
files = JSON.parse(json).files;
33+
} else {
34+
const id = url.pathname.split('/')[2];
35+
const response = await fetch(`https://svelte.dev/playground/api/${id}.json`);
36+
37+
files = (await response.json()).components.map((data) => {
38+
const basename = `${data.name}.${data.type}`;
39+
40+
return {
41+
type: 'file',
42+
name: basename,
43+
basename,
44+
contents: data.source,
45+
text: true
46+
};
47+
});
48+
}
49+
50+
for (const file of files) {
51+
fs.writeFileSync(`src/${file.name}`, file.contents);
52+
}

0 commit comments

Comments
 (0)