Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
3684cbe
initial commit
trueadm Mar 12, 2025
33fecc0
various fixes
Rich-Harris Mar 13, 2025
89894e7
tweak demo
Rich-Harris Mar 13, 2025
10cd660
fix
Rich-Harris Mar 13, 2025
9ea9af4
remove acorn-typescript
Rich-Harris Mar 13, 2025
0b6eea0
simplify
Rich-Harris Mar 13, 2025
0fdcb1e
fix
Rich-Harris Mar 13, 2025
afd66e0
generate, don't transform
Rich-Harris Mar 13, 2025
c3a4651
use an x- header
Rich-Harris Mar 13, 2025
b49d381
regenerate manifest when remote files are added/removed
Rich-Harris Mar 13, 2025
e5fbb1c
move RPC logic out of page, it belongs elsewhere
Rich-Harris Mar 13, 2025
c6271ec
use _app/remote/x
Rich-Harris Mar 13, 2025
9b3cb3d
tighten up error handling
Rich-Harris Mar 13, 2025
ed95600
unused
Rich-Harris Mar 13, 2025
e61faee
CSRF
Rich-Harris Mar 13, 2025
e504a8b
smaller payloads
Rich-Harris Mar 13, 2025
939cd49
belt and braces
Rich-Harris Mar 14, 2025
d0a1234
don't use 204, that only applies to DELETE/PUT requests
Rich-Harris Mar 14, 2025
bfe200c
turn remote_call into a factory - more compact and gives us a lot mor…
Rich-Harris Mar 14, 2025
1a823ba
Merge branch 'main' into rpc
dummdidumm Mar 21, 2025
c37964e
analyze exports of remotes dynamically, add query/action/formAction a…
dummdidumm Mar 22, 2025
ad73a46
update playground
dummdidumm Mar 22, 2025
8900ba3
POC: hydrate query results
dummdidumm Mar 23, 2025
0f26cbc
remote form actions that hydrate and work without JS
dummdidumm Mar 28, 2025
09f81ab
conditional
dummdidumm Apr 1, 2025
b7bf021
load fn WIP
dummdidumm Apr 29, 2025
047e564
rerun query on invalidation
dummdidumm May 2, 2025
d96133f
make it possible to call invalidate in rpc functions
dummdidumm May 2, 2025
13fe672
fix
dummdidumm May 7, 2025
99c8f44
adjust form API
dummdidumm May 7, 2025
0891e10
fix dev stale bug
dummdidumm May 7, 2025
3b0509b
let rpc partake in prerendering
dummdidumm May 7, 2025
4bbe879
prerender POC
dummdidumm May 7, 2025
e01d4b7
cache POC
dummdidumm May 12, 2025
750ada2
simplify server remote logic by leveraging ESM self imports
dummdidumm May 12, 2025
25d878a
cleanup server remote info
dummdidumm May 12, 2025
48fbbdd
rename functions + some docs
dummdidumm May 13, 2025
2b29f9d
move more stuff into functions to deduplicate/cleanup/make connection…
dummdidumm May 13, 2025
b6738c6
prerendering
dummdidumm May 14, 2025
7479f3f
hide remote url, avoid unnecessary work
dummdidumm May 14, 2025
5de51aa
cache refinement
dummdidumm May 14, 2025
b4e7c8a
various fixes
dummdidumm May 21, 2025
3290a67
tests
dummdidumm May 21, 2025
001af94
don't call prerender function at runtime, tweak tests
dummdidumm May 22, 2025
c4e20f3
tweak
dummdidumm May 22, 2025
bcd0f04
tweak caching behavior
dummdidumm May 23, 2025
115474d
remove cache function from public API for now
dummdidumm May 23, 2025
5906918
add experimental flag
dummdidumm May 23, 2025
de84282
remove load helper function for now
dummdidumm May 23, 2025
30ee652
move file
dummdidumm May 23, 2025
7127b7c
add refreshAll, related to #13139
dummdidumm May 23, 2025
07e9e40
adjust overrides signature
dummdidumm May 23, 2025
c4a2990
query/redirect/form-fail handling
dummdidumm May 26, 2025
c13f226
adjust caching behavior to cache until query is removed
dummdidumm May 26, 2025
c71d255
disallow non-remote-function exports from .remote files
dummdidumm May 27, 2025
9f2258d
Merge branch 'main' into rpc-ssr-2
dummdidumm May 27, 2025
30ca6df
bump dts-buddy to be able to generate types without bugs again
dummdidumm May 27, 2025
98fb10d
handle query redirect without going through error boundaries
dummdidumm May 27, 2025
7fec138
harmonize refresh with override signature
dummdidumm May 27, 2025
8ba78cf
fixes
dummdidumm May 28, 2025
3bbd244
resolve cyclic import dependency
dummdidumm May 28, 2025
b1e3971
prerender treeshaking
dummdidumm May 28, 2025
7b998c4
refine API
dummdidumm May 31, 2025
455bddd
refresh from the server
dummdidumm May 31, 2025
c7a5333
adjust tests, fix
dummdidumm Jun 2, 2025
7fe241f
adjust prerender
dummdidumm Jun 2, 2025
b00d949
reorder functions
dummdidumm Jun 2, 2025
c499157
make query eager on the client if in reactive context
dummdidumm Jun 3, 2025
a8f0585
implement current/error/pending
dummdidumm Jun 3, 2025
afbce42
remove optimistic in favor of override callback
dummdidumm Jun 3, 2025
1612f11
add validate
dummdidumm Jun 3, 2025
2bf7748
form.for(...)
dummdidumm Jun 3, 2025
4e57605
tweak api around redirects; allow single flight mutation redirect
dummdidumm Jun 3, 2025
0ef2550
implement stacking override API
dummdidumm Jun 3, 2025
c8beb06
fixes
dummdidumm Jun 4, 2025
3ed093a
deduplicate remote calls on the server during full page visits
dummdidumm Jun 12, 2025
78022ae
rework to use resource class, fix some bugs
dummdidumm Jun 12, 2025
b0c32ed
ensure refresh/then resolve in order
dummdidumm Jun 13, 2025
4cf31bb
microtask tweaking
dummdidumm Jun 13, 2025
4f3c610
fix
dummdidumm Jun 13, 2025
9617625
cleanse event for remote functions
dummdidumm Jun 13, 2025
24eadc7
implement `updates` and `withOverride` for command
dummdidumm Jun 30, 2025
3ecaf5e
implement `updates` and `withOverride` for form
dummdidumm Jun 30, 2025
8d08614
cleanup
dummdidumm Jun 30, 2025
992ff78
Prevent state_unsafe_mutation error
dummdidumm Jun 30, 2025
4a9d8b9
restrict to 0 or 1 argument, enforce validation
dummdidumm Jul 3, 2025
6f2dc5e
validation tests
dummdidumm Jul 4, 2025
3de122d
tweak
dummdidumm Jul 4, 2025
f2ff410
Merge branch 'main' into rpc-ssr-2
dummdidumm Jul 4, 2025
2189d39
comment out for the time being
dummdidumm Jul 4, 2025
4cdc041
remove validate function
dummdidumm Jul 4, 2025
ffa67d2
lint
dummdidumm Jul 4, 2025
25184cd
guard
dummdidumm Jul 4, 2025
4cb4f3a
bump Svelte version
dummdidumm Jul 4, 2025
b921e13
silence type errors
dummdidumm Jul 4, 2025
3b2a636
Update packages/kit/src/runtime/app/server/remote.js
dummdidumm Jul 7, 2025
acf681f
Merge branch 'main' into rpc-ssr-2
dummdidumm Jul 10, 2025
61f9a39
fix
dummdidumm Jul 10, 2025
69e7993
update playground
dummdidumm Jul 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/adapter-cloudflare/test/apps/pages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@sveltejs/kit": "workspace:^",
"@sveltejs/vite-plugin-svelte": "catalog:",
"server-side-dep": "file:server-side-dep",
"svelte": "^5.23.1",
"svelte": "^5.35.2",
"vite": "catalog:",
"wrangler": "^4.14.3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-cloudflare/test/apps/workers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"@sveltejs/vite-plugin-svelte": "catalog:",
"cross-env": "catalog:",
"server-side-dep": "file:server-side-dep",
"svelte": "^5.23.1",
"svelte": "^5.35.2",
"vite": "catalog:",
"wrangler": "^4.14.3"
},
Expand Down
18 changes: 17 additions & 1 deletion packages/adapter-node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ export default function (opts = {}) {
].join('\n\n')
);

