toiljs 0.0.15 → 0.0.19
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/.babelrc +13 -13
- package/.gitattributes +2 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
- package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -90
- package/.github/ISSUE_TEMPLATE/config.yml +8 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
- package/.github/PULL_REQUEST_TEMPLATE.md +43 -43
- package/.github/changelog-config.json +45 -45
- package/.github/dependabot.yml +27 -27
- package/.github/workflows/ci.yml +191 -191
- package/.prettierrc.json +11 -11
- package/.vscode/settings.json +9 -9
- package/CHANGELOG.md +116 -5
- package/LICENSE +187 -187
- package/README.md +524 -315
- package/as-pect.asconfig.json +34 -34
- package/as-pect.config.js +65 -65
- package/assets/logo.svg +36 -36
- package/build/backend/.tsbuildinfo +1 -1
- package/build/backend/index.d.ts +1 -0
- package/build/backend/index.js +20 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +1320 -696
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/devtools.d.ts +6 -0
- package/build/client/dev/devtools.js +479 -0
- package/build/client/dev/error-overlay.d.ts +9 -0
- package/build/client/dev/error-overlay.js +19 -4
- package/build/client/errors.d.ts +1 -0
- package/build/client/errors.js +3 -0
- package/build/client/index.d.ts +2 -0
- package/build/client/index.js +2 -0
- package/build/client/navigation/prefetch.d.ts +1 -0
- package/build/client/navigation/prefetch.js +35 -0
- package/build/client/routing/Router.js +1 -1
- package/build/client/routing/hooks.js +6 -2
- package/build/client/routing/loader.d.ts +23 -0
- package/build/client/routing/loader.js +53 -7
- package/build/client/routing/mount.js +4 -3
- package/build/client/rpc.d.ts +1 -0
- package/build/client/rpc.js +37 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +16 -0
- package/build/compiler/config.js +9 -0
- package/build/compiler/docs.js +78 -21
- package/build/compiler/generate.js +5 -4
- package/build/compiler/index.d.ts +3 -2
- package/build/compiler/index.js +2 -2
- package/build/compiler/plugin.js +228 -0
- package/build/compiler/prerender.d.ts +1 -0
- package/build/compiler/prerender.js +1 -1
- package/build/compiler/seo.d.ts +1 -1
- package/build/compiler/seo.js +20 -5
- package/build/compiler/ssg.js +39 -2
- package/build/compiler/vite.js +25 -0
- package/build/io/.tsbuildinfo +1 -1
- package/build/io/codec.d.ts +54 -0
- package/build/io/codec.js +143 -0
- package/build/io/index.d.ts +1 -2
- package/build/io/index.js +1 -2
- package/build/logger/.tsbuildinfo +1 -1
- package/build/shared/.tsbuildinfo +1 -1
- package/eslint.config.js +48 -48
- package/examples/basic/client/404.tsx +11 -11
- package/examples/basic/client/components/.gitkeep +1 -1
- package/examples/basic/client/global-error.tsx +13 -13
- package/examples/basic/client/layout.tsx +25 -25
- package/examples/basic/client/public/images/.gitkeep +1 -1
- package/examples/basic/client/public/images/logo.svg +36 -36
- package/examples/basic/client/public/robots.txt +2 -2
- package/examples/basic/client/routes/docs/[...slug].tsx +12 -12
- package/examples/basic/client/routes/features/error/error.tsx +16 -16
- package/examples/basic/client/routes/features/index.tsx +1 -1
- package/examples/basic/client/routes/features/template/b.tsx +14 -14
- package/examples/basic/client/routes/files/[[...slug]].tsx +21 -21
- package/examples/basic/client/routes/gallery/layout.tsx +13 -13
- package/examples/basic/client/routes/io.tsx +23 -24
- package/examples/basic/client/routes/loader-demo/loading.tsx +13 -13
- package/examples/basic/client/routes/rest.tsx +74 -0
- package/examples/basic/client/routes/rpc.tsx +43 -0
- package/examples/basic/client/routes/search.tsx +61 -61
- package/examples/basic/client/toil.tsx +5 -5
- package/package.json +167 -148
- package/presets/eslint.js +88 -88
- package/presets/no-uint8array-tostring.js +200 -200
- package/presets/prettier-plugin.js +51 -0
- package/presets/prettier.json +19 -18
- package/presets/tsconfig.json +37 -37
- package/server/runtime/README.md +97 -0
- package/server/runtime/abort/abort.ts +27 -0
- package/server/runtime/env/Server.ts +61 -0
- package/server/runtime/envelope.ts +191 -0
- package/server/runtime/exports/index.ts +52 -0
- package/server/runtime/handlers/ToilHandler.ts +34 -0
- package/server/runtime/index.ts +26 -0
- package/server/runtime/lang/Potential.ts +5 -0
- package/server/runtime/memory.ts +81 -0
- package/server/runtime/request.ts +55 -0
- package/server/runtime/response.ts +86 -0
- package/server/runtime/rest/Rest.ts +39 -0
- package/server/runtime/rest/RestHandler.ts +20 -0
- package/server/runtime/rest/RouteContext.ts +82 -0
- package/server/runtime/rest/match.ts +48 -0
- package/server/runtime/tsconfig.json +7 -0
- package/src/backend/index.ts +202 -160
- package/src/cli/create.ts +15 -5
- package/src/cli/diagnostics.ts +81 -0
- package/src/cli/doctor.ts +384 -7
- package/src/cli/index.ts +11 -2
- package/src/cli/proc.ts +50 -50
- package/src/cli/updates.ts +69 -69
- package/src/cli/validate.ts +31 -31
- package/src/client/channel/channel.ts +146 -146
- package/src/client/components/Form.tsx +65 -65
- package/src/client/components/Script.tsx +113 -113
- package/src/client/components/Slot.tsx +21 -21
- package/src/client/dev/devtools.tsx +1018 -0
- package/src/client/dev/error-overlay.tsx +30 -4
- package/src/client/errors.ts +11 -0
- package/src/client/head/head.ts +167 -167
- package/src/client/head/metadata.ts +112 -112
- package/src/client/index.ts +91 -89
- package/src/client/navigation/NavLink.tsx +86 -86
- package/src/client/navigation/navigation.ts +235 -235
- package/src/client/navigation/prefetch.ts +169 -130
- package/src/client/navigation/scroll.ts +53 -53
- package/src/client/routing/Router.tsx +8 -2
- package/src/client/routing/action.ts +122 -122
- package/src/client/routing/error-boundary.tsx +43 -43
- package/src/client/routing/hooks.ts +21 -6
- package/src/client/routing/loader.ts +325 -235
- package/src/client/routing/match.ts +47 -47
- package/src/client/routing/mount.tsx +54 -52
- package/src/client/routing/params-context.ts +10 -10
- package/src/client/routing/slot-context.ts +7 -7
- package/src/client/rpc.ts +64 -0
- package/src/client/search/search.ts +189 -189
- package/src/client/search/use-page-search.ts +73 -73
- package/src/client/types.ts +73 -73
- package/src/compiler/config.ts +221 -182
- package/src/compiler/docs.ts +285 -228
- package/src/compiler/generate.ts +395 -394
- package/src/compiler/index.ts +66 -57
- package/src/compiler/pages.ts +70 -70
- package/src/compiler/plugin.ts +258 -2
- package/src/compiler/prerender.ts +156 -156
- package/src/compiler/seo.ts +417 -390
- package/src/compiler/ssg.ts +171 -126
- package/src/compiler/vite.ts +34 -0
- package/src/io/FastMap.ts +151 -127
- package/src/io/FastSet.ts +15 -1
- package/src/io/codec.ts +217 -0
- package/src/io/index.ts +10 -11
- package/src/io/lengths.ts +14 -14
- package/src/io/types.ts +19 -18
- package/src/logger/index.ts +22 -22
- package/src/shared/index.ts +10 -10
- package/std/client/index.d.ts +15 -15
- package/std/client/package.json +3 -3
- package/test/assembly/example.spec.ts +17 -7
- package/test/channel.test.ts +21 -21
- package/test/doctor.test.ts +65 -0
- package/test/dom/Link.test.tsx +47 -47
- package/test/dom/NavLink.test.tsx +37 -37
- package/test/dom/error-overlay.test.tsx +44 -44
- package/test/dom/loader.test.tsx +121 -121
- package/test/dom/navigation.test.ts +59 -59
- package/test/dom/revalidate.test.tsx +38 -38
- package/test/dom/route-head.test.tsx +78 -78
- package/test/dom/router-loading.test.tsx +44 -44
- package/test/dom/scroll.test.ts +56 -56
- package/test/dom/use-metadata.test.tsx +58 -58
- package/test/errors.test.ts +21 -0
- package/test/io.test.ts +117 -93
- package/test/navlink.test.ts +28 -28
- package/test/placeholder.test.ts +9 -9
- package/test/prettier-plugin.test.ts +46 -0
- package/test/routes.test.ts +76 -76
- package/test/rpc.test.ts +50 -0
- package/test/seo.test.ts +175 -164
- package/test/slot-layouts.test.ts +69 -69
- package/test/ssg.test.ts +36 -36
- package/test/update.test.ts +44 -44
- package/test/validate.test.ts +42 -42
- package/tests/data-parity/generated-parity.ts +99 -0
- package/tests/data-parity/parity.ts +80 -0
- package/tests/data-parity/spec.ts +46 -0
- package/toil-routes.d.ts +7 -0
- package/tsconfig.backend.json +13 -13
- package/tsconfig.base.json +35 -35
- package/tsconfig.cli.json +13 -13
- package/tsconfig.client.json +14 -14
- package/tsconfig.compiler.json +13 -13
- package/tsconfig.io.json +12 -12
- package/tsconfig.json +22 -22
- package/tsconfig.logger.json +12 -12
- package/tsconfig.server.json +10 -10
- package/tsconfig.shared.json +12 -12
- package/vitest.config.ts +26 -26
- package/.idea/codeStyles/Project.xml +0 -54
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -7
- package/.idea/toiljs.iml +0 -8
- package/.idea/vcs.xml +0 -6
- package/.toil/entry.tsx +0 -9
- package/.toil/index.html +0 -12
- package/.toil/routes.ts +0 -9
- package/build/cli/configure.d.ts +0 -16
- package/build/cli/configure.js +0 -272
- package/build/cli/create.d.ts +0 -16
- package/build/cli/create.js +0 -420
- package/build/cli/diagnostics.d.ts +0 -55
- package/build/cli/diagnostics.js +0 -333
- package/build/cli/doctor.d.ts +0 -6
- package/build/cli/doctor.js +0 -249
- package/build/cli/features.d.ts +0 -25
- package/build/cli/features.js +0 -107
- package/build/cli/index.d.ts +0 -2
- package/build/cli/proc.d.ts +0 -6
- package/build/cli/proc.js +0 -31
- package/build/cli/ui.d.ts +0 -9
- package/build/cli/ui.js +0 -75
- package/build/cli/update.d.ts +0 -7
- package/build/cli/update.js +0 -117
- package/build/cli/updates.d.ts +0 -10
- package/build/cli/updates.js +0 -45
- package/build/cli/validate.d.ts +0 -4
- package/build/cli/validate.js +0 -19
- package/build/client/Link.d.ts +0 -8
- package/build/client/Link.js +0 -44
- package/build/client/NavLink.d.ts +0 -14
- package/build/client/NavLink.js +0 -37
- package/build/client/Router.d.ts +0 -7
- package/build/client/Router.js +0 -55
- package/build/client/channel.d.ts +0 -23
- package/build/client/channel.js +0 -94
- package/build/client/error-boundary.d.ts +0 -16
- package/build/client/error-boundary.js +0 -19
- package/build/client/head.d.ts +0 -26
- package/build/client/head.js +0 -87
- package/build/client/hooks.d.ts +0 -17
- package/build/client/hooks.js +0 -48
- package/build/client/lazy.d.ts +0 -16
- package/build/client/lazy.js +0 -53
- package/build/client/match.d.ts +0 -2
- package/build/client/match.js +0 -32
- package/build/client/mount.d.ts +0 -2
- package/build/client/mount.js +0 -13
- package/build/client/navigation.d.ts +0 -13
- package/build/client/navigation.js +0 -97
- package/build/client/params-context.d.ts +0 -2
- package/build/client/params-context.js +0 -2
- package/build/client/prefetch.d.ts +0 -11
- package/build/client/prefetch.js +0 -100
- package/build/client/runtime.d.ts +0 -31
- package/build/client/runtime.js +0 -112
- package/build/client/scroll.d.ts +0 -8
- package/build/client/scroll.js +0 -36
- package/build/io/BinaryReader.d.ts +0 -44
- package/build/io/BinaryReader.js +0 -244
- package/build/io/BinaryWriter.d.ts +0 -44
- package/build/io/BinaryWriter.js +0 -297
- package/build/server/release.wasm +0 -0
- package/build/server/release.wat +0 -9
- package/src/io/BinaryReader.ts +0 -340
- package/src/io/BinaryWriter.ts +0 -385
- package/src/server/index.ts +0 -10
- package/src/server/main.ts +0 -13
- package/src/server/tsconfig.json +0 -4
- package/toil-env.d.ts +0 -16
- package/toilconfig.json +0 -30
package/src/compiler/ssg.ts
CHANGED
|
@@ -1,126 +1,171 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Build-time SSG for dynamic routes. After the client bundle is written, this loads each dynamic
|
|
3
|
-
* route that exports `generateStaticParams`, enumerates its concrete URLs, runs the route's
|
|
4
|
-
* `generateMetadata` per URL, and bakes a `<url>/index.html` (so JS-less crawlers get per-page tags)
|
|
5
|
-
* plus a `sitemap.xml` entry. Opt-in: a route without `generateStaticParams` is untouched, and the
|
|
6
|
-
* whole pass is skipped when no such route exists or `seo` is unconfigured. Build-only.
|
|
7
|
-
*
|
|
8
|
-
* Runs from `build()` (not the prerender Vite plugin) so it can reuse `createViteConfig` without the
|
|
9
|
-
* `vite.ts` <-> `prerender.ts` import cycle; it spins up a short-lived SSR server to load route source.
|
|
10
|
-
*/
|
|
11
|
-
import fs from 'node:fs';
|
|
12
|
-
import path from 'node:path';
|
|
13
|
-
|
|
14
|
-
import { createServer } from 'vite';
|
|
15
|
-
|
|
16
|
-
import { type ResolvedToilConfig } from './config.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Build-time SSG for dynamic routes. After the client bundle is written, this loads each dynamic
|
|
3
|
+
* route that exports `generateStaticParams`, enumerates its concrete URLs, runs the route's
|
|
4
|
+
* `generateMetadata` per URL, and bakes a `<url>/index.html` (so JS-less crawlers get per-page tags)
|
|
5
|
+
* plus a `sitemap.xml` entry. Opt-in: a route without `generateStaticParams` is untouched, and the
|
|
6
|
+
* whole pass is skipped when no such route exists or `seo` is unconfigured. Build-only.
|
|
7
|
+
*
|
|
8
|
+
* Runs from `build()` (not the prerender Vite plugin) so it can reuse `createViteConfig` without the
|
|
9
|
+
* `vite.ts` <-> `prerender.ts` import cycle; it spins up a short-lived SSR server to load route source.
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
|
|
14
|
+
import { createServer } from 'vite';
|
|
15
|
+
|
|
16
|
+
import { type ResolvedToilConfig } from './config.js';
|
|
17
|
+
import { extractStaticMetadata, loadTypeScript } from './prerender.js';
|
|
18
|
+
import { scanRoutes } from './routes.js';
|
|
19
|
+
import { injectSeoHtml, joinUrl, llmsTxt, routeSeo, sitemapXml, type LlmsPage } from './seo.js';
|
|
20
|
+
import { createViteConfig } from './vite.js';
|
|
21
|
+
|
|
22
|
+
/** Reads a string field off a metadata record, or undefined. */
|
|
23
|
+
function metaString(meta: Record<string, unknown> | null, key: string): string | undefined {
|
|
24
|
+
const value = meta?.[key];
|
|
25
|
+
return typeof value === 'string' ? value : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type StaticParams = Record<string, string | string[]>;
|
|
29
|
+
|
|
30
|
+
interface RouteModule {
|
|
31
|
+
generateStaticParams?: () => StaticParams[] | Promise<StaticParams[]>;
|
|
32
|
+
generateMetadata?: (args: {
|
|
33
|
+
params: StaticParams;
|
|
34
|
+
searchParams: URLSearchParams;
|
|
35
|
+
data: unknown;
|
|
36
|
+
}) => unknown;
|
|
37
|
+
loader?: (args: { params: StaticParams; searchParams: URLSearchParams }) => unknown;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Substitutes `:param` / `*catch-all` segments in a route pattern with concrete param values. */
|
|
42
|
+
export function fillPattern(pattern: string, params: StaticParams): string {
|
|
43
|
+
return pattern.replace(/[:*]([A-Za-z0-9_]+)/g, (_m, name: string) => {
|
|
44
|
+
const value = params[name] as string | string[] | undefined;
|
|
45
|
+
if (Array.isArray(value)) return value.join('/');
|
|
46
|
+
return value ?? '';
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Coerces an unknown module export to a typed Metadata-ish record, or null. */
|
|
51
|
+
function asMetadata(value: unknown): Record<string, unknown> | null {
|
|
52
|
+
return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Pre-renders every dynamic route that opts in via `generateStaticParams`. Bakes per-URL HTML into
|
|
57
|
+
* `outDir` and rewrites `sitemap.xml` with the generated URLs. Returns the list of generated URLs.
|
|
58
|
+
*/
|
|
59
|
+
export async function prerenderStaticParams(cfg: ResolvedToilConfig): Promise<string[]> {
|
|
60
|
+
if (!cfg.seo) return [];
|
|
61
|
+
const outDir = path.resolve(cfg.root, cfg.outDir);
|
|
62
|
+
// Prefer the clean shell stashed by the prerender plugin (no per-route SEO baked in); fall back
|
|
63
|
+
// to the built index.html.
|
|
64
|
+
const stashed = path.join(cfg.toilDir, 'shell.html');
|
|
65
|
+
const shellPath = fs.existsSync(stashed) ? stashed : path.join(outDir, 'index.html');
|
|
66
|
+
if (!fs.existsSync(shellPath)) return [];
|
|
67
|
+
|
|
68
|
+
const allRoutes = scanRoutes(cfg.routesAbsDir);
|
|
69
|
+
const dynamic = allRoutes.filter(
|
|
70
|
+
(r) => r.slot === undefined && !r.intercept && /[:*]/.test(r.pattern),
|
|
71
|
+
);
|
|
72
|
+
if (dynamic.length === 0) return [];
|
|
73
|
+
|
|
74
|
+
const shell = fs.readFileSync(shellPath, 'utf8');
|
|
75
|
+
const server = await createServer({
|
|
76
|
+
...(await createViteConfig(cfg)),
|
|
77
|
+
server: { middlewareMode: true, hmr: false },
|
|
78
|
+
appType: 'custom',
|
|
79
|
+
logLevel: 'silent',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const baseUrl = cfg.seo.url;
|
|
83
|
+
const generated: string[] = [];
|
|
84
|
+
const dynamicPages: LlmsPage[] = [];
|
|
85
|
+
const warn = (msg: string): void => {
|
|
86
|
+
process.stderr.write(` toil: SSG ${msg}\n`);
|
|
87
|
+
};
|
|
88
|
+
try {
|
|
89
|
+
for (const route of dynamic) {
|
|
90
|
+
let mod: RouteModule;
|
|
91
|
+
try {
|
|
92
|
+
mod = (await server.ssrLoadModule(route.file)) as RouteModule;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
warn(`skipped ${route.pattern} (${err instanceof Error ? err.message : String(err)})`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (typeof mod.generateStaticParams !== 'function') continue;
|
|
98
|
+
const paramSets = await mod.generateStaticParams();
|
|
99
|
+
for (const params of paramSets) {
|
|
100
|
+
const url = fillPattern(route.pattern, params);
|
|
101
|
+
// Containment guard: a param value with `..`/separators could make `url` resolve the
|
|
102
|
+
// output path outside `outDir`. Resolve the target up front and skip anything that
|
|
103
|
+
// escapes, so a (possibly externally-derived) param can't clobber files elsewhere.
|
|
104
|
+
const target = path.join(outDir, url.replace(/^\//, ''), 'index.html');
|
|
105
|
+
const outRoot = path.resolve(outDir);
|
|
106
|
+
const absTarget = path.resolve(target);
|
|
107
|
+
if (absTarget !== outRoot && !absTarget.startsWith(outRoot + path.sep)) {
|
|
108
|
+
warn(`skipped ${route.pattern}: params escape outDir (${url})`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
let metadata: Record<string, unknown> | null = null;
|
|
112
|
+
try {
|
|
113
|
+
if (typeof mod.generateMetadata === 'function') {
|
|
114
|
+
const searchParams = new URLSearchParams();
|
|
115
|
+
const data =
|
|
116
|
+
typeof mod.loader === 'function'
|
|
117
|
+
? await mod.loader({ params, searchParams })
|
|
118
|
+
: undefined;
|
|
119
|
+
metadata = asMetadata(await mod.generateMetadata({ params, searchParams, data }));
|
|
120
|
+
} else if (mod.metadata) {
|
|
121
|
+
metadata = asMetadata(mod.metadata);
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
warn(`metadata failed for ${url} (${err instanceof Error ? err.message : String(err)})`);
|
|
125
|
+
}
|
|
126
|
+
const html = injectSeoHtml(shell, routeSeo(cfg.seo, metadata, url));
|
|
127
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
128
|
+
fs.writeFileSync(target, html);
|
|
129
|
+
generated.push(url);
|
|
130
|
+
dynamicPages.push({
|
|
131
|
+
title: metaString(metadata, 'title') ?? url,
|
|
132
|
+
url: baseUrl !== undefined ? joinUrl(baseUrl, url) : url,
|
|
133
|
+
description: metaString(metadata, 'description'),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} finally {
|
|
138
|
+
await server.close();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (generated.length > 0) {
|
|
142
|
+
const sitemap = sitemapXml(cfg.seo, allRoutes, generated);
|
|
143
|
+
if (sitemap) fs.writeFileSync(path.join(outDir, 'sitemap.xml'), sitemap);
|
|
144
|
+
process.stdout.write(
|
|
145
|
+
` ✓ prerendered ${String(generated.length)} dynamic route${generated.length === 1 ? '' : 's'}\n`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Rewrite llms.txt with the full page index: every static route's resolved title/description plus
|
|
150
|
+
// the enumerated dynamic pages, so AI crawlers get the whole site, not just the static paths.
|
|
151
|
+
if (baseUrl !== undefined) {
|
|
152
|
+
const ts = await loadTypeScript(cfg.root);
|
|
153
|
+
const staticPages: LlmsPage[] = allRoutes
|
|
154
|
+
.filter((r) => r.slot === undefined && !r.intercept && !/[:*]/.test(r.pattern))
|
|
155
|
+
.map((r): LlmsPage => {
|
|
156
|
+
const meta = ts ? extractStaticMetadata(ts, r.file) : null;
|
|
157
|
+
return {
|
|
158
|
+
title: metaString(meta, 'title') ?? (r.pattern === '/' ? 'Home' : r.pattern),
|
|
159
|
+
url: joinUrl(baseUrl, r.pattern),
|
|
160
|
+
description: metaString(meta, 'description'),
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
const pages = [...staticPages, ...dynamicPages];
|
|
164
|
+
if (pages.length > 0) {
|
|
165
|
+
const llms = llmsTxt(cfg.seo, allRoutes, pages);
|
|
166
|
+
if (llms) fs.writeFileSync(path.join(outDir, 'llms.txt'), llms);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return generated;
|
|
171
|
+
}
|
package/src/compiler/vite.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import { createRequire } from 'node:module';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { pathToFileURL } from 'node:url';
|
|
@@ -41,6 +42,36 @@ const IMAGE_EXT = /^(png|jpe?g|svg|gif|tiff|bmp|ico|webp|avif)$/i;
|
|
|
41
42
|
/** Font extensions routed to `fonts/`. */
|
|
42
43
|
const FONT_EXT = /^(woff|woff2|eot|ttf|otf)$/i;
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Resolves bare `shared/*` imports to the project's `shared/` folder (replacing a plain alias so it
|
|
47
|
+
* can run early), and fails `shared/server` with an actionable message when the module has not been
|
|
48
|
+
* generated yet (it comes from the server build, which must run first), instead of Vite's opaque
|
|
49
|
+
* `UNLOADABLE_DEPENDENCY`.
|
|
50
|
+
*/
|
|
51
|
+
function sharedResolverPlugin(cfg: ResolvedToilConfig): PluginOption {
|
|
52
|
+
const sharedDir = path.join(cfg.root, 'shared');
|
|
53
|
+
return {
|
|
54
|
+
name: 'toiljs:shared-resolver',
|
|
55
|
+
enforce: 'pre',
|
|
56
|
+
resolveId(source: string) {
|
|
57
|
+
if (source !== 'shared' && !source.startsWith('shared/')) return null;
|
|
58
|
+
const rel = source === 'shared' ? 'index' : source.slice('shared/'.length);
|
|
59
|
+
for (const ext of ['', '.ts', '.tsx', '.js', '.jsx', '.mjs']) {
|
|
60
|
+
const candidate = path.join(sharedDir, rel + ext);
|
|
61
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
62
|
+
}
|
|
63
|
+
if (source === 'shared/server') {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'toiljs: "shared/server" is generated by the server build but is missing. ' +
|
|
66
|
+
'Run the server build first (it emits shared/server.ts from your @data/@remote code): ' +
|
|
67
|
+
'`npm run build:server` (toilscript --target release --rpcModule shared/server.ts).',
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
44
75
|
/** Routes a built asset to a typed sub-folder (`images/`, `fonts/`, `css/`, else `assets/`). */
|
|
45
76
|
function assetFileName(name: string): string {
|
|
46
77
|
const ext = name.split('.').pop() ?? '';
|
|
@@ -116,12 +147,15 @@ export async function createViteConfig(cfg: ResolvedToilConfig): Promise<InlineC
|
|
|
116
147
|
cfg.fonts ? fontPreloadPlugin(cfg) : undefined,
|
|
117
148
|
nodePolyfills({ globals: { Buffer: true, global: true, process: true } }),
|
|
118
149
|
react(),
|
|
150
|
+
sharedResolverPlugin(cfg),
|
|
119
151
|
toilPlugin(cfg),
|
|
120
152
|
],
|
|
121
153
|
resolve: {
|
|
122
154
|
alias: {
|
|
123
155
|
'toiljs/client': cfg.runtimePath,
|
|
124
156
|
'toiljs/routes': path.join(cfg.toilDir, 'routes.ts'),
|
|
157
|
+
// `shared/*` is resolved by sharedResolverPlugin (above) so a missing generated
|
|
158
|
+
// shared/server.ts gives an actionable error instead of an opaque load failure.
|
|
125
159
|
...polyfillShimAliases,
|
|
126
160
|
},
|
|
127
161
|
dedupe: ['react', 'react-dom'],
|
package/src/io/FastMap.ts
CHANGED
|
@@ -1,127 +1,151 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
public
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
1
|
+
/** A key usable with {@link FastMap}: any `PropertyKey` plus `bigint`. */
|
|
2
|
+
export type PropertyExtendedKey = PropertyKey | bigint;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Like Record, but supports bigint keys (which JS auto-converts to strings).
|
|
6
|
+
* Reflects actual JavaScript behavior where obj[123n] becomes obj["123"].
|
|
7
|
+
*/
|
|
8
|
+
export type FastRecord<V> = {
|
|
9
|
+
[key: string]: V;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** The string/number form a key takes once used to index the backing object. */
|
|
13
|
+
export type IndexKey = string | number;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* An insertion-ordered map backed by a key array plus a plain object, supporting
|
|
17
|
+
* `bigint` keys (coerced to their string form, like native property access). Exposed
|
|
18
|
+
* to the client as a global (no import). Implements `Disposable`, so a `using` binding
|
|
19
|
+
* clears it on scope exit.
|
|
20
|
+
*/
|
|
21
|
+
export class FastMap<K extends PropertyExtendedKey, V> implements Disposable {
|
|
22
|
+
protected _keys: K[] = [];
|
|
23
|
+
protected _values: FastRecord<V> = {};
|
|
24
|
+
|
|
25
|
+
/** @param iterable - initial entries, or another FastMap to copy. */
|
|
26
|
+
constructor(iterable?: ReadonlyArray<readonly [K, V]> | null | FastMap<K, V>) {
|
|
27
|
+
if (iterable instanceof FastMap) {
|
|
28
|
+
this.setAll(iterable);
|
|
29
|
+
} else {
|
|
30
|
+
if (iterable) {
|
|
31
|
+
for (const [key, value] of iterable) {
|
|
32
|
+
this.set(key, value);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Number of entries. */
|
|
39
|
+
public get size(): number {
|
|
40
|
+
return this._keys.length;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Replaces all entries with a copy of `map`'s entries. */
|
|
44
|
+
public setAll(map: FastMap<K, V>): void {
|
|
45
|
+
this._keys = [...map._keys];
|
|
46
|
+
this._values = { ...map._values };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Merges `map`'s entries into this one (existing keys are overwritten). */
|
|
50
|
+
public addAll(map: FastMap<K, V>): void {
|
|
51
|
+
for (const [key, value] of map.entries()) {
|
|
52
|
+
this.set(key, value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Iterates the keys in insertion order. */
|
|
57
|
+
public *keys(): IterableIterator<K> {
|
|
58
|
+
yield* this._keys;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Iterates the values in key-insertion order. */
|
|
62
|
+
public *values(): IterableIterator<V> {
|
|
63
|
+
for (const key of this._keys) {
|
|
64
|
+
yield this._values[key as IndexKey] as V;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Iterates `[key, value]` pairs in insertion order. */
|
|
69
|
+
public *entries(): IterableIterator<[K, V]> {
|
|
70
|
+
for (const key of this._keys) {
|
|
71
|
+
yield [key, this._values[key as IndexKey] as V];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Sets `key` to `value` (appending the key if new), and returns `this` for chaining. */
|
|
76
|
+
public set(key: K, value: V): this {
|
|
77
|
+
if (!this.has(key)) {
|
|
78
|
+
this._keys.push(key);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this._values[key as IndexKey] = value;
|
|
82
|
+
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Returns the insertion index of `key`, or -1 if absent. */
|
|
87
|
+
public indexOf(key: K): number {
|
|
88
|
+
if (!this.has(key)) {
|
|
89
|
+
return -1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < this._keys.length; i++) {
|
|
93
|
+
if (this._keys[i] === key) {
|
|
94
|
+
return i;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new Error('Key not found, this should not happen.');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Returns the value for `key`, or `undefined` if absent. */
|
|
102
|
+
public get(key: K): V | undefined {
|
|
103
|
+
return this._values[key as IndexKey];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Whether `key` is present. */
|
|
107
|
+
public has(key: K): boolean {
|
|
108
|
+
return Object.prototype.hasOwnProperty.call(this._values, key as IndexKey);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Removes `key`; returns true if it was present. */
|
|
112
|
+
public delete(key: K): boolean {
|
|
113
|
+
if (!this.has(key)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const index = this.indexOf(key);
|
|
118
|
+
this._keys.splice(index, 1);
|
|
119
|
+
|
|
120
|
+
delete this._values[key as IndexKey];
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Removes all entries. */
|
|
125
|
+
public clear(): void {
|
|
126
|
+
this._keys = [];
|
|
127
|
+
this._values = {};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** `Disposable` hook: clears the map (so `using m = new FastMap()` frees it on scope exit). */
|
|
131
|
+
public [Symbol.dispose](): void {
|
|
132
|
+
this.clear();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Calls `callback(value, key, map)` for each entry in insertion order. */
|
|
136
|
+
public forEach(
|
|
137
|
+
callback: (value: V, key: K, map: FastMap<K, V>) => void,
|
|
138
|
+
thisArg?: unknown,
|
|
139
|
+
): void {
|
|
140
|
+
for (const key of this._keys) {
|
|
141
|
+
callback.call(thisArg, this._values[key as IndexKey] as V, key, this);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Default iterator: `[key, value]` pairs in insertion order. */
|
|
146
|
+
*[Symbol.iterator](): IterableIterator<[K, V]> {
|
|
147
|
+
for (const key of this._keys) {
|
|
148
|
+
yield [key, this._values[key as IndexKey] as V];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|