Skip to content

Remote Functions #13986

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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/enhanced-img/src/vite-plugin.js
Original file line number Diff line number Diff line change
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('../types/internal.js').Attribute[]} */
const attributes = node.attributes;
const index = attributes.findIndex(
(attribute) => 'name' in attribute && attribute.name === 'sizes'
Expand Down
3 changes: 2 additions & 1 deletion 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,7 +38,7 @@
"@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.35.5",
"svelte-preprocess": "^6.0.0",
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 '../../utils/hash.js';
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, modules_fn] of Object.entries(manifest._.remotes)) {
const modules = await modules_fn();
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
50 changes: 48 additions & 2 deletions packages/kit/src/core/postbuild/prerender.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { forked } from '../../utils/fork.js';
import * as devalue from 'devalue';
import { createReadableStream } from '@sveltejs/kit/node';
import generate_fallback from './fallback.js';
import { stringify_remote_arg } from '../../runtime/shared.js';

export default forked(import.meta.url, prerender);

Expand Down Expand Up @@ -186,6 +187,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
}
const seen = new Set();
const written = new Set();
const remote_responses = new Map();

/** @type {Map<string, Set<string>>} */
const expected_hashlinks = new Map();
Expand Down Expand Up @@ -229,7 +231,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
throw new Error('Cannot read clientAddress during prerendering');
},
prerendering: {
dependencies
dependencies,
remote_responses
},
read: (file) => {
// stuff we just wrote
Expand Down Expand Up @@ -460,8 +463,25 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
}
}

/** @type {Array<Function & { __: import('types').RemoteInfo & { type: 'prerender'}}>} */
const remote_functions = [];

for (const remote of Object.values(manifest._.remotes)) {
const functions = Object.values(await remote()).filter(
(value) =>
typeof value === 'function' &&
/** @type {import('types').RemoteInfo} */ (value.__)?.type === 'prerender'
);
if (functions.length > 0) {
has_prerenderable_routes = true;
remote_functions.push(...functions);
}
}

if (
(config.prerender.entries.length === 0 && route_level_entries.length === 0) ||
(config.prerender.entries.length === 0 &&
route_level_entries.length === 0 &&
remote_functions.length === 0) ||
!has_prerenderable_routes
) {
return { prerendered, prerender_map };
Expand Down Expand Up @@ -499,6 +519,32 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
}
}

const transport = (await internal.get_hooks()).transport ?? {};
for (const remote_function of remote_functions) {
// TODO this writes to /prerender/pages/... eventually, should it go into
// /prerender/dependencies like indirect calls due to page prerenders?
// Does it really matter?
if (remote_function.__.entries) {
for (const entry of await remote_function.__.entries()) {
void enqueue(
null,
config.paths.base +
'/' +
config.appDir +
'/remote/' +
remote_function.__.id +
'/' +
stringify_remote_arg(entry, transport)
);
}
} else {
void enqueue(
null,
config.paths.base + '/' + config.appDir + '/remote/' + remote_function.__.id
);
}
}

await q.done();

// handle invalid fragment links
Expand Down
22 changes: 21 additions & 1 deletion packages/kit/src/core/sync/create_manifest_data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import process from 'node:process';
import colors from 'kleur';
import { lookup } from 'mrmime';
import { list_files, runtime_directory } from '../../utils.js';
import { posixify, resolve_entry } from '../../../utils/filesystem.js';
import { posixify, resolve_entry, walk } from '../../../utils/filesystem.js';
import { parse_route_id } from '../../../utils/routing.js';
import { sort_routes } from './sort.js';
import { isSvelte5Plus } from '../utils.js';
Expand All @@ -27,6 +27,7 @@ export default function create_manifest_data({
const hooks = create_hooks(config, cwd);
const matchers = create_matchers(config, cwd);
const { nodes, routes } = create_routes_and_nodes(cwd, config, fallback);
const remotes = create_remotes(config);

for (const route of routes) {
for (const param of route.params) {
Expand All @@ -41,6 +42,7 @@ export default function create_manifest_data({
hooks,
matchers,
nodes,
remotes,
routes
};
}
Expand Down Expand Up @@ -465,6 +467,24 @@ function create_routes_and_nodes(cwd, config, fallback) {
};
}

/**
* @param {import('types').ValidatedConfig} config
*/
function create_remotes(config) {
if (!config.kit.experimental.remoteFunctions) return [];

const extensions = config.kit.moduleExtensions.map((ext) => `.remote${ext}`);

// TODO could files live in other directories, including node_modules?
return [config.kit.files.lib, config.kit.files.routes].flatMap((dir) =>
fs.existsSync(dir)
? walk(dir)
.filter((file) => extensions.some((ext) => file.endsWith(ext)))
.map((file) => posixify(`${dir}/${file}`))
: []
);
}

/**
* @param {string} project_relative
* @param {string} file
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/core/sync/write_server.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path';
import process from 'node:process';
import { hash } from '../../runtime/hash.js';
import { hash } from '../../utils/hash.js';
import { posixify, resolve_entry } from '../../utils/filesystem.js';
import { s } from '../../utils/misc.js';
import { load_error_page, load_template } from '../config/index.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/exports/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export function text(body, init) {
*/
/**
* Create an `ActionFailure` object. Call when form submission fails.
* @template {Record<string, unknown> | undefined} [T=undefined]
* @template [T=undefined]
* @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
* @param {T} data Data associated with the failure (e.g. validation errors)
* @overload
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/exports/internal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class SvelteKitError extends Error {
}

/**
* @template {Record<string, unknown> | undefined} [T=undefined]
* @template [T=undefined]
*/
export class ActionFailure {
/**
Expand Down
Loading
Loading