Skip to content

Commit

Permalink
chore: separate generated from non-generated server code (#8429)
Browse files Browse the repository at this point in the history
* rename node.server to node.has_server_load - more descriptive

* rename core/prerender to core/postbuild

* fix

* move logic around

* rename server/index.js to server/respond.js

* move some logic around

* move some stuff around

* remove options.paths

* move read to internal request options

* remove version from SSROptions

* tidy up

* add note

* tidy up

* remove public_env from SSROptions

* move logic to sync

* notes to self

* WIP

* hack around the problem

* oops

* partial solution to stack traces

* fix a few more tests

* insane

* use __SVELTEKIT_DEV__ instead of state.dev

* fix

* set globalThis.__SVELTEKIT_DEV__

* tidy some stuff up

* add ambient declaration

* set paths correctly

* remove import

* DRY

* reduce indirection

* use private fields

* separate manifest from server configuration

* tidy up

* exclude SvelteKit deps from Vite processing

* regenerate server-internal.js as necessary

* tidy up

* see if this fixes windows

* of course it's a path separator issue

* remove unused exports

* tidy up, see if this still works in windows

* remove some unused code

* Create gorgeous-actors-run.md

Co-authored-by: Simon Holthausen <[email protected]>
  • Loading branch information
Rich-Harris and dummdidumm authored Jan 13, 2023
1 parent b6c238a commit efc2a4a
Show file tree
Hide file tree
Showing 62 changed files with 1,061 additions and 1,002 deletions.
5 changes: 5 additions & 0 deletions .changeset/gorgeous-actors-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sveltejs/kit": patch
---

chore: separate generated from non-generated server code
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"packages/package/test/fixtures/**/expected/**/*",
"packages/package/test/watch/expected/**/*",
"packages/package/test/watch/package/**/*",
"packages/kit/src/core/prerender/fixtures/**/*",
"packages/kit/src/core/postbuild/fixtures/**/*",
"packages/migrate/migrations/routes/*/samples.md"
],
"options": {
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/core/adapt/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function create_builder({ config, build_data, routes, prerendered, log })

generateFallback(dest) {
// do prerendering in a subprocess so any dangling stuff gets killed upon completion
const script = fileURLToPath(new URL('../prerender/fallback.js', import.meta.url));
const script = fileURLToPath(new URL('../postbuild/fallback.js', import.meta.url));

const manifest_path = `${config.kit.outDir}/output/server/manifest-full.js`;

Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,19 @@ installPolyfills();

const server_root = join(config.outDir, 'output');

/** @type {import('types').ServerInternalModule} */
const { set_building, set_paths } = await import(
pathToFileURL(`${server_root}/server/internal.js`).href
);

/** @type {import('types').ServerModule} */
const { Server, override } = await import(pathToFileURL(`${server_root}/server/index.js`).href);
const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href);

/** @type {import('types').SSRManifest} */
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;

override({
building: true,
paths: config.paths,
read: (file) => readFileSync(join(config.files.assets, file))
});
set_building(true);
set_paths(config.paths);

const server = new Server(manifest);
await server.init({ env: JSON.parse(env) });
Expand All @@ -36,7 +38,8 @@ const rendered = await server.respond(new Request(config.prerender.origin + '/[f
prerendering: {
fallback: true,
dependencies: new Map()
}
},
read: (file) => readFileSync(join(config.files.assets, file))
});

mkdirp(dirname(dest));
Expand Down
104 changes: 104 additions & 0 deletions packages/kit/src/core/postbuild/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { writeFileSync } from 'fs';
import { join } from 'path';
import { pathToFileURL } from 'url';
import { get_option } from '../../runtime/server/utils.js';
import {
validate_common_exports,
validate_page_server_exports,
validate_server_exports
} from '../../utils/exports.js';
import { load_config } from '../config/index.js';
import { prerender } from './prerender.js';

const [, , client_out_dir, manifest_path, results_path, verbose, env] = process.argv;

/** @type {import('types').SSRManifest} */
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;

/** @type {import('types').PrerenderMap} */
const prerender_map = new Map();

/** @type {import('types').ValidatedKitConfig} */
const config = (await load_config()).kit;

