Skip to content
5 changes: 5 additions & 0 deletions .changeset/clear-wolves-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: lazy discovery of remote functions
12 changes: 8 additions & 4 deletions packages/kit/src/core/adapt/builder.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @import { Builder } from '@sveltejs/kit' */
/** @import { ResolvedConfig } from 'vite' */
/** @import { RouteDefinition } from '@sveltejs/kit' */
/** @import { RouteData, ValidatedConfig, BuildData, ServerMetadata, ServerMetadataRoute, Prerendered, PrerenderMap, Logger } from 'types' */
/** @import { RouteData, ValidatedConfig, BuildData, ServerMetadata, ServerMetadataRoute, Prerendered, PrerenderMap, Logger, RemoteChunk } from 'types' */
import colors from 'kleur';
import { createReadStream, createWriteStream, existsSync, statSync } from 'node:fs';
import { extname, resolve, join, dirname, relative } from 'node:path';
Expand Down Expand Up @@ -32,6 +32,7 @@ const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.w
* prerender_map: PrerenderMap;
* log: Logger;
* vite_config: ResolvedConfig;
* remotes: RemoteChunk[]
* }} opts
* @returns {Builder}
*/
Expand All @@ -43,7 +44,8 @@ export function create_builder({
prerendered,
prerender_map,
log,
vite_config
vite_config,
remotes
}) {
/** @type {Map<RouteDefinition, RouteData>} */
const lookup = new Map();
Expand Down Expand Up @@ -145,7 +147,8 @@ export function create_builder({
build_data,
prerendered: [],
relative_path: relativePath,
routes: Array.from(filtered)
routes: Array.from(filtered),
remotes
})
});
}
Expand Down Expand Up @@ -195,7 +198,8 @@ export function create_builder({
relative_path: relativePath,
routes: subset
? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route)))
: route_data.filter((route) => prerender_map.get(route.id) !== true)
: route_data.filter((route) => prerender_map.get(route.id) !== true),
remotes
});
},