// TODO this is how could do caching for remote functions
// builder.copy(`${files}/cache.js`, `${tmp}/cache.js`);

// for (const remote of readdirSync(`${tmp}/remote`)) {
// let code = readFileSync(`${tmp}/remote/${remote}`, 'utf-8');
// // `import * as $$_self_$$` is added to remote files by our code Vite remote plugin
// code += `\n// === generated by node adapter ===
// import $$_add_cache from '../cache.js';
// for (const v of Object.values($$_self_$$)) {
// $$_add_cache(v);
// }\n`;
// writeFileSync(`${tmp}/remote/${remote}`, code, 'utf-8');
// }

const pkg = JSON.parse(readFileSync('package.json', 'utf8'));

// we bundle the Vite output so that deployments only need
Expand All @@ -54,7 +68,8 @@ export default function (opts = {}) {
const bundle = await rollup({
input: {
index: `${tmp}/index.js`,
manifest: `${tmp}/manifest.js`
manifest: `${tmp}/manifest.js`,
cache: `${tmp}/cache.js`
},
external: [
// dependencies could have deep exports, so we need a regex
Expand All @@ -80,6 +95,7 @@ export default function (opts = {}) {
});

builder.copy(files, out, {
filter: (name) => name !== 'cache.js',
replace: {
ENV: './env.js',
HANDLER: './handler.js',
Expand Down
8 changes: 8 additions & 0 deletions packages/adapter-node/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,13 @@ export default [
format: 'esm'
},
plugins: [nodeResolve(), commonjs(), prefixBuiltinModules()]
},
{
input: 'src/cache.js',
output: {
file: 'files/cache.js',
format: 'esm'
},
plugins: [nodeResolve(), commonjs(), prefixBuiltinModules()]
}
];
87 changes: 87 additions & 0 deletions packages/adapter-node/src/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { dirname, join } from 'node:path';
import { existsSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';

// This file will exist within the server folder, which is next to the prerendered folder
const dir = join(dirname(dirname(fileURLToPath(import.meta.url))), 'prerendered');

class Cache {
/**
* @param {string} id - The unique identifier for the cache instance
* @param {number | false} expiration - The expiration time in milliseconds
*/
constructor(id, expiration) {
this.id = id;
this.expiration = typeof expiration === 'number' ? expiration * 1000 : expiration;
this.memory_cache = new Map(); // In-memory cache to store output and write time
}

/** @param {string} input */
path(input) {
return join(dir, encodeURIComponent(this.id + '|' + input));
}

/** @param {string} input */
get(input) {
const now = Date.now();

// Check in-memory cache
if (this.memory_cache.has(input)) {
const { output, timestamp } = this.memory_cache.get(input);
if (this.expiration === false || now - timestamp <= this.expiration) {
return output;
} else {
this.delete(input);
}
}

// Check file system
const file_path = this.path(input);
if (existsSync(file_path)) {
const stats = statSync(file_path);
const last_modified = stats.mtimeMs;

if (this.expiration === false || now - last_modified <= this.expiration) {
const output = readFileSync(file_path, 'utf-8');
this.memory_cache.set(input, { output, timestamp: now });
return output;
} else {
this.delete(input);
}
}

return undefined;
}

/**
* @param {string} input
* @param {string} output
*/
set(input, output) {
const now = Date.now();
this.memory_cache.set(input, { output, timestamp: now });
writeFileSync(this.path(input), output, 'utf-8');

if (typeof this.expiration === 'number') {
setTimeout(() => {
this.delete(input);
}, this.expiration);
}
}

/** @param {string} input */
delete(input) {
this.memory_cache.delete(input);
const file_path = this.path(input);
if (existsSync(file_path)) {
rmSync(file_path);
}
}
}

/** @param {any} cache_fn */
export default function add_cache(cache_fn) {
// For __ see the RemoteInfo type
if (cache_fn.__?.type !== 'cache') return;
cache_fn.cache = new Cache(cache_fn.__.id, cache_fn.__.config.expiration);
}
2 changes: 1 addition & 1 deletion packages/adapter-static/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@sveltejs/vite-plugin-svelte": "catalog:",
"@types/node": "^18.19.48",
"sirv": "^3.0.0",
"svelte": "^5.23.1",
"svelte": "^5.35.2",
"typescript": "^5.3.3",
"vite": "catalog:"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-static/test/apps/prerendered/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@sveltejs/kit": "workspace:^",
"@sveltejs/vite-plugin-svelte": "catalog:",
"sirv-cli": "^3.0.0",
"svelte": "^5.23.1",
"svelte": "^5.35.2",
"vite": "catalog:"
},
"type": "module"
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-static/test/apps/spa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@sveltejs/kit": "workspace:^",
"@sveltejs/vite-plugin-svelte": "catalog:",
"sirv-cli": "^3.0.0",
"svelte": "^5.23.1",
"svelte": "^5.35.2",
"vite": "catalog:"
},
"type": "module"
Expand Down
50 changes: 50 additions & 0 deletions packages/adapter-vercel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,56 @@ const plugin = function (defaults = {}) {
}
}

// TODO this is how we could do it with the remote `cache` function if we want ISR
// if (fs.existsSync(`${builder.getServerDirectory()}/remote`)) {
// for (const remote of fs.readdirSync(`${builder.getServerDirectory()}/remote`)) {
// // For now we're not caching when a full-page-hit is responded to, and make it an ISR function for client-side hits.
// // Once Vercel data cache is available, use that instead, using a mechanism similar to the Node adapter
// const src = pathToFileURL(`${builder.getServerDirectory()}/remote/${remote}`);
// const module = await import(src.href);
// for (const value of Object.values(module)) {
// // For __ see the RemoteInfo type
// if (value.__?.type === 'cache') {
// const isr_name = value.__.id;
// const base = `${dirs.functions}/${isr_name}`;
// builder.mkdirp(base);

// // This is either the single function or the fallback function; both have all the remote files
// const target = `${dirs.functions}/${INTERNAL}.func`;
// const relative = path.relative(path.dirname(base), target);

// // create a symlink to the actual function, but use the
// // route name so that we can derive the correct URL
// fs.symlinkSync(relative, `${base}.func`);

// const pathname = `/${builder.getAppPath()}/remote/${value.__.id}`;
// const json = JSON.stringify(
// {
// expiration: value.__config.expiration,
// bypassToken: value.__config.bypassToken,
// allowQuery: ['__pathname', 'args'],
// passQuery: true
// },
// null,
// '\t'
// );

// write(`${base}.prerender-config.json`, json);

// static_config.routes.push({
// src: pathname + '$',
// dest: `/${isr_name}?__pathname=${pathname}`
// });
// }
// }

// static_config.routes.push({
// src: `/${builder.getAppPath()}/immutable/${remote}(.+)`,
// dest: `/${builder.getAppPath()}/immutable/${remote}$1`
// });
// }
// }

// optional chaining to support older versions that don't have this setting yet
if (builder.config.kit.router?.resolution === 'server') {
// Create a separate edge function just for server-side route resolution.
Expand Down
2 changes: 1 addition & 1 deletion packages/enhanced-img/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@types/estree": "^1.0.5",
"@types/node": "^18.19.48",
"rollup": "^4.27.4",
"svelte": "^5.23.1",
"svelte": "^5.35.2",
"typescript": "^5.6.3",
"vite": "catalog:",
"vitest": "catalog:"
Expand Down
4 changes: 2 additions & 2 deletions packages/enhanced-img/src/vite-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ function get_attr_value(node, attr) {

/**
* @param {string} content
* @param {import('../types/internal.js').Attribute[]} attributes
* @param {import('svelte/compiler').AST.RegularElement['attributes']} attributes
* @param {{
* src: string,
* width: string | number,
Expand Down Expand Up @@ -324,7 +324,7 @@ function stringToNumber(param) {
* @param {import('vite-imagetools').Picture} image
*/
function img_to_picture(content, node, image) {
/** @type {import('../types/internal.js').Attribute[]} attributes */
/** @type {import('svelte/compiler').AST.RegularElement['attributes']} */
const attributes = node.attributes;
const index = attributes.findIndex(
(attribute) => 'name' in attribute && attribute.name === 'sizes'
Expand Down
5 changes: 3 additions & 2 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"homepage": "https://svelte.dev",
"type": "module",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@sveltejs/acorn-typescript": "^1.0.5",
"@types/cookie": "^0.6.0",
"acorn": "^8.14.1",
Expand All @@ -37,9 +38,9 @@
"@types/connect": "^3.4.38",
"@types/node": "^18.19.48",
"@types/set-cookie-parser": "^2.4.7",
"dts-buddy": "^0.6.1",
"dts-buddy": "^0.6.2",
"rollup": "^4.14.2",
"svelte": "^5.23.1",
"svelte": "^5.35.2",
"svelte-preprocess": "^6.0.0",
"typescript": "^5.3.3",
"vite": "catalog:",
Expand Down
3 changes: 3 additions & 0 deletions packages/kit/src/core/config/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ const get_defaults = (prefix = '') => ({
publicPrefix: 'PUBLIC_',
privatePrefix: ''
},
experimental: {
remoteFunctions: false
},
files: {
assets: join(prefix, 'static'),
hooks: {
Expand Down
4 changes: 4 additions & 0 deletions packages/kit/src/core/config/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ const options = object(
privatePrefix: string('')
}),

experimental: object({
remoteFunctions: boolean(false)
}),

files: object({
assets: string('static'),
hooks: object({
Expand Down
4 changes: 4 additions & 0 deletions packages/kit/src/core/generate_manifest/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { compact } from '../../utils/array.js';
import { join_relative } from '../../utils/filesystem.js';
import { dedent } from '../sync/utils.js';
import { find_server_assets } from './find_server_assets.js';
import { hash } from '../../runtime/hash.js'; // TODO move this function
import { uneval } from 'devalue';

/**
Expand Down Expand Up @@ -100,6 +101,9 @@ export function generate_manifest({ build_data, prerendered, relative_path, rout
nodes: [
${(node_paths).map(loader).join(',\n')}
],
remotes: {
${build_data.manifest_data.remotes.map((filename) => `'${hash(filename)}': ${loader(join_relative(relative_path, resolve_symlinks(build_data.server_manifest, filename).chunk.file))}`).join(',\n\t\t\t\t\t')}
},
routes: [
${routes.map(route => {
if (!route.page && !route.endpoint) return;
Expand Down
15 changes: 14 additions & 1 deletion packages/kit/src/core/postbuild/analyse.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ async function analyse({
/** @type {import('types').ServerMetadata} */
const metadata = {
nodes: [],
routes: new Map()
routes: new Map(),
remotes: new Map()
};

const nodes = await Promise.all(manifest._.nodes.map((loader) => loader()));
Expand Down Expand Up @@ -164,6 +165,18 @@ async function analyse({
});
}

// analyse remotes
for (const remote of Object.keys(manifest._.remotes)) {
const modules = await manifest._.remotes[remote]();
const exports = new Map();
for (const [name, value] of Object.entries(modules)) {
const type = /** @type {import('types').RemoteInfo} */ (value.__)?.type;
if (!type) continue;
exports.set(type, (exports.get(type) ?? []).concat(name));
}
metadata.remotes.set(remote, exports);
}

return { metadata, static_exports };
}

Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/core/postbuild/fallback.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ async function generate_fallback({ manifest_path, env }) {
},
prerendering: {
fallback: true,
dependencies: new Map()
dependencies: new Map(),
remote_responses: new Map()
},
read: (file) => readFileSync(join(config.files.assets, file))
});
Expand Down
Loading