const server_root = join(config.outDir, 'output');

/** @type {import('types').ServerInternalModule} */
const internal = await import(pathToFileURL(`${server_root}/server/internal.js`).href);

/** @type {import('types').ServerModule} */
const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href);

// configure `import { building } from '$app/environment'` —
// essential we do this before analysing the code
internal.set_building(true);

// analyse routes
for (const route of manifest._.routes) {
if (route.endpoint) {
const mod = await route.endpoint();
if (mod.prerender !== undefined) {
validate_server_exports(mod, route.id);

if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
throw new Error(
`Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})`
);
}

prerender_map.set(route.id, mod.prerender);
}
}

if (route.page) {
const nodes = await Promise.all(
[...route.page.layouts, route.page.leaf].map((n) => {
if (n !== undefined) return manifest._.nodes[n]();
})
);

const layouts = nodes.slice(0, -1);
const page = nodes.at(-1);

for (const layout of layouts) {
if (layout) {
validate_common_exports(layout.server, route.id);
validate_common_exports(layout.universal, route.id);
}
}

if (page) {
validate_page_server_exports(page.server, route.id);
validate_common_exports(page.universal, route.id);
}

const should_prerender = get_option(nodes, 'prerender');
const prerender =
should_prerender === true ||
// Try prerendering if ssr is false and no server needed. Set it to 'auto' so that
// the route is not removed from the manifest, there could be a server load function.
// People can opt out of this behavior by explicitly setting prerender to false
(should_prerender !== false && get_option(nodes, 'ssr') === false && !page?.server?.actions
? 'auto'
: should_prerender ?? false);

prerender_map.set(route.id, prerender);
}
}

const { prerendered } = await prerender({
Server,
internal,
manifest,
prerender_map,
client_out_dir,
verbose,
env
});

writeFileSync(
results_path,
JSON.stringify({ prerendered, prerender_map }, (_key, value) =>
value instanceof Map ? Array.from(value.entries()) : value
)
);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { readFileSync, writeFileSync } from 'fs';
import { dirname, join } from 'path';
import { pathToFileURL, URL } from 'url';
import { URL } from 'url';
import { installPolyfills } from '../../exports/node/polyfills.js';
import { mkdirp, posixify, walk } from '../../utils/filesystem.js';
import { should_polyfill } from '../../utils/platform.js';
Expand All @@ -11,16 +11,6 @@ import { escape_html_attr } from '../../utils/escape.js';
import { logger } from '../utils.js';
import { load_config } from '../config/index.js';
import { get_route_segments } from '../../utils/routing.js';
import { get_option } from '../../runtime/server/utils.js';
import {
validate_common_exports,
validate_page_server_exports,
validate_server_exports
} from '../../utils/exports.js';

const [, , client_out_dir, manifest_path, results_path, verbose, env] = process.argv;

prerender();

/**
* @template {{message: string}} T
Expand Down Expand Up @@ -52,22 +42,27 @@ const OK = 2;
const REDIRECT = 3;

/**
*
* @param {{
* prerendered: import('types').Prerendered;
* Server: typeof import('types').InternalServer;
* internal: import('types').ServerInternalModule;
* manifest: import('types').SSRManifest;
* prerender_map: import('types').PrerenderMap;
* }} data
* client_out_dir: string;
* verbose: string;
* env: string;
* }} opts
* @returns
*/
const output_and_exit = (data) => {
writeFileSync(
results_path,
JSON.stringify(data, (_key, value) =>
value instanceof Map ? Array.from(value.entries()) : value
)
);
process.exit(0);
};