Expand Down
3 changes: 3 additions & 0 deletions packages/kit/src/core/adapt/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { create_builder } from './builder.js';
* @param {import('types').Prerendered} prerendered
* @param {import('types').PrerenderMap} prerender_map
* @param {import('types').Logger} log
* @param {import('types').RemoteChunk[]} remotes
* @param {import('vite').ResolvedConfig} vite_config
*/
export async function adapt(
Expand All @@ -17,6 +18,7 @@ export async function adapt(
prerendered,
prerender_map,
log,
remotes,
vite_config
) {
// This is only called when adapter is truthy, so the cast is safe
Expand All @@ -32,6 +34,7 @@ export async function adapt(
prerendered,
prerender_map,
log,
remotes,
vite_config
});

Expand Down
6 changes: 4 additions & 2 deletions packages/kit/src/core/generate_manifest/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** @import { RemoteChunk } from 'types' */
import fs from 'node:fs';
import path from 'node:path';
import * as mime from 'mrmime';
Expand All @@ -18,9 +19,10 @@ import { uneval } from 'devalue';
* prerendered: string[];
* relative_path: string;
* routes: import('types').RouteData[];
* remotes: RemoteChunk[];
* }} opts
*/
export function generate_manifest({ build_data, prerendered, relative_path, routes }) {
export function generate_manifest({ build_data, prerendered, relative_path, routes, remotes }) {
/**
* @type {Map<any, number>} The new index of each node in the filtered nodes array
*/
Expand Down Expand Up @@ -101,7 +103,7 @@ export function generate_manifest({ build_data, prerendered, relative_path, rout
${(node_paths).map(loader).join(',\n')}
],
remotes: {
${build_data.manifest_data.remotes.map((remote) => `'${remote.hash}': ${loader(join_relative(relative_path, resolve_symlinks(build_data.server_manifest, remote.file).chunk.file))}`).join(',\n')}
${remotes.map((remote) => `'${remote.hash}': ${loader(join_relative(relative_path, `chunks/remote-${remote.hash}.js`))}`).join(',\n')}
},
routes: [
${routes.map(route => {
Expand Down
16 changes: 8 additions & 8 deletions packages/kit/src/core/postbuild/analyse.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** @import { RemoteChunk } from 'types' */
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { validate_server_exports } from '../../utils/exports.js';
Expand All @@ -11,7 +12,6 @@ import { check_feature } from '../../utils/features.js';
import { createReadableStream } from '@sveltejs/kit/node';
import { PageNodes } from '../../utils/page_nodes.js';
import { build_server_nodes } from '../../exports/vite/build/build_server.js';
import { validate_remote_functions } from '@sveltejs/kit/internal';

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

Expand All @@ -25,6 +25,7 @@ export default forked(import.meta.url, analyse);
* env: Record<string, string>;
* out: string;
* output_config: import('types').RecursiveRequired<import('types').ValidatedConfig['kit']['output']>;
* remotes: RemoteChunk[];
* }} opts
*/
async function analyse({
Expand All @@ -35,7 +36,8 @@ async function analyse({
tracked_features,
env,
out,
output_config
output_config,
remotes
}) {
/** @type {import('@sveltejs/kit').SSRManifest} */
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
Expand Down Expand Up @@ -166,16 +168,14 @@ async function analyse({
}

// analyse remotes
for (const remote of manifest_data.remotes) {
for (const remote of remotes) {
const loader = manifest._.remotes[remote.hash];
const module = await loader();

validate_remote_functions(module, remote.file);
const { default: functions } = await loader();

const exports = new Map();

for (const name in module) {
const info = /** @type {import('types').RemoteInfo} */ (module[name].__);
for (const name in functions) {
const info = /** @type {import('types').RemoteInfo} */ (functions[name].__);
const type = info.type;

exports.set(name, {
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/core/postbuild/prerender.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
for (const loader of Object.values(manifest._.remotes)) {
const module = await loader();

for (const fn of Object.values(module)) {
for (const fn of Object.values(module.default)) {
if (fn?.__?.type === 'prerender') {
prerender_functions.push(fn.__);
should_prerender = true;
Expand Down
32 changes: 1 addition & 31 deletions packages/kit/src/core/sync/create_manifest_data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ 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, walk } from '../../../utils/filesystem.js';
import { posixify, resolve_entry } from '../../../utils/filesystem.js';
import { parse_route_id } from '../../../utils/routing.js';
import { sort_routes } from './sort.js';
import { isSvelte5Plus } from '../utils.js';
import { hash } from '../../../utils/hash.js';

/**
* Generates the manifest data used for the client-side manifest and types generation.
Expand All @@ -28,7 +27,6 @@ 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, cwd);

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

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

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

/** @type {import('types').ManifestData['remotes']} */
const remotes = [];

// TODO could files live in other directories, including node_modules?
for (const file of walk(config.kit.files.src)) {
if (extensions.some((ext) => file.endsWith(ext))) {
const posixified = posixify(path.relative(cwd, `${config.kit.files.src}/${file}`));

remotes.push({
hash: hash(posixified),
file: posixified
});
}
}

return remotes;
}

/**
* @param {string} project_relative
* @param {string} file
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 @@ -62,4 +62,4 @@ export class ActionFailure {
}
}

export { validate_remote_functions } from './remote-functions.js';
export { init_remote_functions } from './remote-functions.js';
17 changes: 12 additions & 5 deletions packages/kit/src/exports/internal/remote-functions.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
/** @import { RemoteInfo } from 'types' */

/** @type {RemoteInfo['type'][]} */
const types = ['command', 'form', 'prerender', 'query'];

/**
* @param {Record<string, any>} module
* @param {string} file
* @param {string} hash
*/
export function validate_remote_functions(module, file) {
export function init_remote_functions(module, file, hash) {
if (module.default) {
throw new Error(
`Cannot export \`default\` from a remote module (${file}) — please use named exports instead`
);
}

for (const name in module) {
const type = module[name]?.__?.type;

if (type !== 'form' && type !== 'command' && type !== 'query' && type !== 'prerender') {
for (const [name, fn] of Object.entries(module)) {
if (!types.includes(fn?.__?.type)) {
throw new Error(
`\`${name}\` exported from ${file} is invalid — all exports from this file must be remote functions`
);
}

fn.__.id = `${hash}/${name}`;
fn.__.name = name;
}
}
129 changes: 0 additions & 129 deletions packages/kit/src/exports/vite/build/build_remote.js

This file was deleted.

Loading
Loading