@jk2908/solas 0.5.3 → 0.6.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 +29 -0
- package/README.md +81 -85
- package/dist/config.d.ts +25 -0
- package/dist/config.js +152 -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 -37
- 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 +46 -0
- package/dist/internal/browser-router/history.js +131 -0
- 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 +6 -14
- package/dist/internal/browser-router/router.js +39 -13
- package/dist/internal/browser-router/shared.d.ts +183 -164
- package/dist/internal/browser-router/shared.js +49 -56
- package/dist/internal/browser-router/use-router.d.ts +1 -10
- 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 +3 -3
- package/dist/internal/codegen/environments.js +5 -6
- 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 +6 -4
- package/dist/internal/env/rsc.js +27 -25
- 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/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 -21
- 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/runtimes/bun.d.ts +0 -13
- package/dist/runtimes/bun.js +0 -39
|
@@ -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 config object
|
|
@@ -7,8 +7,8 @@ export function writeConfig(config) {
|
|
|
7
7
|
const { runtime: _runtime, ...runtimeConfig } = config;
|
|
8
8
|
const loggerLevel = config.logger?.level;
|
|
9
9
|
const importLines = [
|
|
10
|
-
`import type { RuntimeConfig } from '${
|
|
11
|
-
loggerLevel ? `import { Logger } from '${
|
|
10
|
+
`import type { RuntimeConfig } from '${Config.PKG_NAME}'`,
|
|
11
|
+
loggerLevel ? `import { Logger } from '${Config.PKG_NAME}/utils/logger'` : '',
|
|
12
12
|
]
|
|
13
13
|
.filter(Boolean)
|
|
14
14
|
.join('\n');
|
|
@@ -1,4 +1,4 @@
|
|
|
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
|
|
@@ -8,15 +8,14 @@ export function writeRSCEntry(config) {
|
|
|
8
8
|
return source `
|
|
9
9
|
${AUTOGEN_MSG}
|
|
10
10
|
|
|
11
|
-
import { createHandler, createRuntime, Runtime } from '${
|
|
12
|
-
import { Solas } from '${Solas.Config.PKG_NAME}/$'
|
|
11
|
+
import { createHandler, createRuntime, loadManifest, Runtime } from '${Config.PKG_NAME}/env/rsc'
|
|
13
12
|
|
|
14
13
|
import { manifest } from './manifest.js'
|
|
15
14
|
import { importMap } from './maps.js'
|
|
16
15
|
import { config } from './config.js'
|
|
17
16
|
|
|
18
17
|
Runtime.runtime = createRuntime(${runtime})
|
|
19
|
-
const runtimeManifest = await
|
|
18
|
+
const runtimeManifest = await loadManifest(${toStringLiteral(Config.OUT_DIR)})
|
|
20
19
|
|
|
21
20
|
export default createHandler(config, manifest, importMap, runtimeManifest)
|
|
22
21
|
|
|
@@ -32,7 +31,7 @@ export function writeSSREntry() {
|
|
|
32
31
|
return source `
|
|
33
32
|
${AUTOGEN_MSG}
|
|
34
33
|
|
|
35
|
-
export { prerender, resume, ssr } from '${
|
|
34
|
+
export { prerender, resume, ssr } from '${Config.PKG_NAME}/env/ssr'
|
|
36
35
|
`;
|
|
37
36
|
}
|
|
38
37
|
/**
|
|
@@ -42,7 +41,7 @@ export function writeBrowserEntry() {
|
|
|
42
41
|
return source `
|
|
43
42
|
${AUTOGEN_MSG}
|
|
44
43
|
|
|
45
|
-
import { browser } from '${
|
|
44
|
+
import { browser } from '${Config.PKG_NAME}/env/browser'
|
|
46
45
|
|
|
47
46
|
browser()
|
|
48
47
|
`;
|
|
@@ -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,10 @@
|
|
|
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
5
|
export { createRuntime } from '../runtimes/create.js';
|
|
6
6
|
export { Runtime } from '../runtimes/runtime.js';
|
|
7
|
+
export { loadManifest } from '../../manifest.js';
|
|
7
8
|
export type RscPayload = {
|
|
8
9
|
returnValue?: {
|
|
9
10
|
ok: boolean;
|
|
@@ -12,9 +13,10 @@ export type RscPayload = {
|
|
|
12
13
|
formState?: ReactFormState;
|
|
13
14
|
root: React.ReactNode;
|
|
14
15
|
metadata?: Promise<Metadata.Item>;
|
|
15
|
-
url
|
|
16
|
-
pathname
|
|
16
|
+
url: {
|
|
17
|
+
pathname: string;
|
|
17
18
|
search?: string;
|
|
19
|
+
hash?: string;
|
|
18
20
|
};
|
|
19
21
|
};
|
|
20
22
|
/**
|
|
@@ -22,6 +24,6 @@ export type RscPayload = {
|
|
|
22
24
|
* route manifest, and import map to build the router once, then returns an object
|
|
23
25
|
* with a fetch method that handles requests
|
|
24
26
|
*/
|
|
25
|
-
export declare function createHandler(config: RuntimeConfig, manifest: Manifest, importMap: ImportMap, runtimeManifest?:
|
|
27
|
+
export declare function createHandler(config: RuntimeConfig, manifest: Manifest, importMap: ImportMap, runtimeManifest?: RuntimeManifest.Manifest | null): {
|
|
26
28
|
fetch(req: Request): Promise<Response>;
|
|
27
29
|
};
|
package/dist/internal/env/rsc.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
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';
|
|
@@ -19,8 +19,9 @@ import { RequestContext } from './request-context.js';
|
|
|
19
19
|
import { getKnownDigest, isKnownError } from './utils.js';
|
|
20
20
|
export { createRuntime } from '../runtimes/create.js';
|
|
21
21
|
export { Runtime } from '../runtimes/runtime.js';
|
|
22
|
+
export { loadManifest } from '../../manifest.js';
|
|
22
23
|
const logger = new Logger();
|
|
23
|
-
const BASE_PATH =
|
|
24
|
+
const BASE_PATH = normaliseBasePath(import.meta.env.BASE_URL);
|
|
24
25
|
function resolveFilePath(root, relativePath) {
|
|
25
26
|
try {
|
|
26
27
|
const decodedPath = decodeURIComponent(relativePath);
|
|
@@ -43,16 +44,16 @@ function resolveFilePath(root, relativePath) {
|
|
|
43
44
|
*/
|
|
44
45
|
async function createPayload(req, manifest, importMap, baseMetadata, returnValue, formState, temporaryReferences) {
|
|
45
46
|
const resolver = new Resolver(manifest, importMap);
|
|
46
|
-
const prerender = req.headers.get(`x-${
|
|
47
|
+
const prerender = req.headers.get(`x-${Config.SLUG}-prerender`) === '1';
|
|
47
48
|
const url = new URL(req.url);
|
|
48
|
-
const routedPath =
|
|
49
|
+
const routedPath = stripBasePath(url.pathname, BASE_PATH) ?? url.pathname;
|
|
49
50
|
const pathname = routedPath.endsWith('/') && routedPath !== '/' ? routedPath.slice(0, -1) : routedPath;
|
|
50
|
-
const match = resolver.enhance(resolver.reconcile(pathname, req[
|
|
51
|
+
const match = resolver.enhance(resolver.reconcile(pathname, req[Config.REQUEST_META_KEY].match, req[Config.REQUEST_META_KEY].error));
|
|
51
52
|
// if there's no match then no user supplied error boundary
|
|
52
53
|
// has been found, and we should server render a default
|
|
53
54
|
// error screen
|
|
54
55
|
if (!match) {
|
|
55
|
-
const error = toHttpExceptionLike(req[
|
|
56
|
+
const error = toHttpExceptionLike(req[Config.REQUEST_META_KEY].error ?? new HttpException(404, 'Not found'));
|
|
56
57
|
const title = `${error.status ? `${error.status} -` : ''}${error.message}`;
|
|
57
58
|
const rscPayload = {
|
|
58
59
|
root: (_jsxs("html", { lang: "en", children: [
|
|
@@ -65,6 +66,7 @@ async function createPayload(req, manifest, importMap, baseMetadata, returnValue
|
|
|
65
66
|
url: {
|
|
66
67
|
pathname: url.pathname,
|
|
67
68
|
search: url.search,
|
|
69
|
+
hash: url.hash,
|
|
68
70
|
},
|
|
69
71
|
};
|
|
70
72
|
return {
|
|
@@ -162,11 +164,11 @@ async function createPayload(req, manifest, importMap, baseMetadata, returnValue
|
|
|
162
164
|
* with a fetch method that handles requests
|
|
163
165
|
*/
|
|
164
166
|
export function createHandler(config, manifest, importMap, runtimeManifest = null) {
|
|
165
|
-
const CLIENT_OUTPUT_DIR = path.resolve(
|
|
167
|
+
const CLIENT_OUTPUT_DIR = path.resolve(Config.OUT_DIR, 'client');
|
|
166
168
|
// vite emits solas-controlled assets under dist/client/_solas
|
|
167
|
-
const SOLAS_ASSETS_DIR = path.resolve(CLIENT_OUTPUT_DIR,
|
|
169
|
+
const SOLAS_ASSETS_DIR = path.resolve(CLIENT_OUTPUT_DIR, Config.ASSETS_DIR);
|
|
168
170
|
// requests for /_solas and /_solas/* are reserved
|
|
169
|
-
const SOLAS_ASSETS_URL_ROOT = `/${
|
|
171
|
+
const SOLAS_ASSETS_URL_ROOT = `/${Config.ASSETS_DIR}`;
|
|
170
172
|
const prerenderPathMode = config.trailingSlash === 'always' ? 'always' : 'never';
|
|
171
173
|
/**
|
|
172
174
|
* Create the HTTP response for a single incoming request. Runs actions when needed,
|
|
@@ -179,7 +181,7 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
179
181
|
temporaryReferences: undefined,
|
|
180
182
|
returnValue: undefined,
|
|
181
183
|
};
|
|
182
|
-
if (req[
|
|
184
|
+
if (req[Config.REQUEST_META_KEY].action) {
|
|
183
185
|
opts = await processActionRequest(req, {
|
|
184
186
|
trustedOrigins: config.trustedOrigins,
|
|
185
187
|
url: config.url,
|
|
@@ -205,16 +207,16 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
205
207
|
if (isRedirect(err)) {
|
|
206
208
|
const redirect = toRedirect(err);
|
|
207
209
|
const location = redirect.url.startsWith('/')
|
|
208
|
-
? new URL(
|
|
210
|
+
? new URL(applyBasePath(redirect.url, BASE_PATH), req.url).toString()
|
|
209
211
|
: redirect.url;
|
|
210
212
|
return Response.redirect(location, redirect.status);
|
|
211
213
|
}
|
|
212
|
-
if (req[
|
|
214
|
+
if (req[Config.REQUEST_META_KEY].error || !isHttpException(err)) {
|
|
213
215
|
return null;
|
|
214
216
|
}
|
|
215
217
|
// retry once with the surfaced HttpException attached so createPayload can
|
|
216
218
|
// rebuild the route through the nearest matching status boundary
|
|
217
|
-
req[
|
|
219
|
+
req[Config.REQUEST_META_KEY].error = toHttpException(err);
|
|
218
220
|
try {
|
|
219
221
|
const { stream: retriedRscStream, status: retriedStatus } = await createPayload(req, manifest, importMap, config.metadata, opts.returnValue, opts.formState, opts.temporaryReferences);
|
|
220
222
|
const retriedStream = await retriedRscStream;
|
|
@@ -224,13 +226,13 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
224
226
|
};
|
|
225
227
|
}
|
|
226
228
|
finally {
|
|
227
|
-
req[
|
|
229
|
+
req[Config.REQUEST_META_KEY].error = undefined;
|
|
228
230
|
}
|
|
229
231
|
}
|
|
230
232
|
// prerender artifact requests bypass the normal document path so the cli
|
|
231
233
|
// gets structured JSON instead of a rendered html response
|
|
232
|
-
if (req.headers.get(`x-${
|
|
233
|
-
req.headers.get(`x-${
|
|
234
|
+
if (req.headers.get(`x-${Config.SLUG}-prerender`) === '1' &&
|
|
235
|
+
req.headers.get(`x-${Config.SLUG}-prerender-artifact`) === '1') {
|
|
234
236
|
try {
|
|
235
237
|
const artifact = await mod.prerender(stream, {
|
|
236
238
|
formState: opts.formState,
|
|
@@ -274,8 +276,8 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
274
276
|
: null;
|
|
275
277
|
const tryPrelude = artifactEntry?.mode === 'ppr';
|
|
276
278
|
if (tryPrelude) {
|
|
277
|
-
const postponedState = await Prerender.
|
|
278
|
-
const prelude = await Prerender.
|
|
279
|
+
const postponedState = await Prerender.loadPostponedState(Config.OUT_DIR, lookupPath);
|
|
280
|
+
const prelude = await Prerender.loadPrelude(Config.OUT_DIR, lookupPath);
|
|
279
281
|
// resumable ppr responses splice fresh streamed content into the cached
|
|
280
282
|
// prelude when postponed state is available for this route
|
|
281
283
|
if (postponedState) {
|
|
@@ -286,7 +288,7 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
286
288
|
injectPayload: false,
|
|
287
289
|
});
|
|
288
290
|
const body = prelude
|
|
289
|
-
? Prerender.
|
|
291
|
+
? Prerender.composePreludeAndResume(prelude, resumeStream)
|
|
290
292
|
: resumeStream;
|
|
291
293
|
return new Response(body, {
|
|
292
294
|
headers: {
|
|
@@ -342,13 +344,13 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
342
344
|
if (method !== 'GET' && method !== 'HEAD')
|
|
343
345
|
return httpRouter.fetch(req);
|
|
344
346
|
const accept = req.headers.get('accept') ?? '';
|
|
345
|
-
const routedPath =
|
|
347
|
+
const routedPath = stripBasePath(url.pathname, BASE_PATH);
|
|
346
348
|
const canonicalPath = routedPath == null
|
|
347
349
|
? null
|
|
348
350
|
: config.trailingSlash === 'ignore'
|
|
349
351
|
? routedPath
|
|
350
352
|
: normalisePathname(routedPath, config.trailingSlash);
|
|
351
|
-
const canonicalPathname = canonicalPath == null ? null :
|
|
353
|
+
const canonicalPathname = canonicalPath == null ? null : applyBasePath(canonicalPath, BASE_PATH);
|
|
352
354
|
if (canonicalPathname != null &&
|
|
353
355
|
(method === 'GET' || method === 'HEAD') &&
|
|
354
356
|
config.trailingSlash !== 'ignore' &&
|
|
@@ -383,12 +385,12 @@ export function createHandler(config, manifest, importMap, runtimeManifest = nul
|
|
|
383
385
|
if (canonicalPath != null &&
|
|
384
386
|
!import.meta.env.DEV &&
|
|
385
387
|
accept.includes('text/html') &&
|
|
386
|
-
req.headers.get(`x-${
|
|
388
|
+
req.headers.get(`x-${Config.SLUG}-prerender-artifact`) !== '1') {
|
|
387
389
|
// turn the request path into the normal route shape we use for artifact lookups
|
|
388
390
|
const lookupPath = normalisePathname(canonicalPath, prerenderPathMode);
|
|
389
391
|
// only full prerender routes have a saved html file we can serve directly
|
|
390
392
|
const prerenderPath = runtimeManifest?.artifacts[lookupPath]?.mode === 'full'
|
|
391
|
-
? Prerender.
|
|
393
|
+
? Prerender.getArtifactFilePath(Config.OUT_DIR, lookupPath, Prerender.FULL_PRERENDER_FILENAME)
|
|
392
394
|
: null;
|
|
393
395
|
if (prerenderPath) {
|
|
394
396
|
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);
|