@rsc-kit/core 0.17.0 → 0.18.1

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 (62) hide show
  1. package/dist/barrelImports.d.ts +6 -0
  2. package/dist/barrelImports.js +93 -0
  3. package/dist/barrelImports.js.map +1 -0
  4. package/dist/events.d.ts +57 -0
  5. package/dist/events.js +113 -0
  6. package/dist/events.js.map +1 -0
  7. package/dist/host.d.ts +16 -6
  8. package/dist/host.js +219 -124
  9. package/dist/host.js.map +1 -1
  10. package/dist/hostCalls.d.ts +27 -0
  11. package/dist/hostCalls.js +127 -12
  12. package/dist/hostCalls.js.map +1 -1
  13. package/dist/hostRouting.d.ts +53 -0
  14. package/dist/hostRouting.js +100 -0
  15. package/dist/hostRouting.js.map +1 -0
  16. package/dist/js/DefaultRouteError.d.ts +3 -0
  17. package/dist/js/DefaultRouteError.js +61 -0
  18. package/dist/js/DefaultRouteError.js.map +1 -0
  19. package/dist/js/LoadingBoundary.d.ts +5 -0
  20. package/dist/js/LoadingBoundary.js +19 -0
  21. package/dist/js/LoadingBoundary.js.map +1 -0
  22. package/dist/js/fallbackReport.js +10 -8
  23. package/dist/js/fallbackReport.js.map +1 -1
  24. package/dist/js/queryClient.d.ts +2 -1
  25. package/dist/js/queryClient.js +0 -12
  26. package/dist/js/queryClient.js.map +1 -1
  27. package/dist/js/router.d.ts +2 -2
  28. package/dist/js/router.js.map +1 -1
  29. package/dist/js/useEvents.d.ts +25 -0
  30. package/dist/js/useEvents.js +80 -0
  31. package/dist/js/useEvents.js.map +1 -0
  32. package/dist/js/usePolling.d.ts +29 -0
  33. package/dist/js/usePolling.js +142 -0
  34. package/dist/js/usePolling.js.map +1 -0
  35. package/dist/manifest.d.ts +18 -1
  36. package/dist/manifest.js.map +1 -1
  37. package/dist/metadataRoutes.d.ts +20 -0
  38. package/dist/metadataRoutes.js +35 -0
  39. package/dist/metadataRoutes.js.map +1 -1
  40. package/dist/prerender.js +2 -0
  41. package/dist/prerender.js.map +1 -1
  42. package/dist/redirect.d.ts +16 -2
  43. package/dist/redirect.js +10 -19
  44. package/dist/redirect.js.map +1 -1
  45. package/dist/revalidate.d.ts +6 -4
  46. package/dist/revalidate.js +6 -5
  47. package/dist/revalidate.js.map +1 -1
  48. package/dist/routeSchema.d.ts +49 -0
  49. package/dist/routeSchema.js.map +1 -1
  50. package/dist/routes.d.ts +16 -3
  51. package/dist/routes.js +5 -5
  52. package/dist/routes.js.map +1 -1
  53. package/dist/routing.d.ts +2 -2
  54. package/dist/routing.js +47 -25
  55. package/dist/routing.js.map +1 -1
  56. package/dist/testing.d.ts +8 -0
  57. package/dist/testing.js +27 -3
  58. package/dist/testing.js.map +1 -1
  59. package/dist/vite.d.ts +53 -2
  60. package/dist/vite.js +503 -38
  61. package/dist/vite.js.map +1 -1
  62. package/package.json +15 -3
package/dist/testing.js CHANGED
@@ -16,7 +16,7 @@
16
16
  // works; this proves the app answers a url the way it is deployed, which is
17
17
  // where a route that renders fine and is served wrong shows up.
18
18
  import { spawnSync } from 'node:child_process';
19
- import { existsSync, readdirSync, statSync } from 'node:fs';
19
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
20
20
  import { join, resolve } from 'node:path';
21
21
  import { pathToFileURL } from 'node:url';
22
22
  /**
@@ -51,6 +51,27 @@ function newest(dir) {
51
51
  }
52
52
  /** One build per process per root, however many test files ask. */
53
53
  const built = new Map();
