aa-coordination-resource
Dominic Gannaway 7 months ago
commit 73d10e7dfb

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure tracking returns true, even if in unowned

@ -176,14 +176,15 @@ export function build_component(node, component_name, context, anchor = context.
// When we have a non-simple computation, anything other than an Identifier or Member expression,
// then there's a good chance it needs to be memoized to avoid over-firing when read within the
// child component (e.g. `active={i === index}`)
const should_wrap_in_derived = get_attribute_chunks(attribute.value).some((n) => {
return (
n.type === 'ExpressionTag' &&
n.expression.type !== 'Identifier' &&
(n.expression.type !== 'MemberExpression' ||
n.expression.object.type === 'AwaitExpression')
);
});
const should_wrap_in_derived =
metadata.is_async ||
get_attribute_chunks(attribute.value).some((n) => {
return (
n.type === 'ExpressionTag' &&
n.expression.type !== 'Identifier' &&
n.expression.type !== 'MemberExpression'
);
});
return should_wrap_in_derived
? b.call(

@ -7,7 +7,17 @@ import * as b from '../../../../utils/builders.js';
* @param {Context} context
*/
export function AwaitExpression(node, context) {
if (context.state.scope.function_depth > 1) {
// if `await` is inside a function, or inside `<script module>`,
// allow it, otherwise error
if (
context.state.scope.function_depth === 0 ||
context.path.some(
(node) =>
node.type === 'ArrowFunctionExpression' ||
node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression'
)
) {
return context.next();
}

@ -142,7 +142,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
var boundary = find_boundary(active_effect);
/** @type {Map<any, EachItem>} */
var pending_items = new Map();
var offscreen_items = new Map();
// TODO: ideally we could use derived for runes mode but because of the ability
// 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
each_effect,
array,
state,
pending_items,
offscreen_items,
anchor,
render_fn,
flags,
@ -275,7 +275,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
value = array[i];
key = get_key(value, i);
var existing = state.items.get(key) ?? pending_items.get(key);
var existing = state.items.get(key) ?? offscreen_items.get(key);
if (existing) {
// 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
true
);
pending_items.set(key, item);
offscreen_items.set(key, item);
}
}
@ -332,7 +332,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
* @param {Effect} each_effect
* @param {Array<V>} array
* @param {EachState} state
* @param {Map<any, EachItem>} pending_items
* @param {Map<any, EachItem>} offscreen_items
* @param {Element | Comment | Text} anchor
* @param {(anchor: Node, item: MaybeSource<V>, index: number | Source<number>, collection: () => V[]) => void} render_fn
* @param {number} flags
@ -344,7 +344,7 @@ function reconcile(
each_effect,
array,
state,
pending_items,
offscreen_items,
anchor,
render_fn,
flags,
@ -406,10 +406,10 @@ function reconcile(
item = items.get(key);
if (item === undefined) {
var pending = pending_items.get(key);
var pending = offscreen_items.get(key);
if (pending !== undefined) {
pending_items.delete(key);
offscreen_items.delete(key);
items.set(key, pending);
var next = prev && prev.next;
@ -566,18 +566,14 @@ function reconcile(
});
}
// TODO this seems super weird... should be `each_effect`, but that doesn't seem to work?
// if (active_effect !== null) {
// active_effect.first = state.first && state.first.e;
// active_effect.last = prev && prev.e;
// }
each_effect.first = state.first && state.first.e;
each_effect.last = prev && prev.e;
for (var unused of pending_items.values()) {
for (var unused of offscreen_items.values()) {
destroy_effect(unused.e);
}
offscreen_items.clear();
}
/**

@ -169,13 +169,7 @@ function create_effect(type, fn, sync, push = true) {
* @returns {boolean}
*/
export function effect_tracking() {
if (active_reaction === null || untracking) {
return false;
}
// If it's skipped, that's because we're inside an unowned
// that is not being tracked by another reaction
return !skip_reaction;
return active_reaction !== null && !untracking;
}
/**

@ -0,0 +1,16 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
const b1 = target.querySelector('button');
b1?.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`<o>Store: new</o><p>Text: new message</p><button>Change Store</button>`
);
}
});

@ -0,0 +1,12 @@
<script>
import { writable, fromStore, toStore } from "svelte/store";
const store = writable("previous");
let text = $derived(fromStore(store).current + " message");
text; // read derived in a non-tracking context
</script>
<o>Store: {$store}</o>
<p>Text: {text}</p>
<button onclick={() => { store.set("new"); }}>Change Store</button>

@ -12,7 +12,7 @@
<script type="module">
import { mount, hydrate, unmount } from 'svelte';
import App from '/src/main.svelte';
import App from '/src/App.svelte';
const root = document.getElementById('root');
const render = root.firstChild?.nextSibling ? hydrate : mount;

@ -9,7 +9,9 @@
"ssr": "node --conditions=development ./ssr-dev.js",
"build": "vite build --outDir dist/client && vite build --outDir dist/server --ssr ssr-prod.js",
"prod": "npm run build && node dist/server/ssr-prod",
"preview": "vite preview"
"preview": "vite preview",
"download": "node scripts/download.js",
"hash": "node scripts/hash.js | pbcopy && echo \"copied URL to clipboard\""
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",

@ -0,0 +1,52 @@
import fs from 'node:fs';
const arg = process.argv[2];
/** @type {URL} */
let url;
try {
url = new URL(arg);
} catch (e) {
console.error(`${arg} is not a URL`);
process.exit(1);
}
if (url.origin !== 'https://svelte.dev' || !url.pathname.startsWith('/playground/')) {
console.error(`${arg} is not a Svelte playground URL`);
process.exit(1);
}
let files;
if (url.hash.length > 1) {
const decoded = atob(url.hash.slice(1).replaceAll('-', '+').replaceAll('_', '/'));
// putting it directly into the blob gives a corrupted file
const u8 = new Uint8Array(decoded.length);
for (let i = 0; i < decoded.length; i++) {
u8[i] = decoded.charCodeAt(i);
}
const stream = new Blob([u8]).stream().pipeThrough(new DecompressionStream('gzip'));
const json = await new Response(stream).text();
files = JSON.parse(json).files;
} else {
const id = url.pathname.split('/')[2];
const response = await fetch(`https://svelte.dev/playground/api/${id}.json`);
files = (await response.json()).components.map((data) => {
const basename = `${data.name}.${data.type}`;
return {
type: 'file',
name: basename,
basename,
contents: data.source,
text: true
};
});
}
for (const file of files) {
fs.writeFileSync(`src/${file.name}`, file.contents);
}

@ -0,0 +1,45 @@
import fs from 'node:fs';
const files = [];
for (const basename of fs.readdirSync('src')) {
if (fs.statSync(`src/${basename}`).isDirectory()) continue;
files.push({
type: 'file',
name: basename,
basename,
contents: fs.readFileSync(`src/${basename}`, 'utf-8'),
text: true // TODO might not be
});
}
const payload = JSON.stringify({
name: 'sandbox',
files
});
async function compress(payload) {
const reader = new Blob([payload])
.stream()
.pipeThrough(new CompressionStream('gzip'))
.getReader();
let buffer = '';
for (;;) {
const { done, value } = await reader.read();
if (done) {
reader.releaseLock();
return btoa(buffer).replaceAll('+', '-').replaceAll('/', '_');
} else {
for (let i = 0; i < value.length; i++) {
// decoding as utf-8 will make btoa reject the string
buffer += String.fromCharCode(value[i]);
}
}
}
}
const hash = await compress(payload);
console.log(`https://svelte.dev/playground/untitled#${hash}`);

@ -22,7 +22,7 @@ polka()
.use(async (req, res) => {
const template = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8');
const transformed_template = await vite.transformIndexHtml(req.url, template);
const { default: App } = await vite.ssrLoadModule('/src/main.svelte');
const { default: App } = await vite.ssrLoadModule('/src/App.svelte');
const { head, body } = render(App);
const html = transformed_template

@ -2,7 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import polka from 'polka';
import { render } from 'svelte/server';
import App from './src/main.svelte';
import App from './src/App.svelte';
const { head, body } = render(App);

Loading…
Cancel
Save