export async function prerender() {
export async function prerender({
Server,
internal,
manifest,
prerender_map,
client_out_dir,
verbose,
env
}) {
/** @type {import('types').Prerendered} */
const prerendered = {
pages: new Map(),
Expand All @@ -76,9 +71,6 @@ export async function prerender() {
paths: []
};

/** @type {import('types').PrerenderMap} */
const prerender_map = new Map();

/** @type {Set<string>} */
const prerendered_routes = new Set();

Expand All @@ -94,29 +86,10 @@ export async function prerender() {
installPolyfills();
}

const server_root = join(config.outDir, 'output');

/** @type {import('types').ServerModule} */
const { Server, override } = await import(pathToFileURL(`${server_root}/server/index.js`).href);

/** @type {import('types').SSRManifest} */
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;

/** @type {Map<string, string>} */
const saved = new Map();

override({
building: true,
paths: config.paths,
read: (file) => {
// stuff we just wrote
const filepath = saved.get(file);
if (filepath) return readFileSync(filepath);

// stuff in `static`
return readFileSync(join(config.files.assets, file));
}
});
internal.set_paths(config.paths);

const server = new Server(manifest);
await server.init({ env: JSON.parse(env) });
Expand Down Expand Up @@ -201,9 +174,19 @@ export async function prerender() {
const dependencies = new Map();

const response = await server.respond(new Request(config.prerender.origin + encoded), {
getClientAddress,
getClientAddress() {
throw new Error('Cannot read clientAddress during prerendering');
},
prerendering: {
dependencies
},
read: (file) => {
// stuff we just wrote
const filepath = saved.get(file);
if (filepath) return readFileSync(filepath);

// stuff in `static`
return readFileSync(join(config.files.assets, file));
}
});

Expand Down Expand Up @@ -367,58 +350,6 @@ export async function prerender() {
saved.set(file, dest);
}

for (const route of manifest._.routes) {
if (route.endpoint) {
const mod = await route.endpoint();
if (mod.prerender !== undefined) {
validate_server_exports(mod, route.id);

if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
throw new Error(
`Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})`
);
}

prerender_map.set(route.id, mod.prerender);
}
}

if (route.page) {
const nodes = await Promise.all(
[...route.page.layouts, route.page.leaf].map((n) => {
if (n !== undefined) return manifest._.nodes[n]();
})
);

const layouts = nodes.slice(0, -1);
const page = nodes.at(-1);

for (const layout of layouts) {
if (layout) {
validate_common_exports(layout.server, route.id);
validate_common_exports(layout.universal, route.id);
}
}

if (page) {
validate_page_server_exports(page.server, route.id);
validate_common_exports(page.universal, route.id);
}

const should_prerender = get_option(nodes, 'prerender');
const prerender =
should_prerender === true ||
// Try prerendering if ssr is false and no server needed. Set it to 'auto' so that
// the route is not removed from the manifest, there could be a server load function.
// People can opt out of this behavior by explicitly setting prerender to false
(should_prerender !== false && get_option(nodes, 'ssr') === false && !page?.server?.actions
? 'auto'
: should_prerender ?? false);

prerender_map.set(route.id, prerender);
}
}

for (const entry of config.prerender.entries) {
if (entry === '*') {
for (const [id, prerender] of prerender_map) {
Expand Down Expand Up @@ -467,10 +398,5 @@ export async function prerender() {
);
}

output_and_exit({ prerendered, prerender_map });
}

/** @return {string} */
function getClientAddress() {
throw new Error('Cannot read clientAddress during prerendering');
return { prerendered, prerender_map };
}
File renamed without changes.
10 changes: 10 additions & 0 deletions packages/kit/src/core/sync/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { write_root } from './write_root.js';
import { write_tsconfig } from './write_tsconfig.js';
import { write_types, write_all_types } from './write_types/index.js';
import { write_ambient } from './write_ambient.js';
import { write_server } from './write_server.js';

/**
* Initialize SvelteKit's generated files.
Expand All @@ -27,6 +28,7 @@ export async function create(config) {
const output = path.join(config.kit.outDir, 'generated');

write_client_manifest(config, manifest_data, output);
write_server(config, output);
write_root(manifest_data, output);
write_matchers(manifest_data, output);
await write_all_types(config, manifest_data);
Expand Down Expand Up @@ -57,3 +59,11 @@ export async function all(config, mode) {
init(config, mode);
return await create(config);
}

/**
* Regenerate server-internal.js in response to src/{app.html,error.html,service-worker.js} changing
* @param {import('types').ValidatedConfig} config
*/
export function server(config) {
write_server(config, path.join(config.kit.outDir, 'generated'));
}
Loading

0 comments on commit efc2a4a

Please sign in to comment.