@jk2908/solas 0.5.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +34 -94
- package/dist/config.d.ts +24 -0
- package/dist/config.js +143 -0
- package/dist/events.d.ts +4 -0
- package/dist/events.js +5 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +59 -41
- package/dist/internal/boundary-error.d.ts +4 -0
- package/dist/internal/boundary-error.js +8 -0
- package/dist/internal/browser-router/history.d.ts +1 -1
- package/dist/internal/browser-router/link.d.ts +1 -1
- package/dist/internal/browser-router/link.js +1 -1
- package/dist/internal/browser-router/router.d.ts +2 -2
- package/dist/internal/browser-router/router.js +5 -5
- package/dist/internal/browser-router/shared.d.ts +182 -184
- package/dist/internal/browser-router/shared.js +49 -56
- package/dist/internal/browser-router/use-router.d.ts +1 -1
- package/dist/internal/browser-router/use-search-params.js +3 -3
- package/dist/internal/build.d.ts +108 -110
- package/dist/internal/build.js +569 -581
- package/dist/internal/codegen/config.js +4 -5
- package/dist/internal/codegen/environments.d.ts +1 -1
- package/dist/internal/codegen/environments.js +6 -9
- package/dist/internal/codegen/format.d.ts +6 -0
- package/dist/internal/codegen/format.js +24 -0
- package/dist/internal/codegen/manifest.js +2 -2
- package/dist/internal/codegen/maps.d.ts +1 -1
- package/dist/internal/codegen/maps.js +2 -2
- package/dist/internal/codegen/types.js +4 -4
- package/dist/internal/codegen/utils.js +2 -2
- package/dist/internal/env/request-context.js +1 -1
- package/dist/internal/env/rsc.d.ts +3 -3
- package/dist/internal/env/rsc.js +26 -26
- package/dist/internal/env/ssr.js +4 -4
- package/dist/internal/http-router/router.js +8 -8
- package/dist/internal/metadata.d.ts +1 -1
- package/dist/internal/metadata.js +1 -1
- package/dist/internal/navigation/http-exception-boundary.d.ts +1 -1
- package/dist/internal/navigation/redirect-boundary.d.ts +1 -1
- package/dist/internal/navigation/redirect.js +6 -6
- package/dist/internal/postbuild.js +18 -17
- package/dist/internal/prerender.d.ts +130 -138
- package/dist/internal/prerender.js +326 -354
- package/dist/internal/public-files.js +2 -4
- package/dist/internal/render/head.js +2 -2
- package/dist/internal/resolver.js +1 -1
- package/dist/internal/runtimes/runtime.d.ts +7 -21
- package/dist/internal/runtimes/runtime.js +24 -19
- package/dist/internal/server/actions.js +2 -2
- package/dist/internal/ui/error-boundary.d.ts +1 -1
- package/dist/manifest.d.ts +7 -0
- package/dist/manifest.js +85 -0
- package/dist/prerender.d.ts +1 -1
- package/dist/prerender.js +1 -1
- package/dist/solas.d.ts +2 -46
- package/dist/solas.js +6 -266
- package/dist/types.d.ts +14 -25
- package/dist/types.js +1 -1
- package/dist/utils/base-path.d.ts +12 -14
- package/dist/utils/base-path.js +75 -82
- package/dist/utils/compress.d.ts +9 -11
- package/dist/utils/compress.js +59 -64
- package/dist/utils/context.d.ts +4 -6
- package/dist/utils/context.js +17 -22
- package/dist/utils/logger.js +2 -2
- package/dist/utils/time.d.ts +2 -4
- package/dist/utils/time.js +23 -29
- package/package.json +18 -8
- package/dist/internal/runtimes/bun.d.ts +0 -9
- package/dist/internal/runtimes/bun.js +0 -33
- package/dist/internal/runtimes/create.d.ts +0 -4
- package/dist/internal/runtimes/create.js +0 -9
- package/dist/internal/runtimes/node.d.ts +0 -9
- package/dist/internal/runtimes/node.js +0 -31
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Config from '../../config.js';
|
|
2
2
|
import { AUTOGEN_MSG, source, toSourceLiteral } from './utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates the code to create an exported config object
|
|
5
5
|
*/
|
|
6
6
|
export function writeConfig(config) {
|
|
7
|
-
const { runtime: _runtime, ...runtimeConfig } = config;
|
|
8
7
|
const loggerLevel = config.logger?.level;
|
|
9
8
|
const importLines = [
|
|
10
|
-
`import type { RuntimeConfig } from '${
|
|
11
|
-
loggerLevel ? `import { Logger } from '${
|
|
9
|
+
`import type { RuntimeConfig } from '${Config.PKG_NAME}'`,
|
|
10
|
+
loggerLevel ? `import { Logger } from '${Config.PKG_NAME}/utils/logger'` : '',
|
|
12
11
|
]
|
|
13
12
|
.filter(Boolean)
|
|
14
13
|
.join('\n');
|
|
15
|
-
const configStatement = `const config = ${toSourceLiteral(
|
|
14
|
+
const configStatement = `const config = ${toSourceLiteral(config)} as const satisfies RuntimeConfig`;
|
|
16
15
|
const loggerStatement = loggerLevel
|
|
17
16
|
? `Logger.defaultLevel = ${toSourceLiteral(loggerLevel)}`
|
|
18
17
|
: '';
|
|
@@ -2,7 +2,7 @@ import type { ConfiguredPluginConfig } from '../../types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Generates the RSC entry code
|
|
4
4
|
*/
|
|
5
|
-
export declare function writeRSCEntry(
|
|
5
|
+
export declare function writeRSCEntry(_config: ConfiguredPluginConfig): string;
|
|
6
6
|
/**
|
|
7
7
|
* Generates the SSR entry code
|
|
8
8
|
*/
|
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Config from '../../config.js';
|
|
2
2
|
import { AUTOGEN_MSG, source, toStringLiteral } from './utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates the RSC entry code
|
|
5
5
|
*/
|
|
6
|
-
export function writeRSCEntry(
|
|
7
|
-
const runtime = toStringLiteral(config.runtime);
|
|
6
|
+
export function writeRSCEntry(_config) {
|
|
8
7
|
return source `
|
|
9
8
|
${AUTOGEN_MSG}
|
|
10
9
|
|
|
11
|
-
import { createHandler,
|
|
12
|
-
import { Solas } from '${Solas.Config.PKG_NAME}/$'
|
|
10
|
+
import { createHandler, loadManifest } from '${Config.PKG_NAME}/env/rsc'
|
|
13
11
|
|
|
14
12
|
import { manifest } from './manifest.js'
|
|
15
13
|
import { importMap } from './maps.js'
|
|
16
14
|
import { config } from './config.js'
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
const runtimeManifest = await Solas.Runtime.loadManifest(Solas.Config.OUT_DIR)
|
|
16
|
+
const runtimeManifest = await loadManifest(${toStringLiteral(Config.OUT_DIR)})
|
|
20
17
|
|
|
21
18
|
export default createHandler(config, manifest, importMap, runtimeManifest)
|
|
22
19
|
|
|
@@ -32,7 +29,7 @@ export function writeSSREntry() {
|
|
|
32
29
|
return source `
|
|
33
30
|
${AUTOGEN_MSG}
|
|
34
31
|
|
|
35
|
-
export { prerender, resume, ssr } from '${
|
|
32
|
+
export { prerender, resume, ssr } from '${Config.PKG_NAME}/env/ssr'
|
|
36
33
|
`;
|
|
37
34
|
}
|
|
38
35
|
/**
|
|
@@ -42,7 +39,7 @@ export function writeBrowserEntry() {
|
|
|
42
39
|
return source `
|
|
43
40
|
${AUTOGEN_MSG}
|
|
44
41
|
|
|
45
|
-
import { browser } from '${
|
|
42
|
+
import { browser } from '${Config.PKG_NAME}/env/browser'
|
|
46
43
|
|
|
47
44
|
browser()
|
|
48
45
|
`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { format as oxfmtFormat } from 'oxfmt';
|
|
2
|
+
const FORMAT_OPTIONS = {
|
|
3
|
+
useTabs: true,
|
|
4
|
+
tabWidth: 2,
|
|
5
|
+
printWidth: 90,
|
|
6
|
+
singleQuote: true,
|
|
7
|
+
quoteProps: 'as-needed',
|
|
8
|
+
trailingComma: 'all',
|
|
9
|
+
semi: false,
|
|
10
|
+
arrowParens: 'avoid',
|
|
11
|
+
bracketSameLine: true,
|
|
12
|
+
bracketSpacing: true,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Format generated source before it is written to disk. Falls back to the
|
|
16
|
+
* unformatted content when parsing fails so codegen never blocks on a
|
|
17
|
+
* formatter error
|
|
18
|
+
*/
|
|
19
|
+
export async function format(fileName, content) {
|
|
20
|
+
const result = await oxfmtFormat(fileName, content, FORMAT_OPTIONS);
|
|
21
|
+
if (result.errors.length > 0)
|
|
22
|
+
return content;
|
|
23
|
+
return result.code;
|
|
24
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Config from '../../config.js';
|
|
2
2
|
import { AUTOGEN_MSG, source, toSourceLiteral } from './utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates the code to create an exported manifest object
|
|
@@ -7,7 +7,7 @@ export function writeManifest(manifest) {
|
|
|
7
7
|
return source `
|
|
8
8
|
${AUTOGEN_MSG}
|
|
9
9
|
|
|
10
|
-
import type { Manifest } from '${
|
|
10
|
+
import type { Manifest } from '${Config.PKG_NAME}'
|
|
11
11
|
|
|
12
12
|
export const manifest = ${toSourceLiteral(manifest)} as const satisfies Manifest
|
|
13
13
|
`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Config from '../../config.js';
|
|
2
2
|
import { AUTOGEN_MSG, indent, source, toIdentifier, toIdentifierList, toRelativeModuleSpecifier, toStringLiteral, } from './utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates the import map for all route components, endpoints, layouts, shells, and middlewares
|
|
@@ -69,7 +69,7 @@ export function writeMaps(imports, modules) {
|
|
|
69
69
|
return source `
|
|
70
70
|
${AUTOGEN_MSG}
|
|
71
71
|
|
|
72
|
-
import type { ImportMap } from '${
|
|
72
|
+
import type { ImportMap } from '${Config.PKG_NAME}'
|
|
73
73
|
|
|
74
74
|
${importLines
|
|
75
75
|
? `
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as Config from '../../config.js';
|
|
2
|
+
import * as Build from '../build.js';
|
|
3
3
|
import { AUTOGEN_MSG, source } from './utils.js';
|
|
4
4
|
function render(path, params) {
|
|
5
5
|
if (!params || params.length === 0) {
|
|
@@ -36,9 +36,9 @@ export function writeTypes(manifest) {
|
|
|
36
36
|
return source `
|
|
37
37
|
${AUTOGEN_MSG}
|
|
38
38
|
|
|
39
|
-
import '${
|
|
39
|
+
import '${Config.PKG_NAME}'
|
|
40
40
|
|
|
41
|
-
declare module '${
|
|
41
|
+
declare module '${Config.PKG_NAME}' {
|
|
42
42
|
export namespace Solas {
|
|
43
43
|
export interface Routes {
|
|
44
44
|
${body}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const AUTOGEN_MSG = `// auto-generated by ${
|
|
1
|
+
import * as Config from '../../config.js';
|
|
2
|
+
export const AUTOGEN_MSG = `// auto-generated by ${Config.NAME}`;
|
|
3
3
|
const INDENT = '\t';
|
|
4
4
|
const IDENTIFIER = /^[$A-Z_a-z][$\w]*$/;
|
|
5
5
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Context from '../../utils/context.js';
|
|
2
2
|
export const RequestContext = Context.create('request');
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ReactFormState } from 'react-dom/client';
|
|
2
2
|
import type { ImportMap, Manifest, RuntimeConfig } from '../../types.js';
|
|
3
|
-
import
|
|
3
|
+
import * as RuntimeManifest from '../../manifest.js';
|
|
4
4
|
import { Metadata } from '../metadata.js';
|
|
5
|
-
export { createRuntime } from '../runtimes/create.js';
|
|
6
5
|
export { Runtime } from '../runtimes/runtime.js';
|
|
6
|
+
export { loadManifest } from '../../manifest.js';
|
|
7
7
|
export type RscPayload = {
|
|
8
8
|
returnValue?: {
|
|
9
9
|
ok: boolean;
|
|
@@ -23,6 +23,6 @@ export type RscPayload = {
|
|
|
23
23
|
* route manifest, and import map to build the router once, then returns an object
|
|
24
24
|
* with a fetch method that handles requests
|
|
25
25
|
*/
|
|
26
|
-
export declare function createHandler(config: RuntimeConfig, manifest: Manifest, importMap: ImportMap, runtimeManifest?:
|
|
26
|
+
export declare function createHandler(config: RuntimeConfig, manifest: Manifest, importMap: ImportMap, runtimeManifest?: RuntimeManifest.Manifest | null): {
|
|
27
27
|
fetch(req: Request): Promise<Response>;
|
|
28
28
|
};
|
package/dist/internal/env/rsc.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { renderToReadableStream } from '@vitejs/plugin-rsc/rsc';
|
|
4
|
-
import {
|
|
4
|
+
import { applyBasePath, normaliseBasePath, stripBasePath } from '../../utils/base-path.js';
|
|
5
5
|
import { Logger } from '../../utils/logger.js';
|
|
6
|
-
import
|
|
6
|
+
import * as Config from '../../config.js';
|
|
7
7
|
import { createHttpRouter } from '../http-router/create-http-router.js';
|
|
8
8
|
import { HttpRouter } from '../http-router/router.js';
|
|
9
9
|
import { normalisePathname } from '../http-router/utils.js';
|
|
10
10
|
import { Metadata } from '../metadata.js';
|
|
11
11
|
import { HttpException, isHttpException, toHttpException, toHttpExceptionLike, } from '../navigation/http-exception.js';
|
|
12
12
|
import { isRedirect, toRedirect } from '../navigation/redirect.js';
|
|
13
|
-
import
|
|
13
|
+
import * as Prerender from '../prerender.js';
|
|
14
14
|
import { Tree } from '../render/tree.js';
|
|
15
15
|
import { Resolver } from '../resolver.js';
|
|
16
16
|
import { processActionRequest } from '../server/actions.js';
|
|
17
17
|
import DefaultErr from '../ui/defaults/error.js';
|
|
18
18
|
import { RequestContext } from './request-context.js';
|
|
19
19
|
import { getKnownDigest, isKnownError } from './utils.js';
|
|
20
|
-
export { createRuntime } from '../runtimes/create.js';
|
|
21
20
|
export { Runtime } from '../runtimes/runtime.js';
|
|
21
|
+
export { loadManifest } from '../../manifest.js';
|
|
22
22
|
const logger = new Logger();
|
|
23
|
-
const BASE_PATH =
|
|
23
|
+
const BASE_PATH = normaliseBasePath(import.meta.env.BASE_URL);
|
|
24
24
|
function resolveFilePath(root, relativePath) {
|
|
25
25
|
try {
|
|
26
26
|
const decodedPath = decodeURIComponent(relativePath);
|
|
@@ -43,16 +43,16 @@ function resolveFilePath(root, relativePath) {
|
|
|
43
43
|
*/
|
|
44
44
|
async function createPayload(req, manifest, importMap, baseMetadata, returnValue, formState, temporaryReferences) {
|
|
45
45
|
const resolver = new Resolver(manifest, importMap);
|
|
46
|
-
const prerender = req.headers.get(`x-${
|
|
46
|
+
const prerender = req.headers.get(`x-${Config.SLUG}-prerender`) === '1';
|
|
47
47
|
const url = new URL(req.url);
|
|
48
|
-
const routedPath =
|
|
48
|
+
const routedPath = stripBasePath(url.pathname, BASE_PATH) ?? url.pathname;
|
|
49
49
|
const pathname = routedPath.endsWith('/') && routedPath !== '/' ? routedPath.slice(0, -1) : routedPath;
|
|
50
|
-
const match = resolver.enhance(resolver.reconcile(pathname, req[
|
|
50
|
+
const match = resolver.enhance(resolver.reconcile(pathname, req[Config.REQUEST_META_KEY].match, req[Config.REQUEST_META_KEY].error));
|
|
51
51
|
// if there's no match then no user supplied error boundary
|
|
52
52
|
// has been found, and we should server render a default
|
|
53
53
|
// error screen
|
|
54
54
|
if (!match) {
|
|
55
|
-
const error = toHttpExceptionLike(req[
|
|
55
|
+
const error = toHttpExceptionLike(req[Config.REQUEST_META_KEY].error ?? new HttpException(404, 'Not found'));
|
|
56
56
|
const title = `${error.status ? `${error.status} -` : ''}${error.message}`;
|
|
57
57
|
const rscPayload = {
|
|
58
58
|
root: (_jsxs("html", { lang: "en", children: [
|
|
@@ -163,11 +163,11 @@ async function createPayload(req, manifest, importMap, baseMetadata, returnValue
|
|
|
163
163
|
* with a fetch method that handles requests
|
|
164
164
|
*/
|
|
165
165
|
export function createHandler(config, manifest, importMap, runtimeManifest = null) {
|
|
166
|
-
const CLIENT_OUTPUT_DIR = path.resolve(
|
|
166
|
+
const CLIENT_OUTPUT_DIR = path.resolve(Config.OUT_DIR, 'client');
|
|
167
167
|
// vite emits solas-controlled assets under dist/client/_solas
|
|
168
|
-
const SOLAS_ASSETS_DIR = path.resolve(CLIENT_OUTPUT_DIR,
|
|
168
|
+
const SOLAS_ASSETS_DIR = path.resolve(CLIENT_OUTPUT_DIR, Config.ASSETS_DIR);
|
|
169
169
|
// requests for /_solas and /_solas/* are reserved
|
|
170
|
-
const SOLAS_ASSETS_URL_ROOT = `/${
|
|
170
|
+
const SOLAS_ASSETS_URL_ROOT = `/${Config.ASSETS_DIR}`;
|
|
171
171
|
const prerenderPathMode = config.trailingSlash === 'always' ? 'always' : 'never';
|
|
172
172
|
/**
|
|
173
173
|
* Create the HTTP response for a single incoming request. Runs actions when needed,
|
|
@@ -180,7 +180,7 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
180
180
|
temporaryReferences: undefined,
|
|
181
181
|
returnValue: undefined,
|
|
182
182
|
};
|
|
183
|
-
if (req[
|
|
183
|
+
if (req[Config.REQUEST_META_KEY].action) {
|
|
184
184
|
opts = await processActionRequest(req, {
|
|
185
185
|
trustedOrigins: config.trustedOrigins,
|
|
186
186
|
url: config.url,
|
|
@@ -206,16 +206,16 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
206
206
|
if (isRedirect(err)) {
|
|
207
207
|
const redirect = toRedirect(err);
|
|
208
208
|
const location = redirect.url.startsWith('/')
|
|
209
|
-
? new URL(
|
|
209
|
+
? new URL(applyBasePath(redirect.url, BASE_PATH), req.url).toString()
|
|
210
210
|
: redirect.url;
|
|
211
211
|
return Response.redirect(location, redirect.status);
|
|
212
212
|
}
|
|
213
|
-
if (req[
|
|
213
|
+
if (req[Config.REQUEST_META_KEY].error || !isHttpException(err)) {
|
|
214
214
|
return null;
|
|
215
215
|
}
|
|
216
216
|
// retry once with the surfaced HttpException attached so createPayload can
|
|
217
217
|
// rebuild the route through the nearest matching status boundary
|
|
218
|
-
req[
|
|
218
|
+
req[Config.REQUEST_META_KEY].error = toHttpException(err);
|
|
219
219
|
try {
|
|
220
220
|
const { stream: retriedRscStream, status: retriedStatus } = await createPayload(req, manifest, importMap, config.metadata, opts.returnValue, opts.formState, opts.temporaryReferences);
|
|
221
221
|
const retriedStream = await retriedRscStream;
|
|
@@ -225,13 +225,13 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
finally {
|
|
228
|
-
req[
|
|
228
|
+
req[Config.REQUEST_META_KEY].error = undefined;
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
// prerender artifact requests bypass the normal document path so the cli
|
|
232
232
|
// gets structured JSON instead of a rendered html response
|
|
233
|
-
if (req.headers.get(`x-${
|
|
234
|
-
req.headers.get(`x-${
|
|
233
|
+
if (req.headers.get(`x-${Config.SLUG}-prerender`) === '1' &&
|
|
234
|
+
req.headers.get(`x-${Config.SLUG}-prerender-artifact`) === '1') {
|
|
235
235
|
try {
|
|
236
236
|
const artifact = await mod.prerender(stream, {
|
|
237
237
|
formState: opts.formState,
|
|
@@ -275,8 +275,8 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
275
275
|
: null;
|
|
276
276
|
const tryPrelude = artifactEntry?.mode === 'ppr';
|
|
277
277
|
if (tryPrelude) {
|
|
278
|
-
const postponedState = await Prerender.
|
|
279
|
-
const prelude = await Prerender.
|
|
278
|
+
const postponedState = await Prerender.loadPostponedState(Config.OUT_DIR, lookupPath);
|
|
279
|
+
const prelude = await Prerender.loadPrelude(Config.OUT_DIR, lookupPath);
|
|
280
280
|
// resumable ppr responses splice fresh streamed content into the cached
|
|
281
281
|
// prelude when postponed state is available for this route
|
|
282
282
|
if (postponedState) {
|
|
@@ -287,7 +287,7 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
287
287
|
injectPayload: false,
|
|
288
288
|
});
|
|
289
289
|
const body = prelude
|
|
290
|
-
? Prerender.
|
|
290
|
+
? Prerender.composePreludeAndResume(prelude, resumeStream)
|
|
291
291
|
: resumeStream;
|
|
292
292
|
return new Response(body, {
|
|
293
293
|
headers: {
|
|
@@ -343,13 +343,13 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
343
343
|
if (method !== 'GET' && method !== 'HEAD')
|
|
344
344
|
return httpRouter.fetch(req);
|
|
345
345
|
const accept = req.headers.get('accept') ?? '';
|
|
346
|
-
const routedPath =
|
|
346
|
+
const routedPath = stripBasePath(url.pathname, BASE_PATH);
|
|
347
347
|
const canonicalPath = routedPath == null
|
|
348
348
|
? null
|
|
349
349
|
: config.trailingSlash === 'ignore'
|
|
350
350
|
? routedPath
|
|
351
351
|
: normalisePathname(routedPath, config.trailingSlash);
|
|
352
|
-
const canonicalPathname = canonicalPath == null ? null :
|
|
352
|
+
const canonicalPathname = canonicalPath == null ? null : applyBasePath(canonicalPath, BASE_PATH);
|
|
353
353
|
if (canonicalPathname != null &&
|
|
354
354
|
(method === 'GET' || method === 'HEAD') &&
|
|
355
355
|
config.trailingSlash !== 'ignore' &&
|
|
@@ -384,12 +384,12 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
384
384
|
if (canonicalPath != null &&
|
|
385
385
|
!import.meta.env.DEV &&
|
|
386
386
|
accept.includes('text/html') &&
|
|
387
|
-
req.headers.get(`x-${
|
|
387
|
+
req.headers.get(`x-${Config.SLUG}-prerender-artifact`) !== '1') {
|
|
388
388
|
// turn the request path into the normal route shape we use for artifact lookups
|
|
389
389
|
const lookupPath = normalisePathname(canonicalPath, prerenderPathMode);
|
|
390
390
|
// only full prerender routes have a saved html file we can serve directly
|
|
391
391
|
const prerenderPath = runtimeManifest?.artifacts[lookupPath]?.mode === 'full'
|
|
392
|
-
? Prerender.
|
|
392
|
+
? Prerender.getArtifactFilePath(Config.OUT_DIR, lookupPath, Prerender.FULL_PRERENDER_FILENAME)
|
|
393
393
|
: null;
|
|
394
394
|
if (prerenderPath) {
|
|
395
395
|
const res = await HttpRouter.serveStatic(prerenderPath, req, config.precompress, {
|
package/dist/internal/env/ssr.js
CHANGED
|
@@ -4,10 +4,10 @@ import { resume as reactResume, renderToReadableStream } from 'react-dom/server.
|
|
|
4
4
|
import { prerender as reactPrerender } from 'react-dom/static.edge';
|
|
5
5
|
import { createFromReadableStream } from '@vitejs/plugin-rsc/ssr';
|
|
6
6
|
import { Logger } from '../../utils/logger.js';
|
|
7
|
-
import
|
|
7
|
+
import * as Solas from '../../solas.js';
|
|
8
8
|
import { BrowserRouterProvider } from '../browser-router/router.js';
|
|
9
9
|
import { RedirectBoundary } from '../navigation/redirect-boundary.js';
|
|
10
|
-
import
|
|
10
|
+
import * as Prerender from '../prerender.js';
|
|
11
11
|
import { Head } from '../render/head.js';
|
|
12
12
|
import { ErrorBoundary } from '../ui/error-boundary.js';
|
|
13
13
|
import { captureBuffered, injectPayload } from './flight.js';
|
|
@@ -78,13 +78,13 @@ async function prerender(rscStream, opts = {}) {
|
|
|
78
78
|
// abort on a macrotask so sync and microtask work still lands in prelude
|
|
79
79
|
// unresolved work is captured as postponed state for resume
|
|
80
80
|
setTimeout(() => {
|
|
81
|
-
controller.abort(new Prerender.
|
|
81
|
+
controller.abort(new Prerender.Postponed());
|
|
82
82
|
}, 0);
|
|
83
83
|
const { prelude, postponed } = await reactPrerender(_jsx(A, { payloadPromise: payloadPromise }), {
|
|
84
84
|
bootstrapScriptContent,
|
|
85
85
|
signal: controller.signal,
|
|
86
86
|
onError(err) {
|
|
87
|
-
if (Prerender.
|
|
87
|
+
if (Prerender.isPostponed(err))
|
|
88
88
|
return;
|
|
89
89
|
const digest = getKnownDigest(err);
|
|
90
90
|
if (digest)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { match as createMatch } from 'path-to-regexp';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { applyBasePath, normaliseBasePath, stripBasePath } from '../../utils/base-path.js';
|
|
3
|
+
import * as Config from '../../config.js';
|
|
4
4
|
import { HttpException } from '../navigation/http-exception.js';
|
|
5
5
|
import { Runtime } from '../runtimes/runtime.js';
|
|
6
6
|
import { maybeAction } from '../server/actions.js';
|
|
7
7
|
import { enforce } from '../server/csrf.js';
|
|
8
8
|
import { getAlternatePathname, normalisePathname, toPathPattern } from './utils.js';
|
|
9
|
-
const BASE_PATH =
|
|
9
|
+
const BASE_PATH = normaliseBasePath(import.meta.env.BASE_URL);
|
|
10
10
|
/**
|
|
11
11
|
* Handle routing and matching for server requests
|
|
12
12
|
*/
|
|
@@ -166,7 +166,7 @@ export class HttpRouter {
|
|
|
166
166
|
async fetch(req) {
|
|
167
167
|
const url = new URL(req.url);
|
|
168
168
|
const trailingSlash = this.opts.trailingSlash ?? 'never';
|
|
169
|
-
const routedPath =
|
|
169
|
+
const routedPath = stripBasePath(url.pathname, BASE_PATH);
|
|
170
170
|
const path = routedPath == null
|
|
171
171
|
? null
|
|
172
172
|
: trailingSlash === 'ignore'
|
|
@@ -176,7 +176,7 @@ export class HttpRouter {
|
|
|
176
176
|
let action = false;
|
|
177
177
|
try {
|
|
178
178
|
const method = req.method.toUpperCase();
|
|
179
|
-
const canonicalPathname = path == null ? null :
|
|
179
|
+
const canonicalPathname = path == null ? null : applyBasePath(path, BASE_PATH);
|
|
180
180
|
if (canonicalPathname != null &&
|
|
181
181
|
(method === 'GET' || method === 'HEAD') &&
|
|
182
182
|
canonicalPathname !== url.pathname) {
|
|
@@ -197,13 +197,13 @@ export class HttpRouter {
|
|
|
197
197
|
const error = new HttpException(404, 'Not found');
|
|
198
198
|
// unmatched requests still go through the same error hook shape as matched ones
|
|
199
199
|
return (this.#onError?.(error, Object.assign(req, {
|
|
200
|
-
[
|
|
200
|
+
[Config.REQUEST_META_KEY]: { match: null, error, action },
|
|
201
201
|
})) ?? new Response(error.message, { status: error.status }));
|
|
202
202
|
}
|
|
203
203
|
const matched = match;
|
|
204
204
|
// attach route state once so middleware and handlers read the same request data
|
|
205
205
|
const request = Object.assign(req, {
|
|
206
|
-
[
|
|
206
|
+
[Config.REQUEST_META_KEY]: { match: matched, action, parsedFormData },
|
|
207
207
|
});
|
|
208
208
|
// check csrf before any middleware or handler runs
|
|
209
209
|
enforce(request, this.opts.csrf);
|
|
@@ -215,7 +215,7 @@ export class HttpRouter {
|
|
|
215
215
|
// turn unknown throws into Error objects so the error hook sees one shape
|
|
216
216
|
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
|
|
217
217
|
const request = Object.assign(req, {
|
|
218
|
-
[
|
|
218
|
+
[Config.REQUEST_META_KEY]: { match, error, action },
|
|
219
219
|
});
|
|
220
220
|
if (this.#onError)
|
|
221
221
|
return this.#onError(error, request);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpException } from './navigation/http-exception.js';
|
|
2
|
-
import
|
|
2
|
+
import * as Build from './build.js';
|
|
3
3
|
type EntryKind = typeof Build.EntryKind;
|
|
4
4
|
export declare namespace Metadata {
|
|
5
5
|
type EntrySource = Exclude<EntryKind[keyof EntryKind], typeof Build.EntryKind.ENDPOINT | typeof Build.EntryKind.MIDDLEWARE>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BoundaryError } from '
|
|
1
|
+
import type { BoundaryError } from '../boundary-error.js';
|
|
2
2
|
import { type HttpException } from './http-exception.js';
|
|
3
3
|
type ComponentsMap = Partial<Record<HttpException.StatusCode, React.ReactElement | null>>;
|
|
4
4
|
export type Props = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Config from '../../config.js';
|
|
2
2
|
function isRedirectStatusCode(value) {
|
|
3
3
|
return value === 301 || value === 302 || value === 307 || value === 308;
|
|
4
4
|
}
|
|
@@ -24,12 +24,12 @@ export const REDIRECT_DIGEST_PREFIX = 'REDIRECT';
|
|
|
24
24
|
*/
|
|
25
25
|
function validate(url) {
|
|
26
26
|
if (url.startsWith('//')) {
|
|
27
|
-
throw new TypeError(`[${
|
|
27
|
+
throw new TypeError(`[${Config.NAME}] redirect() does not allow protocol-relative urls`);
|
|
28
28
|
}
|
|
29
29
|
// reject urls with control characters to prevent header injection
|
|
30
30
|
for (const char of url) {
|
|
31
31
|
if (char === '\r' || char === '\n') {
|
|
32
|
-
throw new TypeError(`[${
|
|
32
|
+
throw new TypeError(`[${Config.NAME}] redirect() does not allow control characters`);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
// good
|
|
@@ -40,10 +40,10 @@ function validate(url) {
|
|
|
40
40
|
parsed = new URL(url);
|
|
41
41
|
}
|
|
42
42
|
catch {
|
|
43
|
-
throw new TypeError(`[${
|
|
43
|
+
throw new TypeError(`[${Config.NAME}] redirect() only supports relative paths or absolute http/https urls`);
|
|
44
44
|
}
|
|
45
45
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
46
|
-
throw new TypeError(`[${
|
|
46
|
+
throw new TypeError(`[${Config.NAME}] redirect() only supports http:// and https:// urls`);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
@@ -87,7 +87,7 @@ export function toRedirect(err) {
|
|
|
87
87
|
? err.url
|
|
88
88
|
: undefined);
|
|
89
89
|
if (!url) {
|
|
90
|
-
throw new TypeError(`[${
|
|
90
|
+
throw new TypeError(`[${Config.NAME}] failed to reconstruct redirect`);
|
|
91
91
|
}
|
|
92
92
|
return new Redirect(url, status);
|
|
93
93
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import
|
|
3
|
+
import * as Compress from '../utils/compress.js';
|
|
4
4
|
import { Logger } from '../utils/logger.js';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import * as Config from '../config.js';
|
|
6
|
+
import * as Manifest from '../manifest.js';
|
|
7
|
+
import * as Prerender from './prerender.js';
|
|
7
8
|
import { Runtime } from './runtimes/runtime.js';
|
|
8
9
|
const logger = new Logger();
|
|
9
10
|
export async function postbuild(cwd = process.cwd()) {
|
|
10
|
-
const manifestPath = path.join(cwd,
|
|
11
|
+
const manifestPath = path.join(cwd, Config.GENERATED_DIR, 'build.json');
|
|
11
12
|
let manifest;
|
|
12
13
|
try {
|
|
13
14
|
const raw = await fs.readFile(manifestPath, 'utf-8');
|
|
@@ -17,15 +18,15 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
17
18
|
logger.error('[build] failed to read build manifest', err);
|
|
18
19
|
throw err;
|
|
19
20
|
}
|
|
20
|
-
const outDir = path.resolve(cwd,
|
|
21
|
+
const outDir = path.resolve(cwd, Config.OUT_DIR);
|
|
21
22
|
const rscDir = path.join(outDir, 'rsc');
|
|
22
|
-
const artifactRoot = Prerender.
|
|
23
|
+
const artifactRoot = Prerender.getArtifactRootPath(outDir);
|
|
23
24
|
// clear old prerender artifacts so routes that have switched modes
|
|
24
25
|
// do not keep stale metadata from a previous build
|
|
25
26
|
await fs.rm(artifactRoot, { recursive: true, force: true });
|
|
26
|
-
const
|
|
27
|
+
const artfifactManifest = {};
|
|
27
28
|
if (manifest.prerenderRoutes.length > 0) {
|
|
28
|
-
const concurrency = Prerender.
|
|
29
|
+
const concurrency = Prerender.getConcurrency();
|
|
29
30
|
const pendingWrites = new Set();
|
|
30
31
|
logger.info('[prerender]', `prerendering ${manifest.prerenderRoutes.length} routes (concurrency: ${concurrency})...`);
|
|
31
32
|
const rscEntry = path.join(rscDir, 'index.js');
|
|
@@ -39,7 +40,7 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
39
40
|
await Promise.race(pendingWrites);
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
for await (const result of Prerender.
|
|
43
|
+
for await (const result of Prerender.run(app, manifest.prerenderRoutes, {
|
|
43
44
|
base: manifest.base,
|
|
44
45
|
concurrency,
|
|
45
46
|
origin: manifest.url,
|
|
@@ -54,7 +55,7 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
54
55
|
continue;
|
|
55
56
|
}
|
|
56
57
|
const artifact = result.artifact;
|
|
57
|
-
const artifactDir = Prerender.
|
|
58
|
+
const artifactDir = Prerender.getArtifactPath(outDir, route);
|
|
58
59
|
await enqueueWrite(async () => {
|
|
59
60
|
try {
|
|
60
61
|
if (artifact.mode === 'ppr') {
|
|
@@ -72,7 +73,7 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
72
73
|
writes.push(Runtime.write(path.join(artifactDir, 'postponed.json'), JSON.stringify(artifact.postponed)));
|
|
73
74
|
}
|
|
74
75
|
await Promise.all(writes);
|
|
75
|
-
|
|
76
|
+
artfifactManifest[route] = {
|
|
76
77
|
mode: artifact.mode,
|
|
77
78
|
files: artifact.postponed !== undefined
|
|
78
79
|
? ['metadata', 'prelude', 'postponed']
|
|
@@ -89,9 +90,9 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
89
90
|
createdAt: artifact.createdAt,
|
|
90
91
|
mode: artifact.mode,
|
|
91
92
|
})),
|
|
92
|
-
Runtime.write(Prerender.
|
|
93
|
+
Runtime.write(Prerender.getArtifactFilePath(outDir, route, Prerender.FULL_PRERENDER_FILENAME), artifact.html),
|
|
93
94
|
]);
|
|
94
|
-
|
|
95
|
+
artfifactManifest[route] = {
|
|
95
96
|
mode: artifact.mode,
|
|
96
97
|
files: ['metadata', 'html'],
|
|
97
98
|
};
|
|
@@ -106,10 +107,10 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
106
107
|
}
|
|
107
108
|
await fs.mkdir(artifactRoot, { recursive: true });
|
|
108
109
|
const runtimeManifest = {
|
|
109
|
-
artifacts:
|
|
110
|
+
artifacts: artfifactManifest,
|
|
110
111
|
publicFiles: manifest.publicFiles,
|
|
111
112
|
};
|
|
112
|
-
await Runtime.write(
|
|
113
|
+
await Runtime.write(Manifest.getManifestPath(outDir), JSON.stringify(runtimeManifest));
|
|
113
114
|
if (manifest.sitemapRoutes.length > 0 && manifest.url) {
|
|
114
115
|
const origin = manifest.url.replace(/\/$/, '');
|
|
115
116
|
const urls = manifest.sitemapRoutes
|
|
@@ -141,8 +142,8 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
141
142
|
}
|
|
142
143
|
// full prerendered html is served straight from disk, but internal ppr
|
|
143
144
|
// support files like prelude/metadata/postponed are read by the server
|
|
144
|
-
return (normalisedPath.startsWith(`${
|
|
145
|
-
normalisedPath.endsWith(`/${Prerender.
|
|
145
|
+
return (normalisedPath.startsWith(`${Config.GENERATED_DIR}/ppr/`) &&
|
|
146
|
+
normalisedPath.endsWith(`/${Prerender.FULL_PRERENDER_FILENAME}`));
|
|
146
147
|
},
|
|
147
148
|
})) {
|
|
148
149
|
await Runtime.write(`${input}.br`, compressed);
|