waku 1.0.0-beta.5 → 1.0.0-beta.6
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/README.md +16 -16
- package/dist/lib/global-types.d.ts +1 -1
- package/dist/lib/vite-plugins/fs-router-typegen.js +18 -2
- package/dist/lib/vite-plugins/fs-router-typegen.js.map +1 -1
- package/dist/lib/vite-rsc/handler.js +2 -2
- package/dist/lib/vite-rsc/handler.js.map +1 -1
- package/dist/minimal/client-utils/prefetch-cache.js.map +1 -0
- package/dist/minimal/client.d.ts +17 -1
- package/dist/minimal/client.js +46 -14
- package/dist/minimal/client.js.map +1 -1
- package/dist/router/base-types.d.ts +13 -2
- package/dist/router/base-types.js +6 -1
- package/dist/router/base-types.js.map +1 -1
- package/dist/router/client.d.ts +24 -14
- package/dist/router/client.js +126 -67
- package/dist/router/client.js.map +1 -1
- package/dist/router/create-pages-utils/inferred-path-types.d.ts +7 -1
- package/dist/router/create-pages-utils/inferred-path-types.js.map +1 -1
- package/dist/router/create-pages.d.ts +5 -5
- package/dist/router/create-pages.js +2 -2
- package/dist/router/create-pages.js.map +1 -1
- package/dist/router/define-router.d.ts +8 -4
- package/dist/router/define-router.js +43 -40
- package/dist/router/define-router.js.map +1 -1
- package/dist/router/fs-router.d.ts +2 -1
- package/dist/router/{client-utils → isomorphic-utils}/build-route-href.d.ts +6 -1
- package/dist/router/isomorphic-utils/build-route-href.js.map +1 -0
- package/dist/router/isomorphic-utils/match-route-params.js.map +1 -0
- package/dist/router/{common-utils → isomorphic-utils}/route-path.d.ts +1 -0
- package/dist/router/{common-utils → isomorphic-utils}/route-path.js +3 -0
- package/dist/router/isomorphic-utils/route-path.js.map +1 -0
- package/dist/router/isomorphic-utils/search-codec-registry.js.map +1 -0
- package/package.json +1 -1
- package/dist/lib/utils/prefetch-cache.js.map +0 -1
- package/dist/router/client-utils/build-route-href.js.map +0 -1
- package/dist/router/client-utils/match-route-params.js.map +0 -1
- package/dist/router/client-utils/search-codec-registry.js.map +0 -1
- package/dist/router/common-utils/route-path.js.map +0 -1
- /package/dist/{lib/utils → minimal/client-utils}/prefetch-cache.d.ts +0 -0
- /package/dist/{lib/utils → minimal/client-utils}/prefetch-cache.js +0 -0
- /package/dist/router/{client-utils → isomorphic-utils}/build-route-href.js +0 -0
- /package/dist/router/{client-utils → isomorphic-utils}/match-route-params.d.ts +0 -0
- /package/dist/router/{client-utils → isomorphic-utils}/match-route-params.js +0 -0
- /package/dist/router/{client-utils → isomorphic-utils}/search-codec-registry.d.ts +0 -0
- /package/dist/router/{client-utils → isomorphic-utils}/search-codec-registry.js +0 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ visit [waku.gg](https://waku.gg) or `npm create waku@latest`
|
|
|
13
13
|
|
|
14
14
|
## Introduction
|
|
15
15
|
|
|
16
|
-
**Waku** _(wah-ku)_ or **わく** is the minimal React framework. It
|
|
16
|
+
**Waku** _(wah-ku)_ or **わく** is the minimal React framework. It's lightweight and designed for a fun developer experience, yet supports all the latest React 19 features like server components and actions. Built for marketing sites, headless commerce, and web apps. For large enterprise applications, you may prefer a heavier framework.
|
|
17
17
|
|
|
18
18
|
## Getting started
|
|
19
19
|
|
|
@@ -33,17 +33,17 @@ npm create waku@latest
|
|
|
33
33
|
|
|
34
34
|
## Rendering
|
|
35
35
|
|
|
36
|
-
While there
|
|
36
|
+
While there's a bit of a learning curve to modern React rendering, it introduces powerful new patterns of full-stack composability that are only possible with the advent of [server components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md).
|
|
37
37
|
|
|
38
|
-
So please don
|
|
38
|
+
So please don't be intimidated by the `'use client'` directive! Once you get the hang of it, you'll appreciate how awesome it is to flexibly move server-client boundaries with a single line of code as your full-stack React codebase evolves over time. It's way simpler than maintaining separate codebases for your backend and frontend.
|
|
39
39
|
|
|
40
|
-
And please don
|
|
40
|
+
And please don't fret about client components! Even if you only lightly optimize towards server components, your client bundle size will be smaller than traditional React frameworks, which are always 100% client components.
|
|
41
41
|
|
|
42
42
|
> Future versions of Waku may provide additional opt-in APIs to abstract some of the complexity away for an improved developer experience.
|
|
43
43
|
|
|
44
44
|
#### Server components
|
|
45
45
|
|
|
46
|
-
Server components can be made async and can securely perform server-side logic and data fetching. Feel free to access the local file-system and import heavy dependencies since they aren
|
|
46
|
+
Server components can be made async and can securely perform server-side logic and data fetching. Feel free to access the local file-system and import heavy dependencies since they aren't included in the client bundle. They have no state, interactivity, or access to browser APIs since they run _exclusively_ on the server.
|
|
47
47
|
|
|
48
48
|
```tsx
|
|
49
49
|
// server component
|
|
@@ -133,9 +133,9 @@ Waku provides static prerendering (SSG) and server-side rendering (SSR) options
|
|
|
133
133
|
|
|
134
134
|
Each layout and page in Waku is composed of a React component hierarchy.
|
|
135
135
|
|
|
136
|
-
It begins with a server component at the top of the tree. Then at points down the hierarchy, you
|
|
136
|
+
It begins with a server component at the top of the tree. Then at points down the hierarchy, you'll eventually import a component that needs client component APIs. Mark this file with a `'use client'` directive at the top. When imported into a server component, it will create a server-client boundary. Below this point, all imported components are hydrated and will run in the browser as well.
|
|
137
137
|
|
|
138
|
-
Server components can be rendered below this boundary, but only via composition (e.g., `children` props). Together they form [a new
|
|
138
|
+
Server components can be rendered below this boundary, but only via composition (e.g., `children` props). Together they form [a new "React server" layer](https://github.com/reactwg/server-components/discussions/4) that runs _before_ the traditional "React client" layer with which you're already familiar.
|
|
139
139
|
|
|
140
140
|
Client components are still server-side rendered as SSR is separate from RSC. Please see the [linked diagrams](https://github.com/reactwg/server-components/discussions/4) for a helpful visual.
|
|
141
141
|
|
|
@@ -145,7 +145,7 @@ To learn more about the modern React architecture, we recommend [Making Sense of
|
|
|
145
145
|
|
|
146
146
|
## Routing
|
|
147
147
|
|
|
148
|
-
Waku provides a minimal file-based
|
|
148
|
+
Waku provides a minimal file-based "pages router" experience built for the server components era.
|
|
149
149
|
|
|
150
150
|
Its underlying [low-level API](https://github.com/wakujs/waku/blob/main/docs/create-pages.mdx) is also available for those that prefer programmatic routing. This documentation covers file-based routing since many React developers prefer it, but please feel free to try both and see which you like more!
|
|
151
151
|
|
|
@@ -395,7 +395,7 @@ export const getConfig = async () => {
|
|
|
395
395
|
|
|
396
396
|
#### Catch-all routes
|
|
397
397
|
|
|
398
|
-
Catch-all or
|
|
398
|
+
Catch-all or "wildcard" segment routes (e.g., `/app/[...catchAll]`) are marked with an ellipsis before the name and have indefinite segments.
|
|
399
399
|
|
|
400
400
|
Wildcard routes receive a prop with segment values as an ordered array. For example, the `/app/profile/settings` route would receive a `catchAll` prop with the value `['profile', 'settings']`. These values can then be used to determine what to render in the component.
|
|
401
401
|
|
|
@@ -1250,7 +1250,7 @@ export const ContactForm = () => {
|
|
|
1250
1250
|
|
|
1251
1251
|
#### Configuring API routes
|
|
1252
1252
|
|
|
1253
|
-
API routes are dynamic by default, but if you
|
|
1253
|
+
API routes are dynamic by default, but if you're using them to create a static resource such as an XML document, you can export a `getConfig` function that returns a config object with the render property set to `'static'`.
|
|
1254
1254
|
|
|
1255
1255
|
```ts
|
|
1256
1256
|
// ./src/pages/_api/blog/rss.xml.ts
|
|
@@ -1413,17 +1413,17 @@ export const ContactForm = ({ author = 'guest' }) => {
|
|
|
1413
1413
|
|
|
1414
1414
|
Server actions integrate with many other React APIs such as the [`useTransition`](https://react.dev/reference/react/useTransition) hook for handling pending states, the [`useActionState`](https://react.dev/reference/react/useActionState) hook for accessing returned values, and the [`useOptimistic`](https://react.dev/reference/react/useOptimistic) hook for performing optimistic UI updates.
|
|
1415
1415
|
|
|
1416
|
-
See the talk [What
|
|
1416
|
+
See the talk [What's new in React 19?](https://www.youtube.com/watch?v=AJOGzVygGcY) to learn more.
|
|
1417
1417
|
|
|
1418
1418
|
## State management
|
|
1419
1419
|
|
|
1420
|
-
We recommend [Jotai](https://jotai.org) for global React state management based on the atomic model
|
|
1420
|
+
We recommend [Jotai](https://jotai.org) for global React state management based on the atomic model's performance and scalability, but Waku is compatible with all React state management libraries such as Zustand and Valtio.
|
|
1421
1421
|
|
|
1422
|
-
> We
|
|
1422
|
+
> We're exploring a deeper integration of atomic state management into Waku to achieve the performance and developer experience of signals while preserving React's declarative programming model.
|
|
1423
1423
|
|
|
1424
1424
|
## Environment variables
|
|
1425
1425
|
|
|
1426
|
-
It
|
|
1426
|
+
It's important to distinguish environment variables that must be kept secret from those that can be made public.
|
|
1427
1427
|
|
|
1428
1428
|
#### Private
|
|
1429
1429
|
|
|
@@ -1431,7 +1431,7 @@ By default all environment variables are considered private and are accessible o
|
|
|
1431
1431
|
|
|
1432
1432
|
#### Public
|
|
1433
1433
|
|
|
1434
|
-
A special `WAKU_PUBLIC_` prefix is required to make an environment variable public and accessible in client components. They will be present as cleartext in the production JavaScript bundle sent to users
|
|
1434
|
+
A special `WAKU_PUBLIC_` prefix is required to make an environment variable public and accessible in client components. They will be present as cleartext in the production JavaScript bundle sent to users' browsers.
|
|
1435
1435
|
|
|
1436
1436
|
### Runtime agnostic (recommended)
|
|
1437
1437
|
|
|
@@ -1616,7 +1616,7 @@ Please join our friendly [GitHub discussions](https://github.com/wakujs/waku/dis
|
|
|
1616
1616
|
|
|
1617
1617
|
## Roadmap
|
|
1618
1618
|
|
|
1619
|
-
Waku is in active development and we
|
|
1619
|
+
Waku is in active development and we're seeking additional contributors. Check out our [roadmap](https://github.com/wakujs/waku/issues/24) for more information.
|
|
1620
1620
|
|
|
1621
1621
|
## Contributing
|
|
1622
1622
|
|
|
@@ -4,7 +4,7 @@ declare global {
|
|
|
4
4
|
var __WAKU_REFETCH_ROUTE__: (() => void) | undefined;
|
|
5
5
|
var __WAKU_START_PREVIEW_SERVER__: (() => Promise<import('./vite-rsc/preview.js').PreviewServer>) | undefined;
|
|
6
6
|
var __WAKU_SERVER_ENV__: Readonly<Record<string, string>> | undefined;
|
|
7
|
-
var __WAKU_PREFETCHED__: import('
|
|
7
|
+
var __WAKU_PREFETCHED__: import('../minimal/client-utils/prefetch-cache.js').PrefetchEntry[] | undefined;
|
|
8
8
|
var __WAKU_INITIAL_RSC__: import('./utils/initial-rsc.js').InitialRscEntry | undefined;
|
|
9
9
|
var __WAKU_DEBUG_CHANNELS__: Map<string, {
|
|
10
10
|
readable: ReadableStream;
|
|
@@ -160,6 +160,7 @@ export async function generateFsRouterTypes(pagesDir) {
|
|
|
160
160
|
};
|
|
161
161
|
const generateFile = async (filePaths)=>{
|
|
162
162
|
const fileInfo = [];
|
|
163
|
+
const layoutPaths = [];
|
|
163
164
|
const moduleNames = getImportModuleNames(filePaths);
|
|
164
165
|
for (const filePath of filePaths){
|
|
165
166
|
// where to import the component from
|
|
@@ -170,8 +171,11 @@ export async function generateFsRouterTypes(pagesDir) {
|
|
|
170
171
|
} catch {
|
|
171
172
|
return null;
|
|
172
173
|
}
|
|
173
|
-
if (
|
|
174
|
+
if (isIgnoredPath(filePath.split('/'))) {
|
|
174
175
|
continue;
|
|
176
|
+
} else if (filePath.endsWith('/_layout.tsx')) {
|
|
177
|
+
const path = filePath.slice(0, -'/_layout.tsx'.length);
|
|
178
|
+
layoutPaths.push(getGrouplessPath(path) || '/');
|
|
175
179
|
} else if (filePath.endsWith('/index.tsx')) {
|
|
176
180
|
const path = filePath.slice(0, -'/index.tsx'.length);
|
|
177
181
|
fileInfo.push({
|
|
@@ -214,7 +218,19 @@ export async function generateFsRouterTypes(pagesDir) {
|
|
|
214
218
|
}
|
|
215
219
|
}
|
|
216
220
|
lines[lines.length - 1] += ';';
|
|
217
|
-
|
|
221
|
+
const uniqueLayoutPaths = [
|
|
222
|
+
...new Set(layoutPaths)
|
|
223
|
+
];
|
|
224
|
+
if (uniqueLayoutPaths.length) {
|
|
225
|
+
lines.push('', '// prettier-ignore', 'type Layout =');
|
|
226
|
+
for (const path of uniqueLayoutPaths){
|
|
227
|
+
lines.push(`| { path: '${path}' }`);
|
|
228
|
+
}
|
|
229
|
+
lines[lines.length - 1] += ';';
|
|
230
|
+
}
|
|
231
|
+
lines.push('', '// prettier-ignore', "declare module 'waku/router' {", ' interface RouteConfig {', ' paths: PathsForPages<Page>;', ' }', ' interface CreatePagesConfig {', ' pages: Page;', ...uniqueLayoutPaths.length ? [
|
|
232
|
+
' layouts: Layout;'
|
|
233
|
+
] : [], ' }', ...hasAnyGetConfig ? [
|
|
218
234
|
' interface SearchCodecsConfig extends SearchCodecsForPages<Page> {}'
|
|
219
235
|
] : [], '}', '');
|
|
220
236
|
const result = lines.join('\n');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/vite-plugins/fs-router-typegen.ts"],"sourcesContent":["// waku/router is technically a separate library from waku core.\n// This file is an exception placed here so that fsRouter users\n// get automatic type generation with better DX.\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { readdir, writeFile } from 'node:fs/promises';\nimport { parse, transformWithOxc } from 'vite';\nimport type { ParseResult, Plugin } from 'vite';\nimport { EXTENSIONS, SRC_PAGES, SRC_SERVER_ENTRY } from '../constants.js';\nimport { getGrouplessPath } from '../utils/create-pages.js';\nimport { isIgnoredPath } from '../utils/fs-router.js';\nimport { joinPath } from '../utils/path.js';\n\ntype ProgramNode = ParseResult['program'];\ntype ImportDeclaration = ProgramNode['body'][number] & {\n type: 'ImportDeclaration';\n};\ntype ImportSpecifier = ImportDeclaration['specifiers'][number] & {\n type: 'ImportSpecifier';\n};\ntype ExportNamedDeclaration = ProgramNode['body'][number] & {\n type: 'ExportNamedDeclaration';\n};\ntype ExportSpecifier = ExportNamedDeclaration['specifiers'][number] & {\n type: 'ExportSpecifier';\n};\n\n// https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-names-and-keywords\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words\nexport function toIdentifier(input: string): string {\n // Strip the file extension\n let identifier = input.includes('.')\n ? input.split('.').slice(0, -1).join('.')\n : input;\n // Replace any characters besides letters, numbers, underscores, and dollar signs with underscores\n identifier = identifier.replace(/[^\\p{L}\\p{N}_$]/gu, '_');\n // Ensure it starts with a letter\n if (/^\\d/.test(identifier)) {\n identifier = '_' + identifier;\n }\n // Turn it into PascalCase\n // Since the first letter is uppercased, it will not be a reserved word\n return (\n 'File_' +\n identifier\n .split('_')\n .map((part) => {\n if (part[0] === undefined) {\n return '';\n }\n return part[0].toUpperCase() + part.slice(1);\n })\n .join('')\n );\n}\n\nexport function getImportModuleNames(filePaths: string[]): {\n [k: string]: string;\n} {\n const moduleNameCount: { [k: string]: number } = {};\n const moduleNames: { [k: string]: string } = {};\n for (const filePath of filePaths) {\n let identifier = toIdentifier(filePath);\n moduleNameCount[identifier] = (moduleNameCount[identifier] ?? -1) + 1;\n if (moduleNameCount[identifier]) {\n identifier = `${identifier}_${moduleNameCount[identifier]}`;\n }\n try {\n moduleNames[filePath.replace(/^\\//, '')] = identifier;\n } catch (e) {\n console.log(e);\n }\n }\n return moduleNames;\n}\n\nconst parseModule = async (filePath: string) => {\n const source = readFileSync(filePath, 'utf8');\n const lang: 'jsx' | 'ts' | 'tsx' = filePath.endsWith('.tsx')\n ? 'tsx'\n : filePath.endsWith('.ts')\n ? 'ts'\n : 'jsx';\n const transformed = await transformWithOxc(source, filePath, {\n lang,\n jsx: 'preserve',\n });\n return (await parse(filePath, transformed.code, { lang } as never)).program;\n};\n\nconst getImportedName = (specifier: ImportSpecifier) =>\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : String(specifier.imported.value);\n\nconst getExportedName = (specifier: ExportSpecifier) =>\n specifier.exported.type === 'Identifier'\n ? specifier.exported.name\n : String(specifier.exported.value);\n\nexport const fsRouterTypegenPlugin = (opts: { srcDir: string }): Plugin => {\n return {\n name: 'waku:vite-plugins:fs-router-typegen',\n apply: 'serve',\n configureServer(server) {\n const srcDir = joinPath(server.config.root, opts.srcDir);\n const pagesDir = joinPath(srcDir, SRC_PAGES);\n\n const outputFile = joinPath(srcDir, 'pages.gen.ts');\n const updateGeneratedFile = async (file: string | undefined) => {\n // skip when the changed file is the generated file itself\n if (file && outputFile.endsWith(file)) {\n return;\n }\n // skip when the entries file exists or pages dir does not exist\n if (!existsSync(pagesDir) || !(await detectFsRouterUsage(srcDir))) {\n return;\n }\n const generation = await generateFsRouterTypes(pagesDir);\n if (!generation) {\n // skip failures\n return;\n }\n await writeFile(outputFile, generation, 'utf-8');\n };\n\n server.watcher.on('change', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('add', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('unlink', async (file) => {\n await updateGeneratedFile(file);\n });\n void updateGeneratedFile(undefined);\n },\n };\n};\n\nexport async function detectFsRouterUsage(srcDir: string): Promise<boolean> {\n const existingServerEntry = EXTENSIONS.map((ext) =>\n joinPath(srcDir, SRC_SERVER_ENTRY + ext),\n ).find((entriesFile) => existsSync(entriesFile));\n\n // managed mode if no entry\n if (!existingServerEntry) {\n return true;\n }\n\n try {\n const file = await parseModule(existingServerEntry);\n const usesFsRouter = file.body.some((node) => {\n if (node.type !== 'ImportDeclaration') {\n return false;\n }\n if (\n node.source.type !== 'Literal' ||\n typeof node.source.value !== 'string' ||\n !node.source.value.startsWith('waku')\n ) {\n return false;\n }\n return node.specifiers.some((specifier) => {\n if (\n specifier.type !== 'ImportSpecifier' ||\n specifier.local.type !== 'Identifier'\n ) {\n return false;\n }\n return getImportedName(specifier) === 'fsRouter';\n });\n });\n return usesFsRouter;\n } catch {\n return false;\n }\n}\n\nexport async function generateFsRouterTypes(pagesDir: string) {\n // Recursively collect `.tsx` files in the given directory\n const collectFiles = async (\n dir: string,\n files: string[] = [],\n ): Promise<string[]> => {\n // TODO revisit recursive option for readdir once more stable\n // https://nodejs.org/docs/latest-v20.x/api/fs.html#direntparentpath\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = joinPath(dir, entry.name);\n if (entry.isDirectory()) {\n await collectFiles(fullPath, files);\n } else {\n if (entry.name.endsWith('.tsx')) {\n files.push(fullPath.slice(pagesDir.length));\n }\n }\n }\n return files;\n };\n\n const fileExportsGetConfig = async (filePath: string) => {\n const file = await parseModule(pagesDir + filePath);\n return file.body.some((node) => {\n if (node.type !== 'ExportNamedDeclaration') {\n return false;\n }\n if (\n node.declaration?.type === 'VariableDeclaration' &&\n node.declaration.declarations.some(\n (decl) =>\n decl.id.type === 'Identifier' && decl.id.name === 'getConfig',\n )\n ) {\n return true;\n }\n if (\n node.declaration?.type === 'FunctionDeclaration' &&\n node.declaration.id?.name === 'getConfig'\n ) {\n return true;\n }\n return node.specifiers.some(\n (specifier) =>\n specifier.type === 'ExportSpecifier' &&\n getExportedName(specifier) === 'getConfig',\n );\n });\n };\n\n const generateFile = async (filePaths: string[]): Promise<string | null> => {\n const fileInfo: { path: string; src: string; hasGetConfig: boolean }[] = [];\n const moduleNames = getImportModuleNames(filePaths);\n\n for (const filePath of filePaths) {\n // where to import the component from\n const src = filePath.replace(/^\\//, '');\n let hasGetConfig: boolean;\n try {\n hasGetConfig = await fileExportsGetConfig(filePath);\n } catch {\n return null;\n }\n\n if (\n filePath.endsWith('/_layout.tsx') ||\n isIgnoredPath(filePath.split('/'))\n ) {\n continue;\n } else if (filePath.endsWith('/index.tsx')) {\n const path = filePath.slice(0, -'/index.tsx'.length);\n fileInfo.push({\n path: getGrouplessPath(path) || '/',\n src,\n hasGetConfig,\n });\n } else {\n fileInfo.push({\n path: getGrouplessPath(filePath.replace('.tsx', '')),\n src,\n hasGetConfig,\n });\n }\n }\n\n const hasAnyGetConfig = fileInfo.some((file) => file.hasGetConfig);\n const lines: string[] = [\n '// deno-fmt-ignore-file',\n '// biome-ignore format: generated types do not need formatting',\n '// prettier-ignore',\n hasAnyGetConfig\n ? \"import type { PathsForPages, GetConfigResponse, SearchCodecsForPages } from 'waku/router';\"\n : \"import type { PathsForPages } from 'waku/router';\",\n ];\n\n if (hasAnyGetConfig) {\n lines.push('');\n for (const file of fileInfo) {\n if (!file.hasGetConfig) {\n continue;\n }\n const moduleName = moduleNames[file.src];\n lines.push(\n `// prettier-ignore`,\n `import type { getConfig as ${moduleName}_getConfig } from './${SRC_PAGES}/${file.src.replace('.tsx', '')}';`,\n );\n }\n }\n\n lines.push('', '// prettier-ignore', 'type Page =');\n for (const file of fileInfo) {\n const moduleName = moduleNames[file.src];\n if (file.hasGetConfig) {\n lines.push(\n `| ({ path: '${file.path}' } & GetConfigResponse<typeof ${moduleName}_getConfig>)`,\n );\n } else {\n lines.push(`| { path: '${file.path}'; render: 'static' }`);\n }\n }\n lines[lines.length - 1] += ';';\n\n lines.push(\n '',\n '// prettier-ignore',\n \"declare module 'waku/router' {\",\n ' interface RouteConfig {',\n ' paths: PathsForPages<Page>;',\n ' }',\n ' interface CreatePagesConfig {',\n ' pages: Page;',\n ' }',\n ...(hasAnyGetConfig\n ? [\n ' interface SearchCodecsConfig extends SearchCodecsForPages<Page> {}',\n ]\n : []),\n '}',\n '',\n );\n\n const result = lines.join('\\n');\n\n return result;\n };\n\n const files = await collectFiles(pagesDir);\n if (!files.length) {\n return;\n }\n const generation = await generateFile(files);\n return generation;\n}\n"],"names":["existsSync","readFileSync","readdir","writeFile","parse","transformWithOxc","EXTENSIONS","SRC_PAGES","SRC_SERVER_ENTRY","getGrouplessPath","isIgnoredPath","joinPath","toIdentifier","input","identifier","includes","split","slice","join","replace","test","map","part","undefined","toUpperCase","getImportModuleNames","filePaths","moduleNameCount","moduleNames","filePath","e","console","log","parseModule","source","lang","endsWith","transformed","jsx","code","program","getImportedName","specifier","imported","type","name","String","value","getExportedName","exported","fsRouterTypegenPlugin","opts","apply","configureServer","server","srcDir","config","root","pagesDir","outputFile","updateGeneratedFile","file","detectFsRouterUsage","generation","generateFsRouterTypes","watcher","on","existingServerEntry","ext","find","entriesFile","usesFsRouter","body","some","node","startsWith","specifiers","local","collectFiles","dir","files","entries","withFileTypes","entry","fullPath","isDirectory","push","length","fileExportsGetConfig","declaration","declarations","decl","id","generateFile","fileInfo","src","hasGetConfig","path","hasAnyGetConfig","lines","moduleName","result"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/vite-plugins/fs-router-typegen.ts"],"sourcesContent":["// waku/router is technically a separate library from waku core.\n// This file is an exception placed here so that fsRouter users\n// get automatic type generation with better DX.\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { readdir, writeFile } from 'node:fs/promises';\nimport { parse, transformWithOxc } from 'vite';\nimport type { ParseResult, Plugin } from 'vite';\nimport { EXTENSIONS, SRC_PAGES, SRC_SERVER_ENTRY } from '../constants.js';\nimport { getGrouplessPath } from '../utils/create-pages.js';\nimport { isIgnoredPath } from '../utils/fs-router.js';\nimport { joinPath } from '../utils/path.js';\n\ntype ProgramNode = ParseResult['program'];\ntype ImportDeclaration = ProgramNode['body'][number] & {\n type: 'ImportDeclaration';\n};\ntype ImportSpecifier = ImportDeclaration['specifiers'][number] & {\n type: 'ImportSpecifier';\n};\ntype ExportNamedDeclaration = ProgramNode['body'][number] & {\n type: 'ExportNamedDeclaration';\n};\ntype ExportSpecifier = ExportNamedDeclaration['specifiers'][number] & {\n type: 'ExportSpecifier';\n};\n\n// https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-names-and-keywords\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words\nexport function toIdentifier(input: string): string {\n // Strip the file extension\n let identifier = input.includes('.')\n ? input.split('.').slice(0, -1).join('.')\n : input;\n // Replace any characters besides letters, numbers, underscores, and dollar signs with underscores\n identifier = identifier.replace(/[^\\p{L}\\p{N}_$]/gu, '_');\n // Ensure it starts with a letter\n if (/^\\d/.test(identifier)) {\n identifier = '_' + identifier;\n }\n // Turn it into PascalCase\n // Since the first letter is uppercased, it will not be a reserved word\n return (\n 'File_' +\n identifier\n .split('_')\n .map((part) => {\n if (part[0] === undefined) {\n return '';\n }\n return part[0].toUpperCase() + part.slice(1);\n })\n .join('')\n );\n}\n\nexport function getImportModuleNames(filePaths: string[]): {\n [k: string]: string;\n} {\n const moduleNameCount: { [k: string]: number } = {};\n const moduleNames: { [k: string]: string } = {};\n for (const filePath of filePaths) {\n let identifier = toIdentifier(filePath);\n moduleNameCount[identifier] = (moduleNameCount[identifier] ?? -1) + 1;\n if (moduleNameCount[identifier]) {\n identifier = `${identifier}_${moduleNameCount[identifier]}`;\n }\n try {\n moduleNames[filePath.replace(/^\\//, '')] = identifier;\n } catch (e) {\n console.log(e);\n }\n }\n return moduleNames;\n}\n\nconst parseModule = async (filePath: string) => {\n const source = readFileSync(filePath, 'utf8');\n const lang: 'jsx' | 'ts' | 'tsx' = filePath.endsWith('.tsx')\n ? 'tsx'\n : filePath.endsWith('.ts')\n ? 'ts'\n : 'jsx';\n const transformed = await transformWithOxc(source, filePath, {\n lang,\n jsx: 'preserve',\n });\n return (await parse(filePath, transformed.code, { lang } as never)).program;\n};\n\nconst getImportedName = (specifier: ImportSpecifier) =>\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : String(specifier.imported.value);\n\nconst getExportedName = (specifier: ExportSpecifier) =>\n specifier.exported.type === 'Identifier'\n ? specifier.exported.name\n : String(specifier.exported.value);\n\nexport const fsRouterTypegenPlugin = (opts: { srcDir: string }): Plugin => {\n return {\n name: 'waku:vite-plugins:fs-router-typegen',\n apply: 'serve',\n configureServer(server) {\n const srcDir = joinPath(server.config.root, opts.srcDir);\n const pagesDir = joinPath(srcDir, SRC_PAGES);\n\n const outputFile = joinPath(srcDir, 'pages.gen.ts');\n const updateGeneratedFile = async (file: string | undefined) => {\n // skip when the changed file is the generated file itself\n if (file && outputFile.endsWith(file)) {\n return;\n }\n // skip when the entries file exists or pages dir does not exist\n if (!existsSync(pagesDir) || !(await detectFsRouterUsage(srcDir))) {\n return;\n }\n const generation = await generateFsRouterTypes(pagesDir);\n if (!generation) {\n // skip failures\n return;\n }\n await writeFile(outputFile, generation, 'utf-8');\n };\n\n server.watcher.on('change', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('add', async (file) => {\n await updateGeneratedFile(file);\n });\n server.watcher.on('unlink', async (file) => {\n await updateGeneratedFile(file);\n });\n void updateGeneratedFile(undefined);\n },\n };\n};\n\nexport async function detectFsRouterUsage(srcDir: string): Promise<boolean> {\n const existingServerEntry = EXTENSIONS.map((ext) =>\n joinPath(srcDir, SRC_SERVER_ENTRY + ext),\n ).find((entriesFile) => existsSync(entriesFile));\n\n // managed mode if no entry\n if (!existingServerEntry) {\n return true;\n }\n\n try {\n const file = await parseModule(existingServerEntry);\n const usesFsRouter = file.body.some((node) => {\n if (node.type !== 'ImportDeclaration') {\n return false;\n }\n if (\n node.source.type !== 'Literal' ||\n typeof node.source.value !== 'string' ||\n !node.source.value.startsWith('waku')\n ) {\n return false;\n }\n return node.specifiers.some((specifier) => {\n if (\n specifier.type !== 'ImportSpecifier' ||\n specifier.local.type !== 'Identifier'\n ) {\n return false;\n }\n return getImportedName(specifier) === 'fsRouter';\n });\n });\n return usesFsRouter;\n } catch {\n return false;\n }\n}\n\nexport async function generateFsRouterTypes(pagesDir: string) {\n // Recursively collect `.tsx` files in the given directory\n const collectFiles = async (\n dir: string,\n files: string[] = [],\n ): Promise<string[]> => {\n // TODO revisit recursive option for readdir once more stable\n // https://nodejs.org/docs/latest-v20.x/api/fs.html#direntparentpath\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = joinPath(dir, entry.name);\n if (entry.isDirectory()) {\n await collectFiles(fullPath, files);\n } else {\n if (entry.name.endsWith('.tsx')) {\n files.push(fullPath.slice(pagesDir.length));\n }\n }\n }\n return files;\n };\n\n const fileExportsGetConfig = async (filePath: string) => {\n const file = await parseModule(pagesDir + filePath);\n return file.body.some((node) => {\n if (node.type !== 'ExportNamedDeclaration') {\n return false;\n }\n if (\n node.declaration?.type === 'VariableDeclaration' &&\n node.declaration.declarations.some(\n (decl) =>\n decl.id.type === 'Identifier' && decl.id.name === 'getConfig',\n )\n ) {\n return true;\n }\n if (\n node.declaration?.type === 'FunctionDeclaration' &&\n node.declaration.id?.name === 'getConfig'\n ) {\n return true;\n }\n return node.specifiers.some(\n (specifier) =>\n specifier.type === 'ExportSpecifier' &&\n getExportedName(specifier) === 'getConfig',\n );\n });\n };\n\n const generateFile = async (filePaths: string[]): Promise<string | null> => {\n const fileInfo: { path: string; src: string; hasGetConfig: boolean }[] = [];\n const layoutPaths: string[] = [];\n const moduleNames = getImportModuleNames(filePaths);\n\n for (const filePath of filePaths) {\n // where to import the component from\n const src = filePath.replace(/^\\//, '');\n let hasGetConfig: boolean;\n try {\n hasGetConfig = await fileExportsGetConfig(filePath);\n } catch {\n return null;\n }\n\n if (isIgnoredPath(filePath.split('/'))) {\n continue;\n } else if (filePath.endsWith('/_layout.tsx')) {\n const path = filePath.slice(0, -'/_layout.tsx'.length);\n layoutPaths.push(getGrouplessPath(path) || '/');\n } else if (filePath.endsWith('/index.tsx')) {\n const path = filePath.slice(0, -'/index.tsx'.length);\n fileInfo.push({\n path: getGrouplessPath(path) || '/',\n src,\n hasGetConfig,\n });\n } else {\n fileInfo.push({\n path: getGrouplessPath(filePath.replace('.tsx', '')),\n src,\n hasGetConfig,\n });\n }\n }\n\n const hasAnyGetConfig = fileInfo.some((file) => file.hasGetConfig);\n const lines: string[] = [\n '// deno-fmt-ignore-file',\n '// biome-ignore format: generated types do not need formatting',\n '// prettier-ignore',\n hasAnyGetConfig\n ? \"import type { PathsForPages, GetConfigResponse, SearchCodecsForPages } from 'waku/router';\"\n : \"import type { PathsForPages } from 'waku/router';\",\n ];\n\n if (hasAnyGetConfig) {\n lines.push('');\n for (const file of fileInfo) {\n if (!file.hasGetConfig) {\n continue;\n }\n const moduleName = moduleNames[file.src];\n lines.push(\n `// prettier-ignore`,\n `import type { getConfig as ${moduleName}_getConfig } from './${SRC_PAGES}/${file.src.replace('.tsx', '')}';`,\n );\n }\n }\n\n lines.push('', '// prettier-ignore', 'type Page =');\n for (const file of fileInfo) {\n const moduleName = moduleNames[file.src];\n if (file.hasGetConfig) {\n lines.push(\n `| ({ path: '${file.path}' } & GetConfigResponse<typeof ${moduleName}_getConfig>)`,\n );\n } else {\n lines.push(`| { path: '${file.path}'; render: 'static' }`);\n }\n }\n lines[lines.length - 1] += ';';\n\n const uniqueLayoutPaths = [...new Set(layoutPaths)];\n if (uniqueLayoutPaths.length) {\n lines.push('', '// prettier-ignore', 'type Layout =');\n for (const path of uniqueLayoutPaths) {\n lines.push(`| { path: '${path}' }`);\n }\n lines[lines.length - 1] += ';';\n }\n\n lines.push(\n '',\n '// prettier-ignore',\n \"declare module 'waku/router' {\",\n ' interface RouteConfig {',\n ' paths: PathsForPages<Page>;',\n ' }',\n ' interface CreatePagesConfig {',\n ' pages: Page;',\n ...(uniqueLayoutPaths.length ? [' layouts: Layout;'] : []),\n ' }',\n ...(hasAnyGetConfig\n ? [\n ' interface SearchCodecsConfig extends SearchCodecsForPages<Page> {}',\n ]\n : []),\n '}',\n '',\n );\n\n const result = lines.join('\\n');\n\n return result;\n };\n\n const files = await collectFiles(pagesDir);\n if (!files.length) {\n return;\n }\n const generation = await generateFile(files);\n return generation;\n}\n"],"names":["existsSync","readFileSync","readdir","writeFile","parse","transformWithOxc","EXTENSIONS","SRC_PAGES","SRC_SERVER_ENTRY","getGrouplessPath","isIgnoredPath","joinPath","toIdentifier","input","identifier","includes","split","slice","join","replace","test","map","part","undefined","toUpperCase","getImportModuleNames","filePaths","moduleNameCount","moduleNames","filePath","e","console","log","parseModule","source","lang","endsWith","transformed","jsx","code","program","getImportedName","specifier","imported","type","name","String","value","getExportedName","exported","fsRouterTypegenPlugin","opts","apply","configureServer","server","srcDir","config","root","pagesDir","outputFile","updateGeneratedFile","file","detectFsRouterUsage","generation","generateFsRouterTypes","watcher","on","existingServerEntry","ext","find","entriesFile","usesFsRouter","body","some","node","startsWith","specifiers","local","collectFiles","dir","files","entries","withFileTypes","entry","fullPath","isDirectory","push","length","fileExportsGetConfig","declaration","declarations","decl","id","generateFile","fileInfo","layoutPaths","src","hasGetConfig","path","hasAnyGetConfig","lines","moduleName","uniqueLayoutPaths","Set","result"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import { BUILD_METADATA_FILE, DIST_PUBLIC, DIST_SERVER } from '../constants.js';
|
|
|
6
6
|
import { setAllEnv } from '../env.js';
|
|
7
7
|
import { getErrorInfo } from '../utils/custom-errors.js';
|
|
8
8
|
import { sanitizeLog } from '../utils/log.js';
|
|
9
|
-
import { joinPath } from '../utils/path.js';
|
|
9
|
+
import { addBase, joinPath } from '../utils/path.js';
|
|
10
10
|
import { DEBUG_ID_HEADER } from '../utils/react-debug-channel.js';
|
|
11
11
|
import { createRenderUtils } from '../utils/render.js';
|
|
12
12
|
import { getInput } from '../utils/request.js';
|
|
@@ -46,7 +46,7 @@ const toProcessRequest = (handleRequest)=>async (req)=>{
|
|
|
46
46
|
const body = stringToStream(message);
|
|
47
47
|
const headers = {};
|
|
48
48
|
if (info?.location) {
|
|
49
|
-
headers.location = info.location;
|
|
49
|
+
headers.location = addBase(info.location, config.basePath);
|
|
50
50
|
}
|
|
51
51
|
return new Response(body, {
|
|
52
52
|
status,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/vite-rsc/handler.ts"],"sourcesContent":["import {\n createFromReadableStream,\n createTemporaryReferenceSet,\n decodeAction,\n decodeFormState,\n decodeReply,\n loadServerAction,\n renderToReadableStream,\n} from '@vitejs/plugin-rsc/rsc';\nimport { buildMetadata } from 'virtual:vite-rsc-waku/build-metadata';\nimport { config, isBuild } from 'virtual:vite-rsc-waku/config';\nimport notFoundHtml from 'virtual:vite-rsc-waku/not-found';\nimport { BUILD_METADATA_FILE, DIST_PUBLIC, DIST_SERVER } from '../constants.js';\nimport { setAllEnv } from '../env.js';\nimport type {\n Unstable_CreateServerEntryAdapter as CreateServerEntryAdapter,\n Unstable_HandleBuild as HandleBuild,\n Unstable_HandleRequest as HandleRequest,\n Unstable_ProcessBuild as ProcessBuild,\n Unstable_ProcessRequest as ProcessRequest,\n} from '../types.js';\nimport { getErrorInfo } from '../utils/custom-errors.js';\nimport { sanitizeLog } from '../utils/log.js';\nimport { joinPath } from '../utils/path.js';\nimport { DEBUG_ID_HEADER } from '../utils/react-debug-channel.js';\nimport { createRenderUtils } from '../utils/render.js';\nimport { getInput } from '../utils/request.js';\nimport { encodeRscPath } from '../utils/rsc-path.js';\nimport { stringToStream } from '../utils/stream.js';\n\nfunction loadSsrEntryModule() {\n // This is an API to communicate between two server environments `rsc` and `ssr`.\n // https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-rsc/README.md#importmetaviterscloadmodule\n return import.meta.viteRsc.loadModule<\n typeof import('../vite-entries/entry.ssr.js')\n >('ssr', 'index');\n}\n\nconst toProcessRequest =\n (handleRequest: HandleRequest): ProcessRequest =>\n async (req) => {\n const temporaryReferences = createTemporaryReferenceSet();\n\n const input = await getInput(\n req,\n config,\n temporaryReferences,\n decodeReply,\n decodeAction,\n decodeFormState,\n loadServerAction,\n );\n\n const debugId =\n (import.meta.env.DEV && req.headers.get(DEBUG_ID_HEADER.toLowerCase())) ||\n undefined;\n const debugChannels = globalThis.__WAKU_DEBUG_CHANNELS__;\n const debugChannel = debugId ? debugChannels?.get(debugId) : undefined;\n if (debugId) {\n debugChannels?.delete(debugId);\n }\n\n const renderUtils = createRenderUtils(\n temporaryReferences,\n renderToReadableStream,\n createFromReadableStream,\n loadSsrEntryModule,\n import.meta.env.WAKU_BUILD_ID ?? '',\n debugChannel,\n debugId,\n );\n\n let res: Awaited<ReturnType<typeof handleRequest>>;\n try {\n res = await handleRequest(input, {\n ...renderUtils,\n loadBuildMetadata: async (key: string) => buildMetadata.get(key),\n });\n } catch (e) {\n const info = getErrorInfo(e);\n const status = info?.status || 500;\n let message: string;\n if (info) {\n message = (e as { message?: string } | undefined)?.message || String(e);\n } else {\n console.warn(sanitizeLog(e));\n message = 'Internal Server Error';\n }\n const body = stringToStream(message);\n const headers: { location?: string } = {};\n if (info?.location) {\n headers.location = info.location;\n }\n return new Response(body, { status, headers });\n }\n\n if (res instanceof ReadableStream) {\n return new Response(res);\n } else if (res && res !== 'fallback') {\n return res;\n }\n\n // fallback index html like packages/waku/src/lib/plugins/vite-plugin-rsc-index.ts\n const url = new URL(req.url);\n if (res === 'fallback' || (!res && url.pathname === '/')) {\n const { INTERNAL_renderHtmlFallback } = await loadSsrEntryModule();\n const htmlFallbackStream = await INTERNAL_renderHtmlFallback();\n const headers = { 'content-type': 'text/html; charset=utf-8' };\n return new Response(htmlFallbackStream, { headers });\n }\n\n return null;\n };\n\nconst toProcessBuild =\n (handleBuild: HandleBuild): ProcessBuild =>\n async ({ emitFile, unstable_registerPrunableFile }) => {\n const renderUtils = createRenderUtils(\n undefined,\n renderToReadableStream,\n createFromReadableStream,\n loadSsrEntryModule,\n import.meta.env.WAKU_BUILD_ID ?? '',\n );\n\n let fallbackHtml: string | undefined;\n const getFallbackHtml = async () => {\n if (!fallbackHtml) {\n const ssrEntryModule = await loadSsrEntryModule();\n fallbackHtml = await ssrEntryModule.INTERNAL_renderHtmlFallback();\n }\n return fallbackHtml;\n };\n\n const getPublicFilePath = (fileName: string) => {\n const filePath = joinPath(DIST_PUBLIC, fileName);\n if (!filePath.startsWith(DIST_PUBLIC + '/')) {\n throw new Error('fileName escapes the public directory: ' + fileName);\n }\n return filePath;\n };\n\n await handleBuild({\n renderRsc: renderUtils.renderRsc,\n parseRsc: renderUtils.parseRsc,\n renderHtml: renderUtils.renderHtml,\n rscPath2pathname: (rscPath) =>\n joinPath(config.rscBase, encodeRscPath(rscPath)),\n saveBuildMetadata: async (key, value) => {\n buildMetadata.set(key, value);\n },\n generateFile: async (fileName, body) => {\n await emitFile(\n getPublicFilePath(fileName),\n typeof body === 'string' ? stringToStream(body) : body,\n );\n },\n generateDefaultHtml: async (fileName) => {\n await emitFile(\n getPublicFilePath(fileName),\n stringToStream(await getFallbackHtml()),\n );\n },\n unstable_registerPrunableFile,\n });\n await emitFile(\n joinPath(DIST_SERVER, BUILD_METADATA_FILE),\n stringToStream(\n `export const buildMetadata = new Map(${JSON.stringify(\n Array.from(buildMetadata),\n )});`,\n ),\n );\n };\n\nexport const createServerEntryAdapter: CreateServerEntryAdapter =\n (fn) => (handlers, options) => {\n const processRequest = toProcessRequest(handlers.handleRequest);\n const processBuild = toProcessBuild(handlers.handleBuild);\n return fn(\n {\n handlers,\n processRequest,\n processBuild,\n setAllEnv,\n config,\n isBuild,\n notFoundHtml,\n },\n options,\n );\n };\n"],"names":["createFromReadableStream","createTemporaryReferenceSet","decodeAction","decodeFormState","decodeReply","loadServerAction","renderToReadableStream","buildMetadata","config","isBuild","notFoundHtml","BUILD_METADATA_FILE","DIST_PUBLIC","DIST_SERVER","setAllEnv","getErrorInfo","sanitizeLog","joinPath","DEBUG_ID_HEADER","createRenderUtils","getInput","encodeRscPath","stringToStream","loadSsrEntryModule","viteRsc","loadModule","toProcessRequest","handleRequest","req","temporaryReferences","input","debugId","env","DEV","headers","get","toLowerCase","undefined","debugChannels","globalThis","__WAKU_DEBUG_CHANNELS__","debugChannel","delete","renderUtils","WAKU_BUILD_ID","res","loadBuildMetadata","key","e","info","status","message","String","console","warn","body","location","Response","ReadableStream","url","URL","pathname","INTERNAL_renderHtmlFallback","htmlFallbackStream","toProcessBuild","handleBuild","emitFile","unstable_registerPrunableFile","fallbackHtml","getFallbackHtml","ssrEntryModule","getPublicFilePath","fileName","filePath","startsWith","Error","renderRsc","parseRsc","renderHtml","rscPath2pathname","rscPath","rscBase","saveBuildMetadata","value","set","generateFile","generateDefaultHtml","JSON","stringify","Array","from","createServerEntryAdapter","fn","handlers","options","processRequest","processBuild"],"mappings":"AAAA,SACEA,wBAAwB,EACxBC,2BAA2B,EAC3BC,YAAY,EACZC,eAAe,EACfC,WAAW,EACXC,gBAAgB,EAChBC,sBAAsB,QACjB,yBAAyB;AAChC,SAASC,aAAa,QAAQ,uCAAuC;AACrE,SAASC,MAAM,EAAEC,OAAO,QAAQ,+BAA+B;AAC/D,OAAOC,kBAAkB,kCAAkC;AAC3D,SAASC,mBAAmB,EAAEC,WAAW,EAAEC,WAAW,QAAQ,kBAAkB;AAChF,SAASC,SAAS,QAAQ,YAAY;AAQtC,SAASC,YAAY,QAAQ,4BAA4B;AACzD,SAASC,WAAW,QAAQ,kBAAkB;AAC9C,SAASC,QAAQ,QAAQ,mBAAmB;AAC5C,SAASC,eAAe,QAAQ,kCAAkC;AAClE,SAASC,iBAAiB,QAAQ,qBAAqB;AACvD,SAASC,QAAQ,QAAQ,sBAAsB;AAC/C,SAASC,aAAa,QAAQ,uBAAuB;AACrD,SAASC,cAAc,QAAQ,qBAAqB;AAEpD,SAASC;IACP,iFAAiF;IACjF,kHAAkH;IAClH,OAAO,YAAYC,OAAO,CAACC,UAAU,CAEnC,OAAO;AACX;AAEA,MAAMC,mBACJ,CAACC,gBACD,OAAOC;QACL,MAAMC,sBAAsB5B;QAE5B,MAAM6B,QAAQ,MAAMV,SAClBQ,KACApB,QACAqB,qBACAzB,aACAF,cACAC,iBACAE;QAGF,MAAM0B,UACJ,AAAC,YAAYC,GAAG,CAACC,GAAG,IAAIL,IAAIM,OAAO,CAACC,GAAG,CAACjB,gBAAgBkB,WAAW,OACnEC;QACF,MAAMC,gBAAgBC,WAAWC,uBAAuB;QACxD,MAAMC,eAAeV,UAAUO,eAAeH,IAAIJ,WAAWM;QAC7D,IAAIN,SAAS;YACXO,eAAeI,OAAOX;QACxB;QAEA,MAAMY,cAAcxB,kBAClBU,qBACAvB,wBACAN,0BACAuB,oBACA,YAAYS,GAAG,CAACY,aAAa,IAAI,IACjCH,cACAV;QAGF,IAAIc;QACJ,IAAI;YACFA,MAAM,MAAMlB,cAAcG,OAAO;gBAC/B,GAAGa,WAAW;gBACdG,mBAAmB,OAAOC,MAAgBxC,cAAc4B,GAAG,CAACY;YAC9D;QACF,EAAE,OAAOC,GAAG;YACV,MAAMC,OAAOlC,aAAaiC;YAC1B,MAAME,SAASD,MAAMC,UAAU;YAC/B,IAAIC;YACJ,IAAIF,MAAM;gBACRE,UAAU,AAACH,GAAwCG,WAAWC,OAAOJ;YACvE,OAAO;gBACLK,QAAQC,IAAI,CAACtC,YAAYgC;gBACzBG,UAAU;YACZ;YACA,MAAMI,OAAOjC,eAAe6B;YAC5B,MAAMjB,UAAiC,CAAC;YACxC,IAAIe,MAAMO,UAAU;gBAClBtB,QAAQsB,QAAQ,GAAGP,KAAKO,QAAQ;YAClC;YACA,OAAO,IAAIC,SAASF,MAAM;gBAAEL;gBAAQhB;YAAQ;QAC9C;QAEA,IAAIW,eAAea,gBAAgB;YACjC,OAAO,IAAID,SAASZ;QACtB,OAAO,IAAIA,OAAOA,QAAQ,YAAY;YACpC,OAAOA;QACT;QAEA,kFAAkF;QAClF,MAAMc,MAAM,IAAIC,IAAIhC,IAAI+B,GAAG;QAC3B,IAAId,QAAQ,cAAe,CAACA,OAAOc,IAAIE,QAAQ,KAAK,KAAM;YACxD,MAAM,EAAEC,2BAA2B,EAAE,GAAG,MAAMvC;YAC9C,MAAMwC,qBAAqB,MAAMD;YACjC,MAAM5B,UAAU;gBAAE,gBAAgB;YAA2B;YAC7D,OAAO,IAAIuB,SAASM,oBAAoB;gBAAE7B;YAAQ;QACpD;QAEA,OAAO;IACT;AAEF,MAAM8B,iBACJ,CAACC,cACD,OAAO,EAAEC,QAAQ,EAAEC,6BAA6B,EAAE;QAChD,MAAMxB,cAAcxB,kBAClBkB,WACA/B,wBACAN,0BACAuB,oBACA,YAAYS,GAAG,CAACY,aAAa,IAAI;QAGnC,IAAIwB;QACJ,MAAMC,kBAAkB;YACtB,IAAI,CAACD,cAAc;gBACjB,MAAME,iBAAiB,MAAM/C;gBAC7B6C,eAAe,MAAME,eAAeR,2BAA2B;YACjE;YACA,OAAOM;QACT;QAEA,MAAMG,oBAAoB,CAACC;YACzB,MAAMC,WAAWxD,SAASL,aAAa4D;YACvC,IAAI,CAACC,SAASC,UAAU,CAAC9D,cAAc,MAAM;gBAC3C,MAAM,IAAI+D,MAAM,4CAA4CH;YAC9D;YACA,OAAOC;QACT;QAEA,MAAMR,YAAY;YAChBW,WAAWjC,YAAYiC,SAAS;YAChCC,UAAUlC,YAAYkC,QAAQ;YAC9BC,YAAYnC,YAAYmC,UAAU;YAClCC,kBAAkB,CAACC,UACjB/D,SAAST,OAAOyE,OAAO,EAAE5D,cAAc2D;YACzCE,mBAAmB,OAAOnC,KAAKoC;gBAC7B5E,cAAc6E,GAAG,CAACrC,KAAKoC;YACzB;YACAE,cAAc,OAAOb,UAAUjB;gBAC7B,MAAMW,SACJK,kBAAkBC,WAClB,OAAOjB,SAAS,WAAWjC,eAAeiC,QAAQA;YAEtD;YACA+B,qBAAqB,OAAOd;gBAC1B,MAAMN,SACJK,kBAAkBC,WAClBlD,eAAe,MAAM+C;YAEzB;YACAF;QACF;QACA,MAAMD,SACJjD,SAASJ,aAAaF,sBACtBW,eACE,CAAC,qCAAqC,EAAEiE,KAAKC,SAAS,CACpDC,MAAMC,IAAI,CAACnF,gBACX,EAAE,CAAC;IAGX;AAEF,OAAO,MAAMoF,2BACX,CAACC,KAAO,CAACC,UAAUC;QACjB,MAAMC,iBAAiBrE,iBAAiBmE,SAASlE,aAAa;QAC9D,MAAMqE,eAAehC,eAAe6B,SAAS5B,WAAW;QACxD,OAAO2B,GACL;YACEC;YACAE;YACAC;YACAlF;YACAN;YACAC;YACAC;QACF,GACAoF;IAEJ,EAAE"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/vite-rsc/handler.ts"],"sourcesContent":["import {\n createFromReadableStream,\n createTemporaryReferenceSet,\n decodeAction,\n decodeFormState,\n decodeReply,\n loadServerAction,\n renderToReadableStream,\n} from '@vitejs/plugin-rsc/rsc';\nimport { buildMetadata } from 'virtual:vite-rsc-waku/build-metadata';\nimport { config, isBuild } from 'virtual:vite-rsc-waku/config';\nimport notFoundHtml from 'virtual:vite-rsc-waku/not-found';\nimport { BUILD_METADATA_FILE, DIST_PUBLIC, DIST_SERVER } from '../constants.js';\nimport { setAllEnv } from '../env.js';\nimport type {\n Unstable_CreateServerEntryAdapter as CreateServerEntryAdapter,\n Unstable_HandleBuild as HandleBuild,\n Unstable_HandleRequest as HandleRequest,\n Unstable_ProcessBuild as ProcessBuild,\n Unstable_ProcessRequest as ProcessRequest,\n} from '../types.js';\nimport { getErrorInfo } from '../utils/custom-errors.js';\nimport { sanitizeLog } from '../utils/log.js';\nimport { addBase, joinPath } from '../utils/path.js';\nimport { DEBUG_ID_HEADER } from '../utils/react-debug-channel.js';\nimport { createRenderUtils } from '../utils/render.js';\nimport { getInput } from '../utils/request.js';\nimport { encodeRscPath } from '../utils/rsc-path.js';\nimport { stringToStream } from '../utils/stream.js';\n\nfunction loadSsrEntryModule() {\n // This is an API to communicate between two server environments `rsc` and `ssr`.\n // https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-rsc/README.md#importmetaviterscloadmodule\n return import.meta.viteRsc.loadModule<\n typeof import('../vite-entries/entry.ssr.js')\n >('ssr', 'index');\n}\n\nconst toProcessRequest =\n (handleRequest: HandleRequest): ProcessRequest =>\n async (req) => {\n const temporaryReferences = createTemporaryReferenceSet();\n\n const input = await getInput(\n req,\n config,\n temporaryReferences,\n decodeReply,\n decodeAction,\n decodeFormState,\n loadServerAction,\n );\n\n const debugId =\n (import.meta.env.DEV && req.headers.get(DEBUG_ID_HEADER.toLowerCase())) ||\n undefined;\n const debugChannels = globalThis.__WAKU_DEBUG_CHANNELS__;\n const debugChannel = debugId ? debugChannels?.get(debugId) : undefined;\n if (debugId) {\n debugChannels?.delete(debugId);\n }\n\n const renderUtils = createRenderUtils(\n temporaryReferences,\n renderToReadableStream,\n createFromReadableStream,\n loadSsrEntryModule,\n import.meta.env.WAKU_BUILD_ID ?? '',\n debugChannel,\n debugId,\n );\n\n let res: Awaited<ReturnType<typeof handleRequest>>;\n try {\n res = await handleRequest(input, {\n ...renderUtils,\n loadBuildMetadata: async (key: string) => buildMetadata.get(key),\n });\n } catch (e) {\n const info = getErrorInfo(e);\n const status = info?.status || 500;\n let message: string;\n if (info) {\n message = (e as { message?: string } | undefined)?.message || String(e);\n } else {\n console.warn(sanitizeLog(e));\n message = 'Internal Server Error';\n }\n const body = stringToStream(message);\n const headers: { location?: string } = {};\n if (info?.location) {\n headers.location = addBase(info.location, config.basePath);\n }\n return new Response(body, { status, headers });\n }\n\n if (res instanceof ReadableStream) {\n return new Response(res);\n } else if (res && res !== 'fallback') {\n return res;\n }\n\n // fallback index html like packages/waku/src/lib/plugins/vite-plugin-rsc-index.ts\n const url = new URL(req.url);\n if (res === 'fallback' || (!res && url.pathname === '/')) {\n const { INTERNAL_renderHtmlFallback } = await loadSsrEntryModule();\n const htmlFallbackStream = await INTERNAL_renderHtmlFallback();\n const headers = { 'content-type': 'text/html; charset=utf-8' };\n return new Response(htmlFallbackStream, { headers });\n }\n\n return null;\n };\n\nconst toProcessBuild =\n (handleBuild: HandleBuild): ProcessBuild =>\n async ({ emitFile, unstable_registerPrunableFile }) => {\n const renderUtils = createRenderUtils(\n undefined,\n renderToReadableStream,\n createFromReadableStream,\n loadSsrEntryModule,\n import.meta.env.WAKU_BUILD_ID ?? '',\n );\n\n let fallbackHtml: string | undefined;\n const getFallbackHtml = async () => {\n if (!fallbackHtml) {\n const ssrEntryModule = await loadSsrEntryModule();\n fallbackHtml = await ssrEntryModule.INTERNAL_renderHtmlFallback();\n }\n return fallbackHtml;\n };\n\n const getPublicFilePath = (fileName: string) => {\n const filePath = joinPath(DIST_PUBLIC, fileName);\n if (!filePath.startsWith(DIST_PUBLIC + '/')) {\n throw new Error('fileName escapes the public directory: ' + fileName);\n }\n return filePath;\n };\n\n await handleBuild({\n renderRsc: renderUtils.renderRsc,\n parseRsc: renderUtils.parseRsc,\n renderHtml: renderUtils.renderHtml,\n rscPath2pathname: (rscPath) =>\n joinPath(config.rscBase, encodeRscPath(rscPath)),\n saveBuildMetadata: async (key, value) => {\n buildMetadata.set(key, value);\n },\n generateFile: async (fileName, body) => {\n await emitFile(\n getPublicFilePath(fileName),\n typeof body === 'string' ? stringToStream(body) : body,\n );\n },\n generateDefaultHtml: async (fileName) => {\n await emitFile(\n getPublicFilePath(fileName),\n stringToStream(await getFallbackHtml()),\n );\n },\n unstable_registerPrunableFile,\n });\n await emitFile(\n joinPath(DIST_SERVER, BUILD_METADATA_FILE),\n stringToStream(\n `export const buildMetadata = new Map(${JSON.stringify(\n Array.from(buildMetadata),\n )});`,\n ),\n );\n };\n\nexport const createServerEntryAdapter: CreateServerEntryAdapter =\n (fn) => (handlers, options) => {\n const processRequest = toProcessRequest(handlers.handleRequest);\n const processBuild = toProcessBuild(handlers.handleBuild);\n return fn(\n {\n handlers,\n processRequest,\n processBuild,\n setAllEnv,\n config,\n isBuild,\n notFoundHtml,\n },\n options,\n );\n };\n"],"names":["createFromReadableStream","createTemporaryReferenceSet","decodeAction","decodeFormState","decodeReply","loadServerAction","renderToReadableStream","buildMetadata","config","isBuild","notFoundHtml","BUILD_METADATA_FILE","DIST_PUBLIC","DIST_SERVER","setAllEnv","getErrorInfo","sanitizeLog","addBase","joinPath","DEBUG_ID_HEADER","createRenderUtils","getInput","encodeRscPath","stringToStream","loadSsrEntryModule","viteRsc","loadModule","toProcessRequest","handleRequest","req","temporaryReferences","input","debugId","env","DEV","headers","get","toLowerCase","undefined","debugChannels","globalThis","__WAKU_DEBUG_CHANNELS__","debugChannel","delete","renderUtils","WAKU_BUILD_ID","res","loadBuildMetadata","key","e","info","status","message","String","console","warn","body","location","basePath","Response","ReadableStream","url","URL","pathname","INTERNAL_renderHtmlFallback","htmlFallbackStream","toProcessBuild","handleBuild","emitFile","unstable_registerPrunableFile","fallbackHtml","getFallbackHtml","ssrEntryModule","getPublicFilePath","fileName","filePath","startsWith","Error","renderRsc","parseRsc","renderHtml","rscPath2pathname","rscPath","rscBase","saveBuildMetadata","value","set","generateFile","generateDefaultHtml","JSON","stringify","Array","from","createServerEntryAdapter","fn","handlers","options","processRequest","processBuild"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/minimal/client-utils/prefetch-cache.ts"],"sourcesContent":["// Client-side cache of prefetched navigations: a prefetch decodes the route\n// eagerly so a later navigation reuses the decoded tree without re-fetching.\n// Bounded by a ttl and a max size.\n\n/** How long (ms) a prefetched entry stays usable before it is discarded. */\nexport const PREFETCH_TTL = 1000 * 60;\n\n/** Maximum number of prefetched entries kept at once. */\nexport const PREFETCH_LIMIT = 100;\n\n// This is exported only for global-types.ts. It is not a public API.\nexport type PrefetchEntry = {\n rscPath: string;\n rscParams: unknown;\n elements: unknown;\n expireAt: number;\n};\n\nconst getCache = (): PrefetchEntry[] => (globalThis.__WAKU_PREFETCHED__ ||= []);\n\nconst findFreshIndex = (\n cache: PrefetchEntry[],\n rscPath: string,\n rscParams: unknown,\n now: number,\n) =>\n cache.findIndex(\n (entry) =>\n entry.expireAt > now &&\n entry.rscPath === rscPath &&\n // rscParams is intentionally compared by reference.\n entry.rscParams === rscParams,\n );\n\n/** Decode and cache a prefetch so a later navigation can reuse the tree. */\nexport const addPrefetchEntry = <Elements>(\n rscPath: string,\n rscParams: unknown,\n elements: Elements,\n): void => {\n const cache = getCache();\n const now = Date.now();\n // Mark as handled so a prefetch that is never consumed stays quiet.\n Promise.resolve(elements).catch(() => {});\n cache.push({ rscPath, rscParams, elements, expireAt: now + PREFETCH_TTL });\n while (\n cache.length > 0 &&\n (cache.length > PREFETCH_LIMIT || cache[0]!.expireAt <= now)\n ) {\n cache.shift();\n }\n};\n\nexport const hasPrefetchEntry = (\n rscPath: string,\n rscParams: unknown,\n): boolean => {\n const cache = getCache();\n return findFreshIndex(cache, rscPath, rscParams, Date.now()) >= 0;\n};\n\n/** Consume a fresh prefetch, returning its eagerly decoded tree. */\nexport const consumePrefetchEntry = <Elements>(\n rscPath: string,\n rscParams: unknown,\n): Elements | undefined => {\n const cache = getCache();\n const index = findFreshIndex(cache, rscPath, rscParams, Date.now());\n if (index < 0) {\n return undefined;\n }\n return cache.splice(index, 1)[0]!.elements as Elements;\n};\n"],"names":["PREFETCH_TTL","PREFETCH_LIMIT","getCache","globalThis","__WAKU_PREFETCHED__","findFreshIndex","cache","rscPath","rscParams","now","findIndex","entry","expireAt","addPrefetchEntry","elements","Date","Promise","resolve","catch","push","length","shift","hasPrefetchEntry","consumePrefetchEntry","index","undefined","splice"],"mappings":"AAAA,4EAA4E;AAC5E,6EAA6E;AAC7E,mCAAmC;AAEnC,0EAA0E,GAC1E,OAAO,MAAMA,eAAe,OAAO,GAAG;AAEtC,uDAAuD,GACvD,OAAO,MAAMC,iBAAiB,IAAI;AAUlC,MAAMC,WAAW,IAAwBC,WAAWC,mBAAmB,KAAK,EAAE;AAE9E,MAAMC,iBAAiB,CACrBC,OACAC,SACAC,WACAC,MAEAH,MAAMI,SAAS,CACb,CAACC,QACCA,MAAMC,QAAQ,GAAGH,OACjBE,MAAMJ,OAAO,KAAKA,WAClB,oDAAoD;QACpDI,MAAMH,SAAS,KAAKA;AAG1B,0EAA0E,GAC1E,OAAO,MAAMK,mBAAmB,CAC9BN,SACAC,WACAM;IAEA,MAAMR,QAAQJ;IACd,MAAMO,MAAMM,KAAKN,GAAG;IACpB,oEAAoE;IACpEO,QAAQC,OAAO,CAACH,UAAUI,KAAK,CAAC,KAAO;IACvCZ,MAAMa,IAAI,CAAC;QAAEZ;QAASC;QAAWM;QAAUF,UAAUH,MAAMT;IAAa;IACxE,MACEM,MAAMc,MAAM,GAAG,KACdd,CAAAA,MAAMc,MAAM,GAAGnB,kBAAkBK,KAAK,CAAC,EAAE,CAAEM,QAAQ,IAAIH,GAAE,EAC1D;QACAH,MAAMe,KAAK;IACb;AACF,EAAE;AAEF,OAAO,MAAMC,mBAAmB,CAC9Bf,SACAC;IAEA,MAAMF,QAAQJ;IACd,OAAOG,eAAeC,OAAOC,SAASC,WAAWO,KAAKN,GAAG,OAAO;AAClE,EAAE;AAEF,kEAAkE,GAClE,OAAO,MAAMc,uBAAuB,CAClChB,SACAC;IAEA,MAAMF,QAAQJ;IACd,MAAMsB,QAAQnB,eAAeC,OAAOC,SAASC,WAAWO,KAAKN,GAAG;IAChE,IAAIe,QAAQ,GAAG;QACb,OAAOC;IACT;IACA,OAAOnB,MAAMoB,MAAM,CAACF,OAAO,EAAE,CAAC,EAAE,CAAEV,QAAQ;AAC5C,EAAE"}
|
package/dist/minimal/client.d.ts
CHANGED
|
@@ -5,7 +5,9 @@ type FetchRscOptions = {
|
|
|
5
5
|
signal?: AbortSignal;
|
|
6
6
|
onBuildIdMismatch?: () => void;
|
|
7
7
|
};
|
|
8
|
-
type Refetch = (rscPath: string, rscParams?: unknown, options?: FetchRscOptions
|
|
8
|
+
type Refetch = (rscPath: string, rscParams?: unknown, options?: FetchRscOptions & {
|
|
9
|
+
unstable_isEager?: (key: string) => boolean;
|
|
10
|
+
}) => Promise<Elements>;
|
|
9
11
|
/**
|
|
10
12
|
* callServer callback
|
|
11
13
|
* This is not a public API.
|
|
@@ -36,13 +38,24 @@ export declare const unstable_prefetchRsc: (rscPath: string, rscParams?: unknown
|
|
|
36
38
|
/**
|
|
37
39
|
* Client root. Seeds the initial elements, bridges the store to React state,
|
|
38
40
|
* and provides the elements to `Slot` descendants.
|
|
41
|
+
*
|
|
42
|
+
* This API is technically unstable and may change or be removed,
|
|
43
|
+
* even though it does not carry the `unstable_` prefix.
|
|
39
44
|
*/
|
|
40
45
|
export declare const Root: ({ initialRscPath, initialRscParams, children, }: {
|
|
41
46
|
initialRscPath?: string;
|
|
42
47
|
initialRscParams?: unknown;
|
|
43
48
|
children: ReactNode;
|
|
44
49
|
}) => import("react").JSX.Element;
|
|
50
|
+
/**
|
|
51
|
+
* This API is technically unstable and may change or be removed,
|
|
52
|
+
* even though it does not carry the `unstable_` prefix.
|
|
53
|
+
*/
|
|
45
54
|
export declare const useRefetch: () => Refetch;
|
|
55
|
+
/**
|
|
56
|
+
* This API is technically unstable and may change or be removed,
|
|
57
|
+
* even though it does not carry the `unstable_` prefix.
|
|
58
|
+
*/
|
|
46
59
|
export declare const Children: () => ReactNode;
|
|
47
60
|
export declare const useElementsPromise_UNSTABLE: () => Promise<Elements>;
|
|
48
61
|
/**
|
|
@@ -58,6 +71,9 @@ export declare const useElementsPromise_UNSTABLE: () => Promise<Elements>;
|
|
|
58
71
|
* ```
|
|
59
72
|
* <Root><Slot id="foo" /><Slot id="bar" /></Root>
|
|
60
73
|
* ```
|
|
74
|
+
*
|
|
75
|
+
* This API is technically unstable and may change or be removed,
|
|
76
|
+
* even though it does not carry the `unstable_` prefix.
|
|
61
77
|
*/
|
|
62
78
|
export declare const Slot: ({ id, children, }: {
|
|
63
79
|
id: string;
|
package/dist/minimal/client.js
CHANGED
|
@@ -4,10 +4,10 @@ import { createContext, memo, startTransition, use, useCallback, useEffect, useS
|
|
|
4
4
|
import RSDWClient from 'react-server-dom-webpack/client';
|
|
5
5
|
import { createCustomError } from '../lib/utils/custom-errors.js';
|
|
6
6
|
import { consumeInitialRscEntry } from '../lib/utils/initial-rsc.js';
|
|
7
|
-
import { addPrefetchEntry, consumePrefetchEntry, hasPrefetchEntry } from '../lib/utils/prefetch-cache.js';
|
|
8
7
|
import { setupDebugChannel } from '../lib/utils/react-debug-channel.js';
|
|
9
8
|
import { encodeFuncId, encodeRscPath } from '../lib/utils/rsc-path.js';
|
|
10
9
|
import { CALL_SERVER_ELEMENTS_LISTENERS, ENTRY, FETCH_ENHANCERS, FETCH_RSC_INPUT_TRANSFORMERS, SET_ELEMENTS, fetchRscStore } from './client-utils/fetch-store.js';
|
|
10
|
+
import { addPrefetchEntry, consumePrefetchEntry, hasPrefetchEntry } from './client-utils/prefetch-cache.js';
|
|
11
11
|
const { createFromFetch, encodeReply, createTemporaryReferenceSet } = RSDWClient;
|
|
12
12
|
const DEFAULT_HTML_HEAD = [
|
|
13
13
|
/*#__PURE__*/ _jsx("meta", {
|
|
@@ -40,18 +40,38 @@ const checkStatus = async (responsePromise)=>{
|
|
|
40
40
|
// TODO(daishi) do we still this?
|
|
41
41
|
const getCached = (c, m, k)=>(m.has(k) ? m : m.set(k, c())).get(k);
|
|
42
42
|
const cache1 = new WeakMap();
|
|
43
|
-
const mergeElementsPromise = (a, b)=>{
|
|
44
|
-
const getResult = ()=>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
const mergeElementsPromise = (a, b, isEager)=>{
|
|
44
|
+
const getResult = ()=>{
|
|
45
|
+
if (!isEager) {
|
|
46
|
+
return Promise.all([
|
|
47
|
+
a,
|
|
48
|
+
b
|
|
49
|
+
]).then(([a, b])=>{
|
|
50
|
+
const nextElements = {
|
|
51
|
+
...a,
|
|
52
|
+
...b
|
|
53
|
+
};
|
|
54
|
+
delete nextElements._value;
|
|
55
|
+
return nextElements;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return Promise.resolve(a).then((aRes)=>{
|
|
59
|
+
const bPromise = Promise.resolve(b);
|
|
60
|
+
const base = {
|
|
61
|
+
...aRes
|
|
51
62
|
};
|
|
52
|
-
delete
|
|
53
|
-
return
|
|
63
|
+
delete base._value;
|
|
64
|
+
return new Proxy(base, {
|
|
65
|
+
get (target, key) {
|
|
66
|
+
if (key !== '_value' && !isEager(key) && // a lazy key still equal to aRes's value hasn't been streamed yet
|
|
67
|
+
target[key] === aRes[key]) {
|
|
68
|
+
target[key] = bPromise.then((bRes)=>key in bRes ? bRes[key] : aRes[key]);
|
|
69
|
+
}
|
|
70
|
+
return target[key];
|
|
71
|
+
}
|
|
72
|
+
});
|
|
54
73
|
});
|
|
74
|
+
};
|
|
55
75
|
const cache2 = getCached(()=>new WeakMap(), cache1, a);
|
|
56
76
|
return getCached(getResult, cache2, b);
|
|
57
77
|
};
|
|
@@ -242,6 +262,9 @@ const ElementsContext = /*#__PURE__*/ createContext(null);
|
|
|
242
262
|
/**
|
|
243
263
|
* Client root. Seeds the initial elements, bridges the store to React state,
|
|
244
264
|
* and provides the elements to `Slot` descendants.
|
|
265
|
+
*
|
|
266
|
+
* This API is technically unstable and may change or be removed,
|
|
267
|
+
* even though it does not carry the `unstable_` prefix.
|
|
245
268
|
*/ export const Root = ({ initialRscPath, initialRscParams, children })=>{
|
|
246
269
|
const [elements, setElements] = useState(()=>unstable_fetchRsc(initialRscPath || '', initialRscParams));
|
|
247
270
|
useEffect(()=>{
|
|
@@ -251,7 +274,7 @@ const ElementsContext = /*#__PURE__*/ createContext(null);
|
|
|
251
274
|
delete fetchRscStore[ENTRY];
|
|
252
275
|
const data = unstable_fetchRsc(rscPath, rscParams, options);
|
|
253
276
|
const dataWithoutErrors = Promise.resolve(data).catch(()=>({}));
|
|
254
|
-
setElements((prev)=>mergeElementsPromise(prev, dataWithoutErrors));
|
|
277
|
+
setElements((prev)=>mergeElementsPromise(prev, dataWithoutErrors, options?.unstable_isEager));
|
|
255
278
|
return data;
|
|
256
279
|
}, []);
|
|
257
280
|
return /*#__PURE__*/ _jsx(RefetchContext, {
|
|
@@ -265,10 +288,16 @@ const ElementsContext = /*#__PURE__*/ createContext(null);
|
|
|
265
288
|
})
|
|
266
289
|
});
|
|
267
290
|
};
|
|
268
|
-
|
|
291
|
+
/**
|
|
292
|
+
* This API is technically unstable and may change or be removed,
|
|
293
|
+
* even though it does not carry the `unstable_` prefix.
|
|
294
|
+
*/ export const useRefetch = ()=>use(RefetchContext);
|
|
269
295
|
const ChildrenContext = /*#__PURE__*/ createContext(undefined);
|
|
270
296
|
const ChildrenContextProvider = /*#__PURE__*/ memo(ChildrenContext);
|
|
271
|
-
|
|
297
|
+
/**
|
|
298
|
+
* This API is technically unstable and may change or be removed,
|
|
299
|
+
* even though it does not carry the `unstable_` prefix.
|
|
300
|
+
*/ export const Children = ()=>use(ChildrenContext);
|
|
272
301
|
export const useElementsPromise_UNSTABLE = ()=>{
|
|
273
302
|
const elementsPromise = use(ElementsContext);
|
|
274
303
|
if (!elementsPromise) {
|
|
@@ -289,6 +318,9 @@ export const useElementsPromise_UNSTABLE = ()=>{
|
|
|
289
318
|
* ```
|
|
290
319
|
* <Root><Slot id="foo" /><Slot id="bar" /></Root>
|
|
291
320
|
* ```
|
|
321
|
+
*
|
|
322
|
+
* This API is technically unstable and may change or be removed,
|
|
323
|
+
* even though it does not carry the `unstable_` prefix.
|
|
292
324
|
*/ export const Slot = ({ id, children })=>{
|
|
293
325
|
const elementsPromise = useElementsPromise_UNSTABLE();
|
|
294
326
|
const elements = use(elementsPromise);
|