54
+ /**
55
+ * How this project builds: its own `build` script, run by the package manager
56
+ * this test is running under. Without a script, Vite directly - on Bun's
57
+ * runtime when the tests are, since a bin's node shebang would otherwise
58
+ * start it under Node.
59
+ */
60
+ /** @internal Exported for its test. */
61
+ export function buildCommand(root) {
62
+ const onBun = typeof process.versions.bun === 'string';
63
+ let scripts = {};
64
+ try {
65
+ scripts = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8')).scripts ?? {};
66
+ }
67
+ catch {
68
+ // No package.json, or not JSON: there is no script to run.
69
+ }
70
+ if (typeof scripts.build === 'string') {
71
+ return onBun ? ['bun', ['run', 'build']] : ['npm', ['run', 'build']];
72
+ }
73
+ return onBun ? ['bun', ['--bun', 'vite', 'build']] : ['npx', ['vite', 'build']];
74
+ }
54
75
  async function ensureBuilt(root, build) {
55
76
  const existing = findBundle(root);
56
77
  if (!build) {
@@ -63,8 +84,11 @@ async function ensureBuilt(root, build) {
63
84
  if (fresh)
64
85
  return existing;
65
86
  // The user's own build command, so what is tested is what ships. A test
66
- // that built some other way would pass against a bundle nobody deploys.
67
- const run = spawnSync('npx', ['vite', 'build'], {
87
+ // that built some other way would pass against a bundle nobody deploys -
88
+ // and used to: this ran `npx vite build` whatever package.json said, which
89
+ // on a Bun project built under Node and failed on the first `import 'bun'`.
90
+ const [command, args] = buildCommand(root);
91
+ const run = spawnSync(command, args, {
68
92
  cwd: root,
69
93
  stdio: ['ignore', 'pipe', 'pipe'],
70
94
  env: { ...process.env, NODE_ENV: 'production' },
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,sCAAsC;AACtC,oFAAoF;AACpF,EAAE;AACF,8EAA8E;AAC9E,2EAA2E;AAC3E,6EAA6E;AAC7E,+EAA+E;AAC/E,gBAAgB;AAChB,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,4EAA4E;AAC5E,gEAAgE;AAEhE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC3D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAyBxC;;;;;;GAMG;AACH,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,EAAE,gDAAgD,CAAC;QAC5D,IAAI,CAAC,IAAI,EAAE,wBAAwB,CAAC;QACpC,IAAI,CAAC,IAAI,EAAE,yBAAyB,CAAC;KACtC,CAAA;IAED,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAA;AAC5D,CAAC;AAED,iFAAiF;AACjF,SAAS,MAAM,CAAC,GAAW;IACzB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAA;IAE9B,IAAI,MAAM,GAAG,CAAC,CAAA;IAEd,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC9D,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAQ;QAEzE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QAClC,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAA;QAExE,IAAI,IAAI,GAAG,MAAM;YAAE,MAAM,GAAG,IAAI,CAAA;IAClC,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED,mEAAmE;AACnE,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAA;AAEhD,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,KAAc;IACrD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAEjC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CACb,4CAA4C,IAAI,8EAA8E,CAC/H,CAAA;QACH,CAAC;QAED,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAA;IAEhF,IAAI,KAAK;QAAE,OAAO,QAAQ,CAAA;IAE1B,wEAAwE;IACxE,wEAAwE;IACxE,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;QAC9C,GAAG,EAAE,IAAI;QACT,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;QACjC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE;KAChD,CAAC,CAAA;IAEF,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,6DAA6D,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;IAC5F,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAE/B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,qEAAqE,IAAI,+BAA+B,CACzG,CAAA;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAO,GAAmB,EAAE;IAC9D,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,kBAAkB,CAAA;IAEnD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAA;IAE5E,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,CAAE,CAAA;IACrC,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAEtD,CAAA;IAED,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,aAAa,MAAM,qCAAqC,CAAC,CAAA;IAC3E,CAAC;IAED,OAAO;QACL,MAAM;QACN,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;KAC/E,CAAA;AACH,CAAC","sourcesContent":["// The whole app as a function, for tests.\n//\n// import { createTestApp } from '@rsc-kit/core/testing'\n//\n// const app = await createTestApp()\n// const res = await app.fetch('/api/orders', { headers: { Cookie: 'user=ada' } })\n//\n// No port, no browser, no server process. The built entry already exports the\n// same Request → Response handler the dev server and the production server\n// both call, so a test can hand it a Request and read the Response — through\n// the real router, the real middleware, the real api routes, and the pages the\n// build stored.\n//\n// That is the tier between a unit test and a browser. An action or a route is\n// a function and can be imported and called; a browser test proves the page\n// works; this proves the app answers a url the way it is deployed, which is\n// where a route that renders fine and is served wrong shows up.\n\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, readdirSync, statSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nexport interface TestApp {\n /** A path, not a url. The origin is whatever the app was told it is. */\n fetch(path: string, init?: RequestInit): Promise<Response>\n /** Where the build was read from, for a test that wants to look. */\n bundle: string\n}\n\nexport interface TestAppOptions {\n /** The project. Defaults to the working directory. */\n root?: string\n /**\n * Whether to build first.\n *\n * `true` builds when the source is newer than the last build, which is what\n * a test run wants: the first run pays for it, the rest do not, and an edit\n * is picked up. `false` never builds and fails loudly if there is nothing to\n * read — for a ci step that already built.\n */\n build?: boolean\n /** The origin requests are made against. Nothing reads it; it is a url. */\n origin?: string\n}\n\n/**\n * Where the build put the server bundle.\n *\n * Two layouts, because two ways of building. Under Nitro — every scaffolded\n * app — it is inside Nitro's own directory. On its own the plugin writes\n * <outDir>/dist/rsc. The first that exists wins.\n */\nfunction findBundle(root: string): string | null {\n const candidates = [\n join(root, 'node_modules/.nitro/vite/services/rsc/index.js'),\n join(root, '.rsc/dist/rsc/index.js'),\n join(root, 'build/dist/rsc/index.js'),\n ]\n\n return candidates.find((path) => existsSync(path)) ?? null\n}\n\n/** The newest mtime under a directory, for deciding whether a build is stale. */\nfunction newest(dir: string): number {\n if (!existsSync(dir)) return 0\n\n let latest = 0\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n\n const path = join(dir, entry.name)\n const time = entry.isDirectory() ? newest(path) : statSync(path).mtimeMs\n\n if (time > latest) latest = time\n }\n\n return latest\n}\n\n/** One build per process per root, however many test files ask. */\nconst built = new Map<string, Promise<string>>()\n\nasync function ensureBuilt(root: string, build: boolean): Promise<string> {\n const existing = findBundle(root)\n\n if (!build) {\n if (!existing) {\n throw new Error(\n `[rsc-kit] No build to test against under ${root}. Run the build first, or let createTestApp() do it by leaving \\`build\\` on.`,\n )\n }\n\n return existing\n }\n\n const fresh = existing && statSync(existing).mtimeMs > newest(join(root, 'src'))\n\n if (fresh) return existing\n\n // The user's own build command, so what is tested is what ships. A test\n // that built some other way would pass against a bundle nobody deploys.\n const run = spawnSync('npx', ['vite', 'build'], {\n cwd: root,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, NODE_ENV: 'production' },\n })\n\n if (run.status !== 0) {\n throw new Error(`[rsc-kit] The build failed, so there is nothing to test:\\n${run.stderr}`)\n }\n\n const bundle = findBundle(root)\n\n if (!bundle) {\n throw new Error(\n `[rsc-kit] The build finished but no server bundle was found under ${root}. Is rscKit() in vite.config?`,\n )\n }\n\n return bundle\n}\n\n/**\n * Build the app if it needs it, load it, and hand back something to fetch from.\n *\n * Memoised per root: every test file in a run shares one build and one loaded\n * module, which is both the fast path and the correct one — two copies of the\n * server bundle in one process would be two client-reference registries.\n */\nexport async function createTestApp(options: TestAppOptions = {}): Promise<TestApp> {\n const root = resolve(options.root ?? process.cwd())\n const origin = options.origin ?? 'https://app.test'\n\n built.set(root, built.get(root) ?? ensureBuilt(root, options.build ?? true))\n\n const bundle = await built.get(root)!\n const entry = (await import(pathToFileURL(bundle).href)) as {\n default: (request: Request) => Promise<Response>\n }\n\n if (typeof entry.default !== 'function') {\n throw new Error(`[rsc-kit] ${bundle} does not export a request handler.`)\n }\n\n return {\n bundle,\n fetch: (path, init) => entry.default(new Request(new URL(path, origin), init)),\n }\n}\n"]}
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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","sourcesContent":["// The whole app as a function, for tests.\n//\n// import { createTestApp } from '@rsc-kit/core/testing'\n//\n// const app = await createTestApp()\n// const res = await app.fetch('/api/orders', { headers: { Cookie: 'user=ada' } })\n//\n// No port, no browser, no server process. The built entry already exports the\n// same Request → Response handler the dev server and the production server\n// both call, so a test can hand it a Request and read the Response — through\n// the real router, the real middleware, the real api routes, and the pages the\n// build stored.\n//\n// That is the tier between a unit test and a browser. An action or a route is\n// a function and can be imported and called; a browser test proves the page\n// works; this proves the app answers a url the way it is deployed, which is\n// where a route that renders fine and is served wrong shows up.\n\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nexport interface TestApp {\n /** A path, not a url. The origin is whatever the app was told it is. */\n fetch(path: string, init?: RequestInit): Promise<Response>\n /** Where the build was read from, for a test that wants to look. */\n bundle: string\n}\n\nexport interface TestAppOptions {\n /** The project. Defaults to the working directory. */\n root?: string\n /**\n * Whether to build first.\n *\n * `true` builds when the source is newer than the last build, which is what\n * a test run wants: the first run pays for it, the rest do not, and an edit\n * is picked up. `false` never builds and fails loudly if there is nothing to\n * read — for a ci step that already built.\n */\n build?: boolean\n /** The origin requests are made against. Nothing reads it; it is a url. */\n origin?: string\n}\n\n/**\n * Where the build put the server bundle.\n *\n * Two layouts, because two ways of building. Under Nitro — every scaffolded\n * app — it is inside Nitro's own directory. On its own the plugin writes\n * <outDir>/dist/rsc. The first that exists wins.\n */\nfunction findBundle(root: string): string | null {\n const candidates = [\n join(root, 'node_modules/.nitro/vite/services/rsc/index.js'),\n join(root, '.rsc/dist/rsc/index.js'),\n join(root, 'build/dist/rsc/index.js'),\n ]\n\n return candidates.find((path) => existsSync(path)) ?? null\n}\n\n/** The newest mtime under a directory, for deciding whether a build is stale. */\nfunction newest(dir: string): number {\n if (!existsSync(dir)) return 0\n\n let latest = 0\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n\n const path = join(dir, entry.name)\n const time = entry.isDirectory() ? newest(path) : statSync(path).mtimeMs\n\n if (time > latest) latest = time\n }\n\n return latest\n}\n\n/** One build per process per root, however many test files ask. */\nconst built = new Map<string, Promise<string>>()\n\n/**\n * How this project builds: its own `build` script, run by the package manager\n * this test is running under. Without a script, Vite directly - on Bun's\n * runtime when the tests are, since a bin's node shebang would otherwise\n * start it under Node.\n */\n/** @internal Exported for its test. */\nexport function buildCommand(root: string): [string, string[]] {\n const onBun = typeof process.versions.bun === 'string'\n\n let scripts: Record<string, string> = {}\n\n try {\n scripts = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8')).scripts ?? {}\n } catch {\n // No package.json, or not JSON: there is no script to run.\n }\n\n if (typeof scripts.build === 'string') {\n return onBun ? ['bun', ['run', 'build']] : ['npm', ['run', 'build']]\n }\n\n return onBun ? ['bun', ['--bun', 'vite', 'build']] : ['npx', ['vite', 'build']]\n}\n\nasync function ensureBuilt(root: string, build: boolean): Promise<string> {\n const existing = findBundle(root)\n\n if (!build) {\n if (!existing) {\n throw new Error(\n `[rsc-kit] No build to test against under ${root}. Run the build first, or let createTestApp() do it by leaving \\`build\\` on.`,\n )\n }\n\n return existing\n }\n\n const fresh = existing && statSync(existing).mtimeMs > newest(join(root, 'src'))\n\n if (fresh) return existing\n\n // The user's own build command, so what is tested is what ships. A test\n // that built some other way would pass against a bundle nobody deploys -\n // and used to: this ran `npx vite build` whatever package.json said, which\n // on a Bun project built under Node and failed on the first `import 'bun'`.\n const [command, args] = buildCommand(root)\n const run = spawnSync(command, args, {\n cwd: root,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, NODE_ENV: 'production' },\n })\n\n if (run.status !== 0) {\n throw new Error(`[rsc-kit] The build failed, so there is nothing to test:\\n${run.stderr}`)\n }\n\n const bundle = findBundle(root)\n\n if (!bundle) {\n throw new Error(\n `[rsc-kit] The build finished but no server bundle was found under ${root}. Is rscKit() in vite.config?`,\n )\n }\n\n return bundle\n}\n\n/**\n * Build the app if it needs it, load it, and hand back something to fetch from.\n *\n * Memoised per root: every test file in a run shares one build and one loaded\n * module, which is both the fast path and the correct one — two copies of the\n * server bundle in one process would be two client-reference registries.\n */\nexport async function createTestApp(options: TestAppOptions = {}): Promise<TestApp> {\n const root = resolve(options.root ?? process.cwd())\n const origin = options.origin ?? 'https://app.test'\n\n built.set(root, built.get(root) ?? ensureBuilt(root, options.build ?? true))\n\n const bundle = await built.get(root)!\n const entry = (await import(pathToFileURL(bundle).href)) as {\n default: (request: Request) => Promise<Response>\n }\n\n if (typeof entry.default !== 'function') {\n throw new Error(`[rsc-kit] ${bundle} does not export a request handler.`)\n }\n\n return {\n bundle,\n fetch: (path, init) => entry.default(new Request(new URL(path, origin), init)),\n }\n}\n"]}
package/dist/vite.d.ts CHANGED
@@ -5,6 +5,42 @@ export interface RscKitOptions {
5
5
  projectRoot?: string;
6
6
  /** Directory holding the app/ route tree. Defaults to `src`. */
7
7
  sourceDir?: string;
8
+ /**
9
+ * Say what built the site: `<meta name="generator" content="rsc-kit">` in
10
+ * every document and `X-Powered-By: rsc-kit` on every response. The name
11
+ * only, never the version - a version in every response is what a
12
+ * vulnerability scanner filters on. On by default; `false` for a team
13
+ * whose policy strips every framework identifier.
14
+ *
15
+ * `X-RSC-Kit: stored | rendered | shell` - how a response was answered, the
16
+ * header a developer reads in the Network tab - is not this and is always
17
+ * sent: it names no product, and a CDN rule or a check can key on it.
18
+ */
19
+ identify?: boolean;
20
+ /**
21
+ * Unroll named imports from a barrel package on the server, in development.
22
+ *
23
+ * `import { ArrowRight } from 'lucide-react'` reaches a file that re-exports
24
+ * fifteen hundred icons, one module each; the browser gets the package
25
+ * pre-bundled, the server environments do not, so Vite loaded every icon
26
+ * module on the first request. The import is rewritten to the module that
27
+ * owns the name, read from the barrel itself. On by default; `false` to
28
+ * leave imports as written.
29
+ */
30
+ barrelImports?: boolean;
31
+ /**
32
+ * The site's own hosts, beyond the one in the root layout's metadataBase.
33
+ *
34
+ * A request from any other host is routed with the host in front of the
35
+ * path: `admin.example.com/users` reaches `app/admin/users/page.tsx`, the
36
+ * file `example.com/admin/users` reaches by path; `acme.example.com/`
37
+ * reaches `app/[domain]/page.tsx` with `domain: "acme"`, and a custom
38
+ * domain `acme.com/` the same file with `domain: "acme.com"`. The site's
39
+ * own hosts keep path routing, so an app adds tenants without moving a
40
+ * file. `www.` of each is its own too. With no metadataBase and nothing
41
+ * here, every host is the site's own and nothing is prepended.
42
+ */
43
+ hosts?: string[];
8
44
  /**
9
45
  * Serve the app from a service worker, so it survives a reload with no
10
46
  * network at all.
@@ -65,6 +101,17 @@ export interface RscKitOptions {
65
101
  * reads this file follows the server instead of guessing at it.
66
102
  */
67
103
  hotFile?: string;
104
+ /**
105
+ * Packages the server bundles import at runtime rather than inline.
106
+ *
107
+ * For a dependency with a native binary, one that spawns a process, or one
108
+ * that reads files beside itself - anything a bundle breaks. A default list
109
+ * covers the usual ones (sharp, bcrypt, better-sqlite3, prisma, puppeteer,
110
+ * ...); this adds to it. Nitro traces an external package into
111
+ * .output/server/node_modules with its binaries, so the deployment is still
112
+ * one directory.
113
+ */
114
+ serverExternalPackages?: string[];
68
115
  /**
69
116
  * Where `rpc()` goes while `vite dev` is serving.
70
117
  *
@@ -94,8 +141,12 @@ export interface RscKitOptions {
94
141
  * because a second setting that could disagree with the first is a bug
95
142
  * waiting to be filed. `false` restores the plain 404.
96
143
  *
97
- * Development only. A built deployment's server.ts decides for itself what
98
- * to do with a url it does not own.
144
+ * The built server forwards too — the entry that carries this is the one
145
+ * Nitro bundles — with the origin read at build time from the project's
146
+ * .env, while host calls read it from the process at runtime. A build on a
147
+ * machine whose .env names a different backend than production's sets
148
+ * RSC_BACKEND for the build. The name predates that: it was the dev server's
149
+ * alone before Nitro built the server from the same entry.
99
150
  */
100
151
  devFallback?: string | false;
101
152
  /**