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.
Files changed (45) hide show
  1. package/README.md +16 -16
  2. package/dist/lib/global-types.d.ts +1 -1
  3. package/dist/lib/vite-plugins/fs-router-typegen.js +18 -2
  4. package/dist/lib/vite-plugins/fs-router-typegen.js.map +1 -1
  5. package/dist/lib/vite-rsc/handler.js +2 -2
  6. package/dist/lib/vite-rsc/handler.js.map +1 -1
  7. package/dist/minimal/client-utils/prefetch-cache.js.map +1 -0
  8. package/dist/minimal/client.d.ts +17 -1
  9. package/dist/minimal/client.js +46 -14
  10. package/dist/minimal/client.js.map +1 -1
  11. package/dist/router/base-types.d.ts +13 -2
  12. package/dist/router/base-types.js +6 -1
  13. package/dist/router/base-types.js.map +1 -1
  14. package/dist/router/client.d.ts +24 -14
  15. package/dist/router/client.js +126 -67
  16. package/dist/router/client.js.map +1 -1
  17. package/dist/router/create-pages-utils/inferred-path-types.d.ts +7 -1
  18. package/dist/router/create-pages-utils/inferred-path-types.js.map +1 -1
  19. package/dist/router/create-pages.d.ts +5 -5
  20. package/dist/router/create-pages.js +2 -2
  21. package/dist/router/create-pages.js.map +1 -1
  22. package/dist/router/define-router.d.ts +8 -4
  23. package/dist/router/define-router.js +43 -40
  24. package/dist/router/define-router.js.map +1 -1
  25. package/dist/router/fs-router.d.ts +2 -1
  26. package/dist/router/{client-utils → isomorphic-utils}/build-route-href.d.ts +6 -1
  27. package/dist/router/isomorphic-utils/build-route-href.js.map +1 -0
  28. package/dist/router/isomorphic-utils/match-route-params.js.map +1 -0
  29. package/dist/router/{common-utils → isomorphic-utils}/route-path.d.ts +1 -0
  30. package/dist/router/{common-utils → isomorphic-utils}/route-path.js +3 -0
  31. package/dist/router/isomorphic-utils/route-path.js.map +1 -0
  32. package/dist/router/isomorphic-utils/search-codec-registry.js.map +1 -0
  33. package/package.json +1 -1
  34. package/dist/lib/utils/prefetch-cache.js.map +0 -1
  35. package/dist/router/client-utils/build-route-href.js.map +0 -1
  36. package/dist/router/client-utils/match-route-params.js.map +0 -1
  37. package/dist/router/client-utils/search-codec-registry.js.map +0 -1
  38. package/dist/router/common-utils/route-path.js.map +0 -1
  39. /package/dist/{lib/utils → minimal/client-utils}/prefetch-cache.d.ts +0 -0
  40. /package/dist/{lib/utils → minimal/client-utils}/prefetch-cache.js +0 -0
  41. /package/dist/router/{client-utils → isomorphic-utils}/build-route-href.js +0 -0
  42. /package/dist/router/{client-utils → isomorphic-utils}/match-route-params.d.ts +0 -0
  43. /package/dist/router/{client-utils → isomorphic-utils}/match-route-params.js +0 -0
  44. /package/dist/router/{client-utils → isomorphic-utils}/search-codec-registry.d.ts +0 -0
  45. /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’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.
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’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).
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’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.
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’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.
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’t included in the client bundle. They have no state, interactivity, or access to browser APIs since they run _exclusively_ on the server.
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’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.
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 “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.
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 “pages router” experience built for the server components era.
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 “wildcard” segment routes (e.g., `/app/[...catchAll]`) are marked with an ellipsis before the name and have indefinite segments.
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’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'`.
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’s new in React 19?](https://www.youtube.com/watch?v=AJOGzVygGcY) to learn more.
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’s performance and scalability, but Waku is compatible with all React state management libraries such as Zustand and Valtio.
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’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.
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’s important to distinguish environment variables that must be kept secret from those that can be made public.
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’ browsers.
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’re seeking additional contributors. Check out our [roadmap](https://github.com/wakujs/waku/issues/24) for more information.
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('./utils/prefetch-cache.js').PrefetchEntry[] | undefined;
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 (filePath.endsWith('/_layout.tsx') || isIgnoredPath(filePath.split('/'))) {
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
- lines.push('', '// prettier-ignore', "declare module 'waku/router' {", ' interface RouteConfig {', ' paths: PathsForPages<Page>;', ' }', ' interface CreatePagesConfig {', ' pages: Page;', ' }', ...hasAnyGetConfig ? [
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":"AAAA,gEAAgE;AAChE,+DAA+D;AAC/D,gDAAgD;AAEhD,SAASA,UAAU,EAAEC,YAAY,QAAQ,UAAU;AACnD,SAASC,OAAO,EAAEC,SAAS,QAAQ,mBAAmB;AACtD,SAASC,KAAK,EAAEC,gBAAgB,QAAQ,OAAO;AAE/C,SAASC,UAAU,EAAEC,SAAS,EAAEC,gBAAgB,QAAQ,kBAAkB;AAC1E,SAASC,gBAAgB,QAAQ,2BAA2B;AAC5D,SAASC,aAAa,QAAQ,wBAAwB;AACtD,SAASC,QAAQ,QAAQ,mBAAmB;AAgB5C,oGAAoG;AACpG,gGAAgG;AAChG,mGAAmG;AACnG,OAAO,SAASC,aAAaC,KAAa;IACxC,2BAA2B;IAC3B,IAAIC,aAAaD,MAAME,QAAQ,CAAC,OAC5BF,MAAMG,KAAK,CAAC,KAAKC,KAAK,CAAC,GAAG,CAAC,GAAGC,IAAI,CAAC,OACnCL;IACJ,kGAAkG;IAClGC,aAAaA,WAAWK,OAAO,CAAC,qBAAqB;IACrD,iCAAiC;IACjC,IAAI,MAAMC,IAAI,CAACN,aAAa;QAC1BA,aAAa,MAAMA;IACrB;IACA,0BAA0B;IAC1B,uEAAuE;IACvE,OACE,UACAA,WACGE,KAAK,CAAC,KACNK,GAAG,CAAC,CAACC;QACJ,IAAIA,IAAI,CAAC,EAAE,KAAKC,WAAW;YACzB,OAAO;QACT;QACA,OAAOD,IAAI,CAAC,EAAE,CAACE,WAAW,KAAKF,KAAKL,KAAK,CAAC;IAC5C,GACCC,IAAI,CAAC;AAEZ;AAEA,OAAO,SAASO,qBAAqBC,SAAmB;IAGtD,MAAMC,kBAA2C,CAAC;IAClD,MAAMC,cAAuC,CAAC;IAC9C,KAAK,MAAMC,YAAYH,UAAW;QAChC,IAAIZ,aAAaF,aAAaiB;QAC9BF,eAAe,CAACb,WAAW,GAAG,AAACa,CAAAA,eAAe,CAACb,WAAW,IAAI,CAAC,CAAA,IAAK;QACpE,IAAIa,eAAe,CAACb,WAAW,EAAE;YAC/BA,aAAa,GAAGA,WAAW,CAAC,EAAEa,eAAe,CAACb,WAAW,EAAE;QAC7D;QACA,IAAI;YACFc,WAAW,CAACC,SAASV,OAAO,CAAC,OAAO,IAAI,GAAGL;QAC7C,EAAE,OAAOgB,GAAG;YACVC,QAAQC,GAAG,CAACF;QACd;IACF;IACA,OAAOF;AACT;AAEA,MAAMK,cAAc,OAAOJ;IACzB,MAAMK,SAASjC,aAAa4B,UAAU;IACtC,MAAMM,OAA6BN,SAASO,QAAQ,CAAC,UACjD,QACAP,SAASO,QAAQ,CAAC,SAChB,OACA;IACN,MAAMC,cAAc,MAAMhC,iBAAiB6B,QAAQL,UAAU;QAC3DM;QACAG,KAAK;IACP;IACA,OAAO,AAAC,CAAA,MAAMlC,MAAMyB,UAAUQ,YAAYE,IAAI,EAAE;QAAEJ;IAAK,EAAU,EAAGK,OAAO;AAC7E;AAEA,MAAMC,kBAAkB,CAACC,YACvBA,UAAUC,QAAQ,CAACC,IAAI,KAAK,eACxBF,UAAUC,QAAQ,CAACE,IAAI,GACvBC,OAAOJ,UAAUC,QAAQ,CAACI,KAAK;AAErC,MAAMC,kBAAkB,CAACN,YACvBA,UAAUO,QAAQ,CAACL,IAAI,KAAK,eACxBF,UAAUO,QAAQ,CAACJ,IAAI,GACvBC,OAAOJ,UAAUO,QAAQ,CAACF,KAAK;AAErC,OAAO,MAAMG,wBAAwB,CAACC;IACpC,OAAO;QACLN,MAAM;QACNO,OAAO;QACPC,iBAAgBC,MAAM;YACpB,MAAMC,SAAS5C,SAAS2C,OAAOE,MAAM,CAACC,IAAI,EAAEN,KAAKI,MAAM;YACvD,MAAMG,WAAW/C,SAAS4C,QAAQhD;YAElC,MAAMoD,aAAahD,SAAS4C,QAAQ;YACpC,MAAMK,sBAAsB,OAAOC;gBACjC,0DAA0D;gBAC1D,IAAIA,QAAQF,WAAWvB,QAAQ,CAACyB,OAAO;oBACrC;gBACF;gBACA,gEAAgE;gBAChE,IAAI,CAAC7D,WAAW0D,aAAa,CAAE,MAAMI,oBAAoBP,SAAU;oBACjE;gBACF;gBACA,MAAMQ,aAAa,MAAMC,sBAAsBN;gBAC/C,IAAI,CAACK,YAAY;oBACf,gBAAgB;oBAChB;gBACF;gBACA,MAAM5D,UAAUwD,YAAYI,YAAY;YAC1C;YAEAT,OAAOW,OAAO,CAACC,EAAE,CAAC,UAAU,OAAOL;gBACjC,MAAMD,oBAAoBC;YAC5B;YACAP,OAAOW,OAAO,CAACC,EAAE,CAAC,OAAO,OAAOL;gBAC9B,MAAMD,oBAAoBC;YAC5B;YACAP,OAAOW,OAAO,CAACC,EAAE,CAAC,UAAU,OAAOL;gBACjC,MAAMD,oBAAoBC;YAC5B;YACA,KAAKD,oBAAoBrC;QAC3B;IACF;AACF,EAAE;AAEF,OAAO,eAAeuC,oBAAoBP,MAAc;IACtD,MAAMY,sBAAsB7D,WAAWe,GAAG,CAAC,CAAC+C,MAC1CzD,SAAS4C,QAAQ/C,mBAAmB4D,MACpCC,IAAI,CAAC,CAACC,cAAgBtE,WAAWsE;IAEnC,2BAA2B;IAC3B,IAAI,CAACH,qBAAqB;QACxB,OAAO;IACT;IAEA,IAAI;QACF,MAAMN,OAAO,MAAM5B,YAAYkC;QAC/B,MAAMI,eAAeV,KAAKW,IAAI,CAACC,IAAI,CAAC,CAACC;YACnC,IAAIA,KAAK9B,IAAI,KAAK,qBAAqB;gBACrC,OAAO;YACT;YACA,IACE8B,KAAKxC,MAAM,CAACU,IAAI,KAAK,aACrB,OAAO8B,KAAKxC,MAAM,CAACa,KAAK,KAAK,YAC7B,CAAC2B,KAAKxC,MAAM,CAACa,KAAK,CAAC4B,UAAU,CAAC,SAC9B;gBACA,OAAO;YACT;YACA,OAAOD,KAAKE,UAAU,CAACH,IAAI,CAAC,CAAC/B;gBAC3B,IACEA,UAAUE,IAAI,KAAK,qBACnBF,UAAUmC,KAAK,CAACjC,IAAI,KAAK,cACzB;oBACA,OAAO;gBACT;gBACA,OAAOH,gBAAgBC,eAAe;YACxC;QACF;QACA,OAAO6B;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,OAAO,eAAeP,sBAAsBN,QAAgB;IAC1D,0DAA0D;IAC1D,MAAMoB,eAAe,OACnBC,KACAC,QAAkB,EAAE;QAEpB,6DAA6D;QAC7D,oEAAoE;QACpE,MAAMC,UAAU,MAAM/E,QAAQ6E,KAAK;YAAEG,eAAe;QAAK;QACzD,KAAK,MAAMC,SAASF,QAAS;YAC3B,MAAMG,WAAWzE,SAASoE,KAAKI,MAAMtC,IAAI;YACzC,IAAIsC,MAAME,WAAW,IAAI;gBACvB,MAAMP,aAAaM,UAAUJ;YAC/B,OAAO;gBACL,IAAIG,MAAMtC,IAAI,CAACT,QAAQ,CAAC,SAAS;oBAC/B4C,MAAMM,IAAI,CAACF,SAASnE,KAAK,CAACyC,SAAS6B,MAAM;gBAC3C;YACF;QACF;QACA,OAAOP;IACT;IAEA,MAAMQ,uBAAuB,OAAO3D;QAClC,MAAMgC,OAAO,MAAM5B,YAAYyB,WAAW7B;QAC1C,OAAOgC,KAAKW,IAAI,CAACC,IAAI,CAAC,CAACC;YACrB,IAAIA,KAAK9B,IAAI,KAAK,0BAA0B;gBAC1C,OAAO;YACT;YACA,IACE8B,KAAKe,WAAW,EAAE7C,SAAS,yBAC3B8B,KAAKe,WAAW,CAACC,YAAY,CAACjB,IAAI,CAChC,CAACkB,OACCA,KAAKC,EAAE,CAAChD,IAAI,KAAK,gBAAgB+C,KAAKC,EAAE,CAAC/C,IAAI,KAAK,cAEtD;gBACA,OAAO;YACT;YACA,IACE6B,KAAKe,WAAW,EAAE7C,SAAS,yBAC3B8B,KAAKe,WAAW,CAACG,EAAE,EAAE/C,SAAS,aAC9B;gBACA,OAAO;YACT;YACA,OAAO6B,KAAKE,UAAU,CAACH,IAAI,CACzB,CAAC/B,YACCA,UAAUE,IAAI,KAAK,qBACnBI,gBAAgBN,eAAe;QAErC;IACF;IAEA,MAAMmD,eAAe,OAAOnE;QAC1B,MAAMoE,WAAmE,EAAE;QAC3E,MAAMlE,cAAcH,qBAAqBC;QAEzC,KAAK,MAAMG,YAAYH,UAAW;YAChC,qCAAqC;YACrC,MAAMqE,MAAMlE,SAASV,OAAO,CAAC,OAAO;YACpC,IAAI6E;YACJ,IAAI;gBACFA,eAAe,MAAMR,qBAAqB3D;YAC5C,EAAE,OAAM;gBACN,OAAO;YACT;YAEA,IACEA,SAASO,QAAQ,CAAC,mBAClB1B,cAAcmB,SAASb,KAAK,CAAC,OAC7B;gBACA;YACF,OAAO,IAAIa,SAASO,QAAQ,CAAC,eAAe;gBAC1C,MAAM6D,OAAOpE,SAASZ,KAAK,CAAC,GAAG,CAAC,aAAasE,MAAM;gBACnDO,SAASR,IAAI,CAAC;oBACZW,MAAMxF,iBAAiBwF,SAAS;oBAChCF;oBACAC;gBACF;YACF,OAAO;gBACLF,SAASR,IAAI,CAAC;oBACZW,MAAMxF,iBAAiBoB,SAASV,OAAO,CAAC,QAAQ;oBAChD4E;oBACAC;gBACF;YACF;QACF;QAEA,MAAME,kBAAkBJ,SAASrB,IAAI,CAAC,CAACZ,OAASA,KAAKmC,YAAY;QACjE,MAAMG,QAAkB;YACtB;YACA;YACA;YACAD,kBACI,+FACA;SACL;QAED,IAAIA,iBAAiB;YACnBC,MAAMb,IAAI,CAAC;YACX,KAAK,MAAMzB,QAAQiC,SAAU;gBAC3B,IAAI,CAACjC,KAAKmC,YAAY,EAAE;oBACtB;gBACF;gBACA,MAAMI,aAAaxE,WAAW,CAACiC,KAAKkC,GAAG,CAAC;gBACxCI,MAAMb,IAAI,CACR,CAAC,kBAAkB,CAAC,EACpB,CAAC,2BAA2B,EAAEc,WAAW,qBAAqB,EAAE7F,UAAU,CAAC,EAAEsD,KAAKkC,GAAG,CAAC5E,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;YAEjH;QACF;QAEAgF,MAAMb,IAAI,CAAC,IAAI,sBAAsB;QACrC,KAAK,MAAMzB,QAAQiC,SAAU;YAC3B,MAAMM,aAAaxE,WAAW,CAACiC,KAAKkC,GAAG,CAAC;YACxC,IAAIlC,KAAKmC,YAAY,EAAE;gBACrBG,MAAMb,IAAI,CACR,CAAC,YAAY,EAAEzB,KAAKoC,IAAI,CAAC,+BAA+B,EAAEG,WAAW,YAAY,CAAC;YAEtF,OAAO;gBACLD,MAAMb,IAAI,CAAC,CAAC,WAAW,EAAEzB,KAAKoC,IAAI,CAAC,qBAAqB,CAAC;YAC3D;QACF;QACAE,KAAK,CAACA,MAAMZ,MAAM,GAAG,EAAE,IAAI;QAE3BY,MAAMb,IAAI,CACR,IACA,sBACA,kCACA,6BACA,mCACA,OACA,mCACA,oBACA,UACIY,kBACA;YACE;SACD,GACD,EAAE,EACN,KACA;QAGF,MAAMG,SAASF,MAAMjF,IAAI,CAAC;QAE1B,OAAOmF;IACT;IAEA,MAAMrB,QAAQ,MAAMF,aAAapB;IACjC,IAAI,CAACsB,MAAMO,MAAM,EAAE;QACjB;IACF;IACA,MAAMxB,aAAa,MAAM8B,aAAab;IACtC,OAAOjB;AACT"}
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":"AAAA,gEAAgE;AAChE,+DAA+D;AAC/D,gDAAgD;AAEhD,SAASA,UAAU,EAAEC,YAAY,QAAQ,UAAU;AACnD,SAASC,OAAO,EAAEC,SAAS,QAAQ,mBAAmB;AACtD,SAASC,KAAK,EAAEC,gBAAgB,QAAQ,OAAO;AAE/C,SAASC,UAAU,EAAEC,SAAS,EAAEC,gBAAgB,QAAQ,kBAAkB;AAC1E,SAASC,gBAAgB,QAAQ,2BAA2B;AAC5D,SAASC,aAAa,QAAQ,wBAAwB;AACtD,SAASC,QAAQ,QAAQ,mBAAmB;AAgB5C,oGAAoG;AACpG,gGAAgG;AAChG,mGAAmG;AACnG,OAAO,SAASC,aAAaC,KAAa;IACxC,2BAA2B;IAC3B,IAAIC,aAAaD,MAAME,QAAQ,CAAC,OAC5BF,MAAMG,KAAK,CAAC,KAAKC,KAAK,CAAC,GAAG,CAAC,GAAGC,IAAI,CAAC,OACnCL;IACJ,kGAAkG;IAClGC,aAAaA,WAAWK,OAAO,CAAC,qBAAqB;IACrD,iCAAiC;IACjC,IAAI,MAAMC,IAAI,CAACN,aAAa;QAC1BA,aAAa,MAAMA;IACrB;IACA,0BAA0B;IAC1B,uEAAuE;IACvE,OACE,UACAA,WACGE,KAAK,CAAC,KACNK,GAAG,CAAC,CAACC;QACJ,IAAIA,IAAI,CAAC,EAAE,KAAKC,WAAW;YACzB,OAAO;QACT;QACA,OAAOD,IAAI,CAAC,EAAE,CAACE,WAAW,KAAKF,KAAKL,KAAK,CAAC;IAC5C,GACCC,IAAI,CAAC;AAEZ;AAEA,OAAO,SAASO,qBAAqBC,SAAmB;IAGtD,MAAMC,kBAA2C,CAAC;IAClD,MAAMC,cAAuC,CAAC;IAC9C,KAAK,MAAMC,YAAYH,UAAW;QAChC,IAAIZ,aAAaF,aAAaiB;QAC9BF,eAAe,CAACb,WAAW,GAAG,AAACa,CAAAA,eAAe,CAACb,WAAW,IAAI,CAAC,CAAA,IAAK;QACpE,IAAIa,eAAe,CAACb,WAAW,EAAE;YAC/BA,aAAa,GAAGA,WAAW,CAAC,EAAEa,eAAe,CAACb,WAAW,EAAE;QAC7D;QACA,IAAI;YACFc,WAAW,CAACC,SAASV,OAAO,CAAC,OAAO,IAAI,GAAGL;QAC7C,EAAE,OAAOgB,GAAG;YACVC,QAAQC,GAAG,CAACF;QACd;IACF;IACA,OAAOF;AACT;AAEA,MAAMK,cAAc,OAAOJ;IACzB,MAAMK,SAASjC,aAAa4B,UAAU;IACtC,MAAMM,OAA6BN,SAASO,QAAQ,CAAC,UACjD,QACAP,SAASO,QAAQ,CAAC,SAChB,OACA;IACN,MAAMC,cAAc,MAAMhC,iBAAiB6B,QAAQL,UAAU;QAC3DM;QACAG,KAAK;IACP;IACA,OAAO,AAAC,CAAA,MAAMlC,MAAMyB,UAAUQ,YAAYE,IAAI,EAAE;QAAEJ;IAAK,EAAU,EAAGK,OAAO;AAC7E;AAEA,MAAMC,kBAAkB,CAACC,YACvBA,UAAUC,QAAQ,CAACC,IAAI,KAAK,eACxBF,UAAUC,QAAQ,CAACE,IAAI,GACvBC,OAAOJ,UAAUC,QAAQ,CAACI,KAAK;AAErC,MAAMC,kBAAkB,CAACN,YACvBA,UAAUO,QAAQ,CAACL,IAAI,KAAK,eACxBF,UAAUO,QAAQ,CAACJ,IAAI,GACvBC,OAAOJ,UAAUO,QAAQ,CAACF,KAAK;AAErC,OAAO,MAAMG,wBAAwB,CAACC;IACpC,OAAO;QACLN,MAAM;QACNO,OAAO;QACPC,iBAAgBC,MAAM;YACpB,MAAMC,SAAS5C,SAAS2C,OAAOE,MAAM,CAACC,IAAI,EAAEN,KAAKI,MAAM;YACvD,MAAMG,WAAW/C,SAAS4C,QAAQhD;YAElC,MAAMoD,aAAahD,SAAS4C,QAAQ;YACpC,MAAMK,sBAAsB,OAAOC;gBACjC,0DAA0D;gBAC1D,IAAIA,QAAQF,WAAWvB,QAAQ,CAACyB,OAAO;oBACrC;gBACF;gBACA,gEAAgE;gBAChE,IAAI,CAAC7D,WAAW0D,aAAa,CAAE,MAAMI,oBAAoBP,SAAU;oBACjE;gBACF;gBACA,MAAMQ,aAAa,MAAMC,sBAAsBN;gBAC/C,IAAI,CAACK,YAAY;oBACf,gBAAgB;oBAChB;gBACF;gBACA,MAAM5D,UAAUwD,YAAYI,YAAY;YAC1C;YAEAT,OAAOW,OAAO,CAACC,EAAE,CAAC,UAAU,OAAOL;gBACjC,MAAMD,oBAAoBC;YAC5B;YACAP,OAAOW,OAAO,CAACC,EAAE,CAAC,OAAO,OAAOL;gBAC9B,MAAMD,oBAAoBC;YAC5B;YACAP,OAAOW,OAAO,CAACC,EAAE,CAAC,UAAU,OAAOL;gBACjC,MAAMD,oBAAoBC;YAC5B;YACA,KAAKD,oBAAoBrC;QAC3B;IACF;AACF,EAAE;AAEF,OAAO,eAAeuC,oBAAoBP,MAAc;IACtD,MAAMY,sBAAsB7D,WAAWe,GAAG,CAAC,CAAC+C,MAC1CzD,SAAS4C,QAAQ/C,mBAAmB4D,MACpCC,IAAI,CAAC,CAACC,cAAgBtE,WAAWsE;IAEnC,2BAA2B;IAC3B,IAAI,CAACH,qBAAqB;QACxB,OAAO;IACT;IAEA,IAAI;QACF,MAAMN,OAAO,MAAM5B,YAAYkC;QAC/B,MAAMI,eAAeV,KAAKW,IAAI,CAACC,IAAI,CAAC,CAACC;YACnC,IAAIA,KAAK9B,IAAI,KAAK,qBAAqB;gBACrC,OAAO;YACT;YACA,IACE8B,KAAKxC,MAAM,CAACU,IAAI,KAAK,aACrB,OAAO8B,KAAKxC,MAAM,CAACa,KAAK,KAAK,YAC7B,CAAC2B,KAAKxC,MAAM,CAACa,KAAK,CAAC4B,UAAU,CAAC,SAC9B;gBACA,OAAO;YACT;YACA,OAAOD,KAAKE,UAAU,CAACH,IAAI,CAAC,CAAC/B;gBAC3B,IACEA,UAAUE,IAAI,KAAK,qBACnBF,UAAUmC,KAAK,CAACjC,IAAI,KAAK,cACzB;oBACA,OAAO;gBACT;gBACA,OAAOH,gBAAgBC,eAAe;YACxC;QACF;QACA,OAAO6B;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,OAAO,eAAeP,sBAAsBN,QAAgB;IAC1D,0DAA0D;IAC1D,MAAMoB,eAAe,OACnBC,KACAC,QAAkB,EAAE;QAEpB,6DAA6D;QAC7D,oEAAoE;QACpE,MAAMC,UAAU,MAAM/E,QAAQ6E,KAAK;YAAEG,eAAe;QAAK;QACzD,KAAK,MAAMC,SAASF,QAAS;YAC3B,MAAMG,WAAWzE,SAASoE,KAAKI,MAAMtC,IAAI;YACzC,IAAIsC,MAAME,WAAW,IAAI;gBACvB,MAAMP,aAAaM,UAAUJ;YAC/B,OAAO;gBACL,IAAIG,MAAMtC,IAAI,CAACT,QAAQ,CAAC,SAAS;oBAC/B4C,MAAMM,IAAI,CAACF,SAASnE,KAAK,CAACyC,SAAS6B,MAAM;gBAC3C;YACF;QACF;QACA,OAAOP;IACT;IAEA,MAAMQ,uBAAuB,OAAO3D;QAClC,MAAMgC,OAAO,MAAM5B,YAAYyB,WAAW7B;QAC1C,OAAOgC,KAAKW,IAAI,CAACC,IAAI,CAAC,CAACC;YACrB,IAAIA,KAAK9B,IAAI,KAAK,0BAA0B;gBAC1C,OAAO;YACT;YACA,IACE8B,KAAKe,WAAW,EAAE7C,SAAS,yBAC3B8B,KAAKe,WAAW,CAACC,YAAY,CAACjB,IAAI,CAChC,CAACkB,OACCA,KAAKC,EAAE,CAAChD,IAAI,KAAK,gBAAgB+C,KAAKC,EAAE,CAAC/C,IAAI,KAAK,cAEtD;gBACA,OAAO;YACT;YACA,IACE6B,KAAKe,WAAW,EAAE7C,SAAS,yBAC3B8B,KAAKe,WAAW,CAACG,EAAE,EAAE/C,SAAS,aAC9B;gBACA,OAAO;YACT;YACA,OAAO6B,KAAKE,UAAU,CAACH,IAAI,CACzB,CAAC/B,YACCA,UAAUE,IAAI,KAAK,qBACnBI,gBAAgBN,eAAe;QAErC;IACF;IAEA,MAAMmD,eAAe,OAAOnE;QAC1B,MAAMoE,WAAmE,EAAE;QAC3E,MAAMC,cAAwB,EAAE;QAChC,MAAMnE,cAAcH,qBAAqBC;QAEzC,KAAK,MAAMG,YAAYH,UAAW;YAChC,qCAAqC;YACrC,MAAMsE,MAAMnE,SAASV,OAAO,CAAC,OAAO;YACpC,IAAI8E;YACJ,IAAI;gBACFA,eAAe,MAAMT,qBAAqB3D;YAC5C,EAAE,OAAM;gBACN,OAAO;YACT;YAEA,IAAInB,cAAcmB,SAASb,KAAK,CAAC,OAAO;gBACtC;YACF,OAAO,IAAIa,SAASO,QAAQ,CAAC,iBAAiB;gBAC5C,MAAM8D,OAAOrE,SAASZ,KAAK,CAAC,GAAG,CAAC,eAAesE,MAAM;gBACrDQ,YAAYT,IAAI,CAAC7E,iBAAiByF,SAAS;YAC7C,OAAO,IAAIrE,SAASO,QAAQ,CAAC,eAAe;gBAC1C,MAAM8D,OAAOrE,SAASZ,KAAK,CAAC,GAAG,CAAC,aAAasE,MAAM;gBACnDO,SAASR,IAAI,CAAC;oBACZY,MAAMzF,iBAAiByF,SAAS;oBAChCF;oBACAC;gBACF;YACF,OAAO;gBACLH,SAASR,IAAI,CAAC;oBACZY,MAAMzF,iBAAiBoB,SAASV,OAAO,CAAC,QAAQ;oBAChD6E;oBACAC;gBACF;YACF;QACF;QAEA,MAAME,kBAAkBL,SAASrB,IAAI,CAAC,CAACZ,OAASA,KAAKoC,YAAY;QACjE,MAAMG,QAAkB;YACtB;YACA;YACA;YACAD,kBACI,+FACA;SACL;QAED,IAAIA,iBAAiB;YACnBC,MAAMd,IAAI,CAAC;YACX,KAAK,MAAMzB,QAAQiC,SAAU;gBAC3B,IAAI,CAACjC,KAAKoC,YAAY,EAAE;oBACtB;gBACF;gBACA,MAAMI,aAAazE,WAAW,CAACiC,KAAKmC,GAAG,CAAC;gBACxCI,MAAMd,IAAI,CACR,CAAC,kBAAkB,CAAC,EACpB,CAAC,2BAA2B,EAAEe,WAAW,qBAAqB,EAAE9F,UAAU,CAAC,EAAEsD,KAAKmC,GAAG,CAAC7E,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;YAEjH;QACF;QAEAiF,MAAMd,IAAI,CAAC,IAAI,sBAAsB;QACrC,KAAK,MAAMzB,QAAQiC,SAAU;YAC3B,MAAMO,aAAazE,WAAW,CAACiC,KAAKmC,GAAG,CAAC;YACxC,IAAInC,KAAKoC,YAAY,EAAE;gBACrBG,MAAMd,IAAI,CACR,CAAC,YAAY,EAAEzB,KAAKqC,IAAI,CAAC,+BAA+B,EAAEG,WAAW,YAAY,CAAC;YAEtF,OAAO;gBACLD,MAAMd,IAAI,CAAC,CAAC,WAAW,EAAEzB,KAAKqC,IAAI,CAAC,qBAAqB,CAAC;YAC3D;QACF;QACAE,KAAK,CAACA,MAAMb,MAAM,GAAG,EAAE,IAAI;QAE3B,MAAMe,oBAAoB;eAAI,IAAIC,IAAIR;SAAa;QACnD,IAAIO,kBAAkBf,MAAM,EAAE;YAC5Ba,MAAMd,IAAI,CAAC,IAAI,sBAAsB;YACrC,KAAK,MAAMY,QAAQI,kBAAmB;gBACpCF,MAAMd,IAAI,CAAC,CAAC,WAAW,EAAEY,KAAK,GAAG,CAAC;YACpC;YACAE,KAAK,CAACA,MAAMb,MAAM,GAAG,EAAE,IAAI;QAC7B;QAEAa,MAAMd,IAAI,CACR,IACA,sBACA,kCACA,6BACA,mCACA,OACA,mCACA,uBACIgB,kBAAkBf,MAAM,GAAG;YAAC;SAAuB,GAAG,EAAE,EAC5D,UACIY,kBACA;YACE;SACD,GACD,EAAE,EACN,KACA;QAGF,MAAMK,SAASJ,MAAMlF,IAAI,CAAC;QAE1B,OAAOsF;IACT;IAEA,MAAMxB,QAAQ,MAAMF,aAAapB;IACjC,IAAI,CAACsB,MAAMO,MAAM,EAAE;QACjB;IACF;IACA,MAAMxB,aAAa,MAAM8B,aAAab;IACtC,OAAOjB;AACT"}
@@ -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":"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"}
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"}
@@ -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) => Promise<Elements>;
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;
@@ -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 = ()=>Promise.all([
45
- a,
46
- b
47
- ]).then(([a, b])=>{
48
- const nextElements = {
49
- ...a,
50
- ...b
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 nextElements._value;
53
- return nextElements;
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
- export const useRefetch = ()=>use(RefetchContext);
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
- export const Children = ()=>use(ChildrenContext);
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);