@rshono/core 1.0.0-rc.2 → 1.0.0-rc.21

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 (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -158
  3. package/bin/rshono.mjs +3 -4
  4. package/dist/builder/env-shadow-loader.cjs +123 -8
  5. package/dist/builder/page-entry-loader.cjs +28 -1
  6. package/dist/builder/page-files.d.ts +0 -1
  7. package/dist/builder/page-files.js +7 -3
  8. package/dist/builder/page-files.js.map +1 -1
  9. package/dist/builder/public-env.d.ts +7 -1
  10. package/dist/builder/public-env.js +7 -0
  11. package/dist/builder/public-env.js.map +1 -1
  12. package/dist/builder/react-versions.d.ts +12 -0
  13. package/dist/builder/react-versions.js +74 -0
  14. package/dist/builder/react-versions.js.map +1 -0
  15. package/dist/builder/rspack-config.d.ts +13 -4
  16. package/dist/builder/rspack-config.js +136 -53
  17. package/dist/builder/rspack-config.js.map +1 -1
  18. package/dist/builder/server-only-imports.d.ts +32 -0
  19. package/dist/builder/server-only-imports.js +51 -0
  20. package/dist/builder/server-only-imports.js.map +1 -0
  21. package/dist/cli/build.d.ts +2 -3
  22. package/dist/cli/build.js +51 -13
  23. package/dist/cli/build.js.map +1 -1
  24. package/dist/cli/dev.d.ts +2 -3
  25. package/dist/cli/dev.js +149 -45
  26. package/dist/cli/dev.js.map +1 -1
  27. package/dist/cli/exit.d.ts +12 -0
  28. package/dist/cli/exit.js +16 -0
  29. package/dist/cli/exit.js.map +1 -0
  30. package/dist/cli/index.d.ts +0 -1
  31. package/dist/cli/index.js +91 -30
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/start.d.ts +0 -1
  34. package/dist/cli/start.js +15 -18
  35. package/dist/cli/start.js.map +1 -1
  36. package/dist/config.d.ts +72 -78
  37. package/dist/config.js +17 -1
  38. package/dist/config.js.map +1 -1
  39. package/dist/deploy/aws-lambda/runtime.d.ts +4 -7
  40. package/dist/deploy/aws-lambda/runtime.js +5 -8
  41. package/dist/deploy/aws-lambda/runtime.js.map +1 -1
  42. package/dist/deploy/build-marker.d.ts +9 -6
  43. package/dist/deploy/build-marker.js +8 -4
  44. package/dist/deploy/build-marker.js.map +1 -1
  45. package/dist/deploy/cloudflare/build.d.ts +0 -1
  46. package/dist/deploy/cloudflare/build.js +17 -13
  47. package/dist/deploy/cloudflare/build.js.map +1 -1
  48. package/dist/deploy/cloudflare/runtime.d.ts +2 -6
  49. package/dist/deploy/cloudflare/runtime.js +73 -68
  50. package/dist/deploy/cloudflare/runtime.js.map +1 -1
  51. package/dist/deploy/contract.d.ts +57 -41
  52. package/dist/deploy/contract.js.map +1 -1
  53. package/dist/deploy/filesystem.d.ts +3 -6
  54. package/dist/deploy/filesystem.js +23 -20
  55. package/dist/deploy/filesystem.js.map +1 -1
  56. package/dist/deploy/node/runtime.d.ts +5 -3
  57. package/dist/deploy/node/runtime.js +23 -9
  58. package/dist/deploy/node/runtime.js.map +1 -1
  59. package/dist/deploy/presets.d.ts +34 -30
  60. package/dist/deploy/presets.js +56 -44
  61. package/dist/deploy/presets.js.map +1 -1
  62. package/dist/deploy/public-paths.d.ts +42 -0
  63. package/dist/deploy/public-paths.js +66 -0
  64. package/dist/deploy/public-paths.js.map +1 -0
  65. package/dist/deploy/vercel/build.d.ts +0 -1
  66. package/dist/deploy/vercel/build.js +25 -16
  67. package/dist/deploy/vercel/build.js.map +1 -1
  68. package/dist/deploy/vercel/runtime.d.ts +10 -7
  69. package/dist/deploy/vercel/runtime.js +48 -11
  70. package/dist/deploy/vercel/runtime.js.map +1 -1
  71. package/dist/index.d.ts +19 -17
  72. package/dist/index.js +20 -10
  73. package/dist/index.js.map +1 -1
  74. package/dist/router.d.ts +212 -120
  75. package/dist/router.js +3 -8
  76. package/dist/router.js.map +1 -1
  77. package/dist/runtime/boundaries.d.ts +62 -48
  78. package/dist/runtime/boundaries.js +32 -34
  79. package/dist/runtime/boundaries.js.map +1 -1
  80. package/dist/runtime/client.d.ts +20 -11
  81. package/dist/runtime/client.js +20 -10
  82. package/dist/runtime/client.js.map +1 -1
  83. package/dist/runtime/context.d.ts +304 -123
  84. package/dist/runtime/context.js +377 -149
  85. package/dist/runtime/context.js.map +1 -1
  86. package/dist/runtime/control.d.ts +15 -1
  87. package/dist/runtime/control.js +24 -0
  88. package/dist/runtime/control.js.map +1 -1
  89. package/dist/runtime/dev-protocol.d.ts +4 -9
  90. package/dist/runtime/dev-protocol.js.map +1 -1
  91. package/dist/runtime/empty-server-app.d.ts +0 -1
  92. package/dist/runtime/entry.client.d.ts +4 -1
  93. package/dist/runtime/entry.client.js +455 -313
  94. package/dist/runtime/entry.client.js.map +1 -1
  95. package/dist/runtime/entry.rsc.d.ts +15 -7
  96. package/dist/runtime/entry.rsc.js +811 -252
  97. package/dist/runtime/entry.rsc.js.map +1 -1
  98. package/dist/runtime/entry.ssr.d.ts +27 -10
  99. package/dist/runtime/entry.ssr.js +30 -48
  100. package/dist/runtime/entry.ssr.js.map +1 -1
  101. package/dist/runtime/failure-document.d.ts +11 -0
  102. package/dist/runtime/failure-document.js +35 -0
  103. package/dist/runtime/failure-document.js.map +1 -0
  104. package/dist/runtime/flight-inject.d.ts +23 -0
  105. package/dist/runtime/flight-inject.js +403 -0
  106. package/dist/runtime/flight-inject.js.map +1 -0
  107. package/dist/runtime/hot-update.d.ts +44 -0
  108. package/dist/runtime/hot-update.js +44 -0
  109. package/dist/runtime/hot-update.js.map +1 -0
  110. package/dist/runtime/navigation.d.ts +46 -54
  111. package/dist/runtime/navigation.js +22 -63
  112. package/dist/runtime/navigation.js.map +1 -1
  113. package/dist/runtime/request.d.ts +42 -8
  114. package/dist/runtime/request.js +84 -7
  115. package/dist/runtime/request.js.map +1 -1
  116. package/dist/runtime/server.d.ts +20 -18
  117. package/dist/runtime/server.js +22 -17
  118. package/dist/runtime/server.js.map +1 -1
  119. package/dist/runtime/validate-entries.d.ts +77 -0
  120. package/dist/runtime/validate-entries.js +405 -0
  121. package/dist/runtime/validate-entries.js.map +1 -0
  122. package/dist/server/headers.d.ts +41 -15
  123. package/dist/server/headers.js +53 -24
  124. package/dist/server/headers.js.map +1 -1
  125. package/dist/server/load-config.d.ts +4 -5
  126. package/dist/server/load-config.js +22 -14
  127. package/dist/server/load-config.js.map +1 -1
  128. package/dist/server/load-env.d.ts +0 -1
  129. package/dist/server/prerendered.d.ts +88 -34
  130. package/dist/server/prerendered.js +184 -24
  131. package/dist/server/prerendered.js.map +1 -1
  132. package/dist/server/server-config.d.ts +35 -50
  133. package/dist/server/server-config.js +27 -74
  134. package/dist/server/server-config.js.map +1 -1
  135. package/dist/server/shutdown.d.ts +5 -4
  136. package/dist/server/shutdown.js +5 -3
  137. package/dist/server/shutdown.js.map +1 -1
  138. package/dist/server/ssg.d.ts +9 -7
  139. package/dist/server/ssg.js +209 -65
  140. package/dist/server/ssg.js.map +1 -1
  141. package/dist/server/static.d.ts +0 -1
  142. package/dist/server/static.js +7 -1
  143. package/dist/server/static.js.map +1 -1
  144. package/package.json +21 -23
  145. package/dist/builder/page-files.d.ts.map +0 -1
  146. package/dist/builder/public-env.d.ts.map +0 -1
  147. package/dist/builder/rspack-config.d.ts.map +0 -1
  148. package/dist/cli/build.d.ts.map +0 -1
  149. package/dist/cli/dev.d.ts.map +0 -1
  150. package/dist/cli/index.d.ts.map +0 -1
  151. package/dist/cli/start.d.ts.map +0 -1
  152. package/dist/config.d.ts.map +0 -1
  153. package/dist/deploy/aws-lambda/runtime.d.ts.map +0 -1
  154. package/dist/deploy/build-marker.d.ts.map +0 -1
  155. package/dist/deploy/bun/runtime.d.ts +0 -11
  156. package/dist/deploy/bun/runtime.d.ts.map +0 -1
  157. package/dist/deploy/bun/runtime.js +0 -22
  158. package/dist/deploy/bun/runtime.js.map +0 -1
  159. package/dist/deploy/cloudflare/build.d.ts.map +0 -1
  160. package/dist/deploy/cloudflare/runtime.d.ts.map +0 -1
  161. package/dist/deploy/contract.d.ts.map +0 -1
  162. package/dist/deploy/deno/runtime.d.ts +0 -11
  163. package/dist/deploy/deno/runtime.d.ts.map +0 -1
  164. package/dist/deploy/deno/runtime.js +0 -16
  165. package/dist/deploy/deno/runtime.js.map +0 -1
  166. package/dist/deploy/filesystem.d.ts.map +0 -1
  167. package/dist/deploy/listen.d.ts +0 -20
  168. package/dist/deploy/listen.d.ts.map +0 -1
  169. package/dist/deploy/listen.js +0 -24
  170. package/dist/deploy/listen.js.map +0 -1
  171. package/dist/deploy/netlify/build.d.ts +0 -8
  172. package/dist/deploy/netlify/build.d.ts.map +0 -1
  173. package/dist/deploy/netlify/build.js +0 -52
  174. package/dist/deploy/netlify/build.js.map +0 -1
  175. package/dist/deploy/netlify/runtime.d.ts +0 -13
  176. package/dist/deploy/netlify/runtime.d.ts.map +0 -1
  177. package/dist/deploy/netlify/runtime.js +0 -24
  178. package/dist/deploy/netlify/runtime.js.map +0 -1
  179. package/dist/deploy/node/runtime.d.ts.map +0 -1
  180. package/dist/deploy/presets.d.ts.map +0 -1
  181. package/dist/deploy/vercel/build.d.ts.map +0 -1
  182. package/dist/deploy/vercel/runtime.d.ts.map +0 -1
  183. package/dist/index.d.ts.map +0 -1
  184. package/dist/router.d.ts.map +0 -1
  185. package/dist/runtime/boundaries.d.ts.map +0 -1
  186. package/dist/runtime/client.d.ts.map +0 -1
  187. package/dist/runtime/context.d.ts.map +0 -1
  188. package/dist/runtime/control.d.ts.map +0 -1
  189. package/dist/runtime/dev-protocol.d.ts.map +0 -1
  190. package/dist/runtime/empty-server-app.d.ts.map +0 -1
  191. package/dist/runtime/entry.client.d.ts.map +0 -1
  192. package/dist/runtime/entry.rsc.d.ts.map +0 -1
  193. package/dist/runtime/entry.ssr.d.ts.map +0 -1
  194. package/dist/runtime/navigation.d.ts.map +0 -1
  195. package/dist/runtime/request.d.ts.map +0 -1
  196. package/dist/runtime/server.d.ts.map +0 -1
  197. package/dist/server/compress.d.ts +0 -15
  198. package/dist/server/compress.d.ts.map +0 -1
  199. package/dist/server/compress.js +0 -76
  200. package/dist/server/compress.js.map +0 -1
  201. package/dist/server/headers.d.ts.map +0 -1
  202. package/dist/server/load-config.d.ts.map +0 -1
  203. package/dist/server/load-env.d.ts.map +0 -1
  204. package/dist/server/prerendered.d.ts.map +0 -1
  205. package/dist/server/server-config.d.ts.map +0 -1
  206. package/dist/server/shutdown.d.ts.map +0 -1
  207. package/dist/server/ssg.d.ts.map +0 -1
  208. package/dist/server/static.d.ts.map +0 -1
@@ -1,20 +1,30 @@
1
1
  import { rspack } from '@rspack/core';
2
2
  import { ReactRefreshRspackPlugin } from '@rspack/plugin-react-refresh';
3
- import { existsSync } from 'node:fs';
3
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
4
4
  import { createRequire } from 'node:module';
5
- import { basename, dirname, join, win32 } from 'node:path';
5
+ import { basename, dirname, join, sep, win32 } from 'node:path';
6
6
  import { resolveServerConfig } from '../server/server-config.js';
7
+ import { serverOnlyImportPlugin } from './server-only-imports.js';
7
8
  import { scanPageFiles } from './page-files.js';
8
9
  import { publicEnv } from './public-env.js';
9
- // The framework's own compiled output (this file lives in `dist/builder/`), which is what the two
10
- // compilers consume: the entries and loaders below are the *built* framework, not its TypeScript.
10
+ import { checkReactVersions } from './react-versions.js';
11
+ // This file lives in `dist/builder/`, so the entries and loaders below are the *built* framework.
11
12
  const FRAMEWORK_DIST = join(import.meta.dirname, '..');
12
13
  const FRAMEWORK_ROOT = join(FRAMEWORK_DIST, '..');
13
- const BUNDLED_PACKAGES = /^(@rshono\/core|react|react-dom|react-server-dom-rspack|rsc-html-stream|hono|@hono\/node-server)(\/|$)/;
14
+ const BUNDLED_PACKAGES = /^(@rshono\/core|react|react-dom|react-server-dom-rspack|hono|@hono\/node-server)(\/|$)/;
14
15
  /**
15
- * Packages that end up imported *by app source* without the app ever mentioning them: the RSC
16
- * transform rewrites a `'use client'` module, a page or a server action into imports of the RSC
17
- * runtime, and those requests resolve from the app's own `src/`.
16
+ * The directory a command writes its bundles to, relative to the project root one per command, never
17
+ * shared: a `rshono build` alongside a running `rshono dev` would `clean` away the route chunks that
18
+ * server imports lazily, and Rspack's watch-mode emit never re-emits a file it believes it wrote.
19
+ *
20
+ * Both names are a single level under the root, which `deploy/filesystem.ts` relies on to derive the
21
+ * project root from where the bundle ended up.
22
+ */
23
+ export const BUILD_OUT_DIR = 'dist';
24
+ export const DEV_OUT_DIR = '.rshono';
25
+ /**
26
+ * Packages the app's source ends up importing without ever naming them: the RSC transform rewrites a
27
+ * `'use client'` module, a page or an action into imports of the RSC runtime, resolved from the app's `src/`.
18
28
  */
19
29
  const INJECTED_PACKAGES = ['react-server-dom-rspack'];
20
30
  /** The `node_modules` directory a resolved file sits in — `…/node_modules/pkg/index.js` → `…/node_modules`. */
@@ -29,17 +39,13 @@ function enclosingNodeModules(file) {
29
39
  return dir;
30
40
  }
31
41
  /**
32
- * Where packages are resolved from: the app's own `node_modules`, then the framework's.
42
+ * Where packages are resolved from: the app's own `node_modules`, then the framework's — which is what makes
43
+ * {@link INJECTED_PACKAGES} resolvable from app source under pnpm, where the framework's dependencies are
44
+ * installed as siblings of `@rshono/core` rather than hoisted into the app's tree.
33
45
  *
34
- * The second part is what makes {@link INJECTED_PACKAGES} resolvable from app source. npm, yarn and bun
35
- * hoist the framework's dependencies to the app's own `node_modules`, so they need nothing; pnpm gives
36
- * `@rshono/core` a private directory and installs its dependencies as *siblings* of the framework
37
- * rather than inside it, and nothing under the app's tree can see them — the build fails with
38
- * `Can't resolve 'react-server-dom-rspack/client'` in a file the user never wrote that import into.
39
- *
40
- * Asking Node where the framework itself resolves the package finds the directory in any layout, and
41
- * making it a search path rather than an alias keeps the package's own `exports` conditions in play —
42
- * the RSC layer, the SSR layer and each deploy target need a different build of it.
46
+ * Asking Node where the framework resolves the package finds it in any layout, and a search path rather than
47
+ * an alias keeps the package's own `exports` conditions in play the RSC layer, the SSR layer and each
48
+ * deploy target need a different build of it.
43
49
  */
44
50
  function resolveModules() {
45
51
  const require = createRequire(import.meta.url);
@@ -54,20 +60,16 @@ function resolveModules() {
54
60
  // Left to the resolver to report against the request that actually needed it.
55
61
  }
56
62
  }
57
- // A checkout or a vendored copy, where the framework has real dependencies of its own and the lookup
58
- // above found nothing to add.
63
+ // A checkout or a vendored copy, where the framework has real dependencies of its own.
59
64
  dirs.add(join(FRAMEWORK_ROOT, 'node_modules'));
60
65
  return ['node_modules', ...dirs];
61
66
  }
62
67
  /**
63
68
  * Whether a request names a file rather than a package, and so belongs in the bundle.
64
69
  *
65
- * Rspack's RSC plugins ask for their client and server-entry proxies by *absolute path*, which on
66
- * Windows means a drive letter (`D:\app\src\home.tsx`) instead of a leading slash. Externalizing one
67
- * of those emits `import("D:\\app\\src\\home.tsx")`, and Node rejects that with
68
- * `ERR_UNSUPPORTED_ESM_URL_SCHEME` ("Received protocol 'd:'") — a build that works everywhere else
69
- * fails on Windows only. `win32.isAbsolute` also accepts the POSIX form, so it is used on every
70
- * platform: one code path, and the Windows shapes stay testable off Windows.
70
+ * Rspack's RSC plugins ask for their proxies by absolute path, which on Windows carries a drive letter —
71
+ * externalizing one emits an `import("D:\\…")` Node rejects. `win32.isAbsolute` accepts the POSIX form too,
72
+ * so it runs on every platform and the Windows shapes stay testable off Windows.
71
73
  */
72
74
  function isPathRequest(request) {
73
75
  return request.startsWith('.') || win32.isAbsolute(request);
@@ -75,29 +77,71 @@ function isPathRequest(request) {
75
77
  const BROWSER_TARGETS = ['last 2 versions', '> 0.2%', 'not dead', 'Firefox ESR'];
76
78
  const NODE_TARGETS = ['node >= 22'];
77
79
  /**
78
- * Rspack's native CSS pipeline, which both compilers get.
79
- *
80
- * It parses *finished* CSS, so a stylesheet needing a PostCSS plugin Tailwind, most obviously — adds
81
- * the loader through the {@link RSHonoConfig.rspack} hook, along with the two packages a PostCSS pass
82
- * takes. The framework stays out of it: `postcss` is a dependency an app that wants one can have, rather
83
- * than one every app pays for. The Styling section of the README has the four lines involved.
80
+ * Rspack's native CSS pipeline, which both compilers get. It parses *finished* CSS, so a stylesheet needing
81
+ * PostCSS — Tailwind, most obviously — adds that loader through the {@link RshonoConfig.rspack} hook, which
82
+ * keeps `postcss` a dependency of the apps that want one.
84
83
  *
85
- * A fresh object per compiler, not one shared between them the hook is handed each config in turn, and
86
- * an app that reaches in to change this rule should not find it has changed the other bundle's too.
84
+ * A fresh object per compiler, so an app changing this rule does not silently change the other bundle's.
87
85
  */
88
86
  function cssRule() {
89
87
  return { test: /\.css$/i, type: 'css/auto' };
90
88
  }
89
+ /** Source files worth reading for {@link mentions} — the app's own code, whatever dialect it is written in. */
90
+ const SOURCE_FILE = /\.[cm]?[jt]sx?$/;
91
+ /**
92
+ * Whether `identifier` appears anywhere in the app's own source. The whole tree rather than `src/server.ts`
93
+ * alone, so middleware registered from a helper module counts.
94
+ *
95
+ * A textual scan on purpose. What is being answered is "did the author think about this at all", which is a
96
+ * question a build warning is allowed to get wrong: the failure mode is a warning that is unwarranted, never a
97
+ * build that fails or a behaviour that changes. Resolving it properly would mean reading the module graph, and
98
+ * a middleware registered from a package outside `src/` would still slip past.
99
+ */
100
+ function mentions(srcDir, identifier) {
101
+ try {
102
+ for (const entry of readdirSync(srcDir, { recursive: true, withFileTypes: true })) {
103
+ if (!entry.isFile() || !SOURCE_FILE.test(entry.name))
104
+ continue;
105
+ if (readFileSync(join(entry.parentPath, entry.name), 'utf8').includes(identifier))
106
+ return true;
107
+ }
108
+ }
109
+ catch {
110
+ // An unreadable `src/` is not this function's to report: the build is about to fail on `routes.ts`.
111
+ }
112
+ return false;
113
+ }
91
114
  export function createConfigs(options) {
92
115
  const { rootDir, isDev, config, preset, onServerComponentChanges } = options;
93
116
  const srcDir = join(rootDir, 'src');
94
117
  const mode = isDev ? 'development' : 'production';
118
+ const outDir = isDev ? DEV_OUT_DIR : BUILD_OUT_DIR;
119
+ // Before anything is compiled: a react/react-dom split fails inside React at render time, and this is the
120
+ // one place that can say so with the two versions in hand.
121
+ checkReactVersions(rootDir);
95
122
  const routesFile = ['routes.ts', 'routes.tsx'].map((f) => join(srcDir, f)).find(existsSync);
96
123
  if (!routesFile) {
97
124
  throw new Error(`[rshono] src/routes.ts not found in ${rootDir} — it is the one required file.`);
98
125
  }
99
126
  const serverAppFile = ['server.ts', 'server.tsx'].map((f) => join(srcDir, f)).find(existsSync);
100
127
  const serverAppAlias = serverAppFile ?? join(FRAMEWORK_DIST, 'runtime', 'empty-server-app.js');
128
+ // Optional, and staying optional — but per-request security is Hono middleware registered there, so an app
129
+ // without one has opted out of all of it, which is worth hearing once rather than discovering. Builds only:
130
+ // `dev` would print it on every rebuild, and it is not news on a developer's machine.
131
+ if (!isDev) {
132
+ if (!serverAppFile) {
133
+ console.warn(' ⚠ No src/server.ts — this build has no CSRF check and no request body cap. Both are Hono middleware\n' +
134
+ ' (`csrf()`, `bodyLimit()`); `npx @rshono/create` scaffolds a src/server.ts with them registered.');
135
+ }
136
+ else if (!mentions(srcDir, 'bodyLimit')) {
137
+ // Having a src/server.ts is not the same as having the cap in it, and this half used to be warned about
138
+ // nowhere: every `'use server'` export is a public POST endpoint by design, and the action path buffers
139
+ // the whole body before it can decide anything about it.
140
+ console.warn(" ⚠ No bodyLimit() anywhere in src/ — this build has no request body cap. Every 'use server' export is\n" +
141
+ ' a public POST endpoint, and the action path buffers whatever arrives before it can reject it.\n' +
142
+ ' Add `server.use(bodyLimit({ maxSize: 1024 * 1024 }))` to src/server.ts (from `hono/body-limit`).');
143
+ }
144
+ }
101
145
  const rscEntry = join(FRAMEWORK_DIST, 'runtime', 'entry.rsc.js');
102
146
  const ssrEntry = join(FRAMEWORK_DIST, 'runtime', 'entry.ssr.js');
103
147
  const clientEntry = join(FRAMEWORK_DIST, 'runtime', 'entry.client.js');
@@ -123,9 +167,8 @@ export function createConfigs(options) {
123
167
  extensionAlias: { '.js': ['.ts', '.tsx', '.js'] },
124
168
  modules: resolveModules(),
125
169
  };
126
- // The platform's own resolve conditions, ahead of whatever the Rspack target implies ('...'). This
127
- // is what hands the server bundle the right build of React and the RSC runtime, both of which ship
128
- // one per runtime. Left unset for Node, where the target already implies the `node` condition.
170
+ // Ahead of whatever the Rspack target implies ('...'), which is what hands the server bundle the right
171
+ // build of React and the RSC runtime. Unset for Node, whose target already implies the `node` condition.
129
172
  const runtimeConditions = preset.resolveConditions ?? [];
130
173
  const serverResolveBase = runtimeConditions.length > 0 ? { ...resolveBase, conditionNames: [...runtimeConditions, '...'] } : resolveBase;
131
174
  const rscConditionNames = ['react-server', ...runtimeConditions, '...'];
@@ -145,10 +188,12 @@ export function createConfigs(options) {
145
188
  mode,
146
189
  target: 'web',
147
190
  context: rootDir,
191
+ // Never in a build: a client map is served from `/_static` like everything else beside it, and it would
192
+ // publish the original source of the app's own modules. Dev binds 127.0.0.1 only.
148
193
  devtool: isDev ? 'source-map' : false,
149
194
  entry: { main: clientEntry },
150
195
  output: {
151
- path: join(rootDir, 'dist', 'static'),
196
+ path: join(rootDir, outDir, 'static'),
152
197
  publicPath: '/_static/',
153
198
  clean: !isDev,
154
199
  filename: isDev ? 'chunks/main.js' : 'chunks/main.[contenthash].js',
@@ -169,6 +214,7 @@ export function createConfigs(options) {
169
214
  },
170
215
  plugins: [
171
216
  new ClientPlugin(),
217
+ serverOnlyImportPlugin(rootDir),
172
218
  new rspack.DefinePlugin({ 'process.env': JSON.stringify(publicEnv(isDev)) }),
173
219
  ...(isDev ? [new rspack.HotModuleReplacementPlugin(), new ReactRefreshRspackPlugin()] : []),
174
220
  ],
@@ -179,11 +225,17 @@ export function createConfigs(options) {
179
225
  mode,
180
226
  target: 'node',
181
227
  context: rootDir,
182
- devtool: isDev ? 'source-map' : false,
228
+ // In a build too, unlike the client's: the bundle is minified, and without a map every stack trace that
229
+ // reaches `onServerError` — the error-tracker funnel — is unmappable minified frames. A server map is
230
+ // never served to anyone; `dist/server` is not on a public path, and the runtime enables Node's own
231
+ // mapping so no host has to pass a flag.
232
+ devtool: 'source-map',
183
233
  entry: { main: rscEntry },
184
234
  output: {
185
- path: join(rootDir, 'dist', 'server'),
186
- clean: true,
235
+ path: join(rootDir, outDir, 'server'),
236
+ // Off in dev: route chunks are imported lazily, so a superseded one on disk is harmless where deleting
237
+ // one mid-request is not. `rshono dev` empties the directory on startup instead.
238
+ clean: !isDev,
187
239
  module: true,
188
240
  chunkFormat: 'module',
189
241
  chunkLoading: 'import',
@@ -194,18 +246,30 @@ export function createConfigs(options) {
194
246
  assetModuleFilename: 'assets/[name].[hash][ext]',
195
247
  },
196
248
  optimization: {
197
- minimize: false,
249
+ minimize: isDev ? false : true,
198
250
  },
199
251
  externalsType: 'module-import',
200
252
  externals: [
201
- ({ request }, callback) => {
253
+ ({ request, contextInfo }, callback) => {
202
254
  if (!request ||
203
255
  isPathRequest(request) ||
204
256
  request.startsWith('@/') ||
205
257
  request.startsWith('@rshono/') ||
206
258
  request.startsWith('builtin:') ||
207
259
  request.includes('!') ||
208
- BUNDLED_PACKAGES.test(request)) {
260
+ BUNDLED_PACKAGES.test(request) ||
261
+ // Anything reached from the SSR layer, which is where `'use client'` components are rendered on the
262
+ // server. It has to be *compiled* rather than imported by name, because the env-shadow loader is what
263
+ // keeps a secret out of SSR'd HTML and a loader cannot run on a module the bundle only names — an
264
+ // externalized third-party client component is loaded raw at request time and reads the real
265
+ // `process.env`. Widening the loader's rule to cover `node_modules` did not reach this: there was no
266
+ // module for it to run on.
267
+ //
268
+ // Free of the usual bundling trade-off, too. Every module in this layer is in the *browser* bundle as
269
+ // well, so it is already required to be bundleable — a native addon or a `__dirname` file read could
270
+ // not have been a client component in the first place. The RSC layer, where server-only dependencies
271
+ // live, keeps the externals policy.
272
+ contextInfo?.issuerLayer === Layers.ssr) {
209
273
  return callback();
210
274
  }
211
275
  callback(undefined, `module-import ${request}`);
@@ -216,8 +280,7 @@ export function createConfigs(options) {
216
280
  alias: {
217
281
  '@rshono/routes$': routesFile,
218
282
  '@rshono/server-app$': serverAppAlias,
219
- // The one import that decides which platform the server bundle is for. Split on '/' so the
220
- // preset can declare a POSIX-looking path and still resolve on Windows.
283
+ // Split on '/' so a preset can declare a POSIX-looking path and still resolve on Windows.
221
284
  '@rshono/deploy$': join(FRAMEWORK_DIST, ...preset.runtimeModule.split('/')),
222
285
  '@': srcDir,
223
286
  },
@@ -230,13 +293,33 @@ export function createConfigs(options) {
230
293
  use: [{ loader: join(FRAMEWORK_DIST, 'builder', 'page-entry-loader.cjs') }],
231
294
  },
232
295
  {
296
+ // Deliberately not scoped to `srcDir`: a `'use client'` component from `node_modules` is SSR'd in
297
+ // the same layer, and scoping this to the app's own source left those rendering against the real
298
+ // `process.env` while the browser bundle saw the `PUBLIC_`-only view — a hydration mismatch on
299
+ // anything the host sets, and a leak for anything secret. The loader's own layer check is what
300
+ // decides; every other module gets one `includes('process')` scan.
233
301
  test: /\.[cm]?[tj]sx?$/,
234
- include: srcDir,
235
302
  enforce: 'pre',
236
303
  use: [
237
304
  {
238
305
  loader: join(FRAMEWORK_DIST, 'builder', 'env-shadow-loader.cjs'),
239
- options: { prelude: `const process = { env: ${JSON.stringify(publicEnv(isDev))} }; `, layer: Layers.ssr },
306
+ // `Object.create` over the real `process`, not a bare `{ env }`: the prelude shadows the whole
307
+ // binding for that module, and `react-dom/server` is in this layer too — anything reading
308
+ // `process.nextTick` or `process.platform` has to still find it. Not a `{ __proto__: … }`
309
+ // literal, whose meaning would change if a minifier ever quoted the key.
310
+ options: {
311
+ prelude: `const process = Object.assign(Object.create(globalThis.process ?? Object.prototype), { env: ${JSON.stringify(publicEnv(isDev))} }); `,
312
+ layer: Layers.ssr,
313
+ // The prelude's binding hides `process.env.NODE_ENV` from DefinePlugin, which is what erases
314
+ // React's `if (NODE_ENV === 'production')` wrappers. The loader substitutes it back so the
315
+ // dead half still goes; without it the SSR layer ships both builds of every React package.
316
+ nodeEnv: mode,
317
+ // Only the app's own source is warned about when it reaches `process` through the global
318
+ // object, which no prelude can shadow — a library feature-detecting `globalThis.process` is
319
+ // doing nothing wrong and has no app secret to read. The separator is part of the prefix so
320
+ // a sibling directory named `src-old` is not mistaken for it.
321
+ appSrcPrefix: srcDir + sep,
322
+ },
240
323
  },
241
324
  ],
242
325
  },
@@ -263,14 +346,14 @@ export function createConfigs(options) {
263
346
  plugins: [
264
347
  pageScanPlugin,
265
348
  new ServerPlugin({ onServerComponentChanges }),
266
- // Bake the framework settings from rshono.config.ts into the bundle (read as __RSHONO_CONFIG__
267
- // in entry.rsc.tsx) — the server-side counterpart to the client's publicEnv injection.
268
- new rspack.DefinePlugin({ __RSHONO_CONFIG__: JSON.stringify(resolveServerConfig(config, { isDev })) }),
349
+ // Bakes rshono.config.ts into the bundle, read back as `__RSHONO_CONFIG__` at request time.
350
+ new rspack.DefinePlugin({
351
+ __RSHONO_CONFIG__: JSON.stringify(resolveServerConfig(config, { isDev, outDir, envBindings: preset.envBindings })),
352
+ }),
269
353
  ],
270
354
  performance: false,
271
355
  };
272
- // The platform's own overrides, then the user's hook — so an app can still adjust whatever a
273
- // preset decided.
356
+ // The platform's overrides first, then the user's hook — so an app can adjust whatever a preset decided.
274
357
  preset.configureServer?.(serverConfig);
275
358
  const rspackHook = config.rspack;
276
359
  if (rspackHook) {
@@ -1 +1 @@
1
- {"version":3,"file":"rspack-config.js","sourceRoot":"","sources":["../../src/builder/rspack-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { rspack, type Compiler, type RspackOptions, type RuleSetRule } from '@rspack/core';\nimport { ReactRefreshRspackPlugin } from '@rspack/plugin-react-refresh';\nimport { existsSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { basename, dirname, join, win32 } from 'node:path';\nimport type { RSHonoConfig } from '../config.js';\nimport type { DeployPreset } from '../deploy/presets.js';\nimport { resolveServerConfig } from '../server/server-config.js';\nimport { scanPageFiles } from './page-files.js';\nimport { publicEnv } from './public-env.js';\n\n// The framework's own compiled output (this file lives in `dist/builder/`), which is what the two\n// compilers consume: the entries and loaders below are the *built* framework, not its TypeScript.\nconst FRAMEWORK_DIST = join(import.meta.dirname, '..');\nconst FRAMEWORK_ROOT = join(FRAMEWORK_DIST, '..');\n\nconst BUNDLED_PACKAGES = /^(@rshono\\/core|react|react-dom|react-server-dom-rspack|rsc-html-stream|hono|@hono\\/node-server)(\\/|$)/;\n\n/**\n * Packages that end up imported *by app source* without the app ever mentioning them: the RSC\n * transform rewrites a `'use client'` module, a page or a server action into imports of the RSC\n * runtime, and those requests resolve from the app's own `src/`.\n */\nconst INJECTED_PACKAGES = ['react-server-dom-rspack'];\n\n/** The `node_modules` directory a resolved file sits in — `…/node_modules/pkg/index.js` → `…/node_modules`. */\nfunction enclosingNodeModules(file: string): string | undefined {\n let dir = dirname(file);\n while (basename(dir) !== 'node_modules') {\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n return dir;\n}\n\n/**\n * Where packages are resolved from: the app's own `node_modules`, then the framework's.\n *\n * The second part is what makes {@link INJECTED_PACKAGES} resolvable from app source. npm, yarn and bun\n * hoist the framework's dependencies to the app's own `node_modules`, so they need nothing; pnpm gives\n * `@rshono/core` a private directory and installs its dependencies as *siblings* of the framework\n * rather than inside it, and nothing under the app's tree can see them — the build fails with\n * `Can't resolve 'react-server-dom-rspack/client'` in a file the user never wrote that import into.\n *\n * Asking Node where the framework itself resolves the package finds the directory in any layout, and\n * making it a search path rather than an alias keeps the package's own `exports` conditions in play —\n * the RSC layer, the SSR layer and each deploy target need a different build of it.\n */\nfunction resolveModules(): string[] {\n const require = createRequire(import.meta.url);\n const dirs = new Set<string>();\n for (const name of INJECTED_PACKAGES) {\n try {\n const dir = enclosingNodeModules(require.resolve(name));\n if (dir) dirs.add(dir);\n } catch {\n // Left to the resolver to report against the request that actually needed it.\n }\n }\n // A checkout or a vendored copy, where the framework has real dependencies of its own and the lookup\n // above found nothing to add.\n dirs.add(join(FRAMEWORK_ROOT, 'node_modules'));\n return ['node_modules', ...dirs];\n}\n\n/**\n * Whether a request names a file rather than a package, and so belongs in the bundle.\n *\n * Rspack's RSC plugins ask for their client and server-entry proxies by *absolute path*, which on\n * Windows means a drive letter (`D:\\app\\src\\home.tsx`) instead of a leading slash. Externalizing one\n * of those emits `import(\"D:\\\\app\\\\src\\\\home.tsx\")`, and Node rejects that with\n * `ERR_UNSUPPORTED_ESM_URL_SCHEME` (\"Received protocol 'd:'\") — a build that works everywhere else\n * fails on Windows only. `win32.isAbsolute` also accepts the POSIX form, so it is used on every\n * platform: one code path, and the Windows shapes stay testable off Windows.\n */\nfunction isPathRequest(request: string): boolean {\n return request.startsWith('.') || win32.isAbsolute(request);\n}\n\nconst BROWSER_TARGETS = ['last 2 versions', '> 0.2%', 'not dead', 'Firefox ESR'];\nconst NODE_TARGETS = ['node >= 22'];\n\n/**\n * Rspack's native CSS pipeline, which both compilers get.\n *\n * It parses *finished* CSS, so a stylesheet needing a PostCSS plugin — Tailwind, most obviously — adds\n * the loader through the {@link RSHonoConfig.rspack} hook, along with the two packages a PostCSS pass\n * takes. The framework stays out of it: `postcss` is a dependency an app that wants one can have, rather\n * than one every app pays for. The Styling section of the README has the four lines involved.\n *\n * A fresh object per compiler, not one shared between them — the hook is handed each config in turn, and\n * an app that reaches in to change this rule should not find it has changed the other bundle's too.\n */\nfunction cssRule(): RuleSetRule {\n return { test: /\\.css$/i, type: 'css/auto' };\n}\n\nexport interface RspackConfigOptions {\n rootDir: string;\n isDev: boolean;\n /** The project's resolved config — supplies the {@link RSHonoConfig.rspack} hook and the runtime settings baked into the server bundle. */\n config: RSHonoConfig;\n /** The platform being built for: decides the `@rshono/deploy` runtime and any server-compiler overrides. */\n preset: DeployPreset;\n onServerComponentChanges?: () => void;\n}\n\nexport function createConfigs(options: RspackConfigOptions): [RspackOptions, RspackOptions] {\n const { rootDir, isDev, config, preset, onServerComponentChanges } = options;\n const srcDir = join(rootDir, 'src');\n const mode = isDev ? 'development' : 'production';\n\n const routesFile = ['routes.ts', 'routes.tsx'].map((f) => join(srcDir, f)).find(existsSync);\n if (!routesFile) {\n throw new Error(`[rshono] src/routes.ts not found in ${rootDir} — it is the one required file.`);\n }\n const serverAppFile = ['server.ts', 'server.tsx'].map((f) => join(srcDir, f)).find(existsSync);\n const serverAppAlias = serverAppFile ?? join(FRAMEWORK_DIST, 'runtime', 'empty-server-app.js');\n\n const rscEntry = join(FRAMEWORK_DIST, 'runtime', 'entry.rsc.js');\n const ssrEntry = join(FRAMEWORK_DIST, 'runtime', 'entry.ssr.js');\n const clientEntry = join(FRAMEWORK_DIST, 'runtime', 'entry.client.js');\n\n const swcRule = (targets: string[]): RuleSetRule => ({\n test: /\\.[cm]?[jt]sx?$/,\n exclude: /[\\\\/]core-js[\\\\/]/,\n use: {\n loader: 'builtin:swc-loader',\n options: {\n detectSyntax: 'auto',\n jsc: {\n transform: { react: { runtime: 'automatic', development: isDev } },\n experimental: { keepImportAttributes: true },\n },\n env: { targets },\n rspackExperiments: { reactServerComponents: true },\n },\n },\n type: 'javascript/auto',\n });\n\n const resolveBase = {\n extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'],\n extensionAlias: { '.js': ['.ts', '.tsx', '.js'] },\n modules: resolveModules(),\n };\n\n // The platform's own resolve conditions, ahead of whatever the Rspack target implies ('...'). This\n // is what hands the server bundle the right build of React and the RSC runtime, both of which ship\n // one per runtime. Left unset for Node, where the target already implies the `node` condition.\n const runtimeConditions = preset.resolveConditions ?? [];\n const serverResolveBase = runtimeConditions.length > 0 ? { ...resolveBase, conditionNames: [...runtimeConditions, '...'] } : resolveBase;\n const rscConditionNames = ['react-server', ...runtimeConditions, '...'];\n\n const { ServerPlugin, ClientPlugin } = rspack.experiments.rsc.createPlugins();\n const { Layers } = rspack.experiments.rsc;\n\n const pageFiles = new Set<string>();\n scanPageFiles(routesFile, srcDir, pageFiles);\n const pageScanPlugin = {\n apply(compiler: Compiler) {\n const refresh = () => scanPageFiles(routesFile, srcDir, pageFiles);\n compiler.hooks.beforeRun.tap('rshono/page-scan', refresh);\n compiler.hooks.watchRun.tap('rshono/page-scan', refresh);\n },\n };\n\n const clientConfig: RspackOptions = {\n name: 'client',\n mode,\n target: 'web',\n context: rootDir,\n devtool: isDev ? 'source-map' : false,\n entry: { main: clientEntry },\n output: {\n path: join(rootDir, 'dist', 'static'),\n publicPath: '/_static/',\n clean: !isDev,\n filename: isDev ? 'chunks/main.js' : 'chunks/main.[contenthash].js',\n chunkFilename: isDev ? 'chunks/[name].js' : 'chunks/[name].[contenthash].js',\n cssFilename: isDev ? 'chunks/[name].css' : 'chunks/[name].[contenthash].css',\n cssChunkFilename: isDev ? 'chunks/[name].css' : 'chunks/[name].[contenthash].css',\n assetModuleFilename: 'assets/[name].[hash][ext]',\n },\n optimization: {\n moduleIds: isDev ? 'named' : 'hashed',\n },\n resolve: {\n ...resolveBase,\n alias: { '@': srcDir },\n },\n module: {\n rules: [swcRule(BROWSER_TARGETS), cssRule(), { test: /\\.(png|jpe?g|gif|webp|avif|ico|svg|woff2?|ttf|otf)$/i, type: 'asset' }],\n },\n plugins: [\n new ClientPlugin(),\n new rspack.DefinePlugin({ 'process.env': JSON.stringify(publicEnv(isDev)) }),\n ...(isDev ? [new rspack.HotModuleReplacementPlugin(), new ReactRefreshRspackPlugin()] : []),\n ],\n performance: false,\n };\n\n const serverConfig: RspackOptions = {\n name: 'server',\n mode,\n target: 'node',\n context: rootDir,\n devtool: isDev ? 'source-map' : false,\n entry: { main: rscEntry },\n output: {\n path: join(rootDir, 'dist', 'server'),\n clean: true,\n module: true,\n chunkFormat: 'module',\n chunkLoading: 'import',\n library: { type: 'module' },\n filename: 'main.mjs',\n chunkFilename: 'chunks/[name].mjs',\n publicPath: '/_static/',\n assetModuleFilename: 'assets/[name].[hash][ext]',\n },\n optimization: {\n minimize: false,\n },\n externalsType: 'module-import',\n externals: [\n ({ request }, callback) => {\n if (\n !request ||\n isPathRequest(request) ||\n request.startsWith('@/') ||\n request.startsWith('@rshono/') ||\n request.startsWith('builtin:') ||\n request.includes('!') ||\n BUNDLED_PACKAGES.test(request)\n ) {\n return callback();\n }\n callback(undefined, `module-import ${request}`);\n },\n ],\n resolve: {\n ...serverResolveBase,\n alias: {\n '@rshono/routes$': routesFile,\n '@rshono/server-app$': serverAppAlias,\n // The one import that decides which platform the server bundle is for. Split on '/' so the\n // preset can declare a POSIX-looking path and still resolve on Windows.\n '@rshono/deploy$': join(FRAMEWORK_DIST, ...preset.runtimeModule.split('/')),\n '@': srcDir,\n },\n },\n module: {\n rules: [\n {\n test: (resource: string) => pageFiles.has(resource),\n enforce: 'pre',\n use: [{ loader: join(FRAMEWORK_DIST, 'builder', 'page-entry-loader.cjs') }],\n },\n {\n test: /\\.[cm]?[tj]sx?$/,\n include: srcDir,\n enforce: 'pre',\n use: [\n {\n loader: join(FRAMEWORK_DIST, 'builder', 'env-shadow-loader.cjs'),\n options: { prelude: `const process = { env: ${JSON.stringify(publicEnv(isDev))} }; `, layer: Layers.ssr },\n },\n ],\n },\n swcRule([...(preset.syntaxTargets ?? NODE_TARGETS)]),\n cssRule(),\n {\n test: /\\.(png|jpe?g|gif|webp|avif|ico|svg|woff2?|ttf|otf)$/i,\n type: 'asset',\n generator: { emit: false },\n },\n { resource: ssrEntry, layer: Layers.ssr },\n {\n resource: rscEntry,\n layer: Layers.rsc,\n resolve: { conditionNames: rscConditionNames },\n },\n {\n issuerLayer: Layers.rsc,\n exclude: ssrEntry,\n resolve: { conditionNames: rscConditionNames },\n },\n ],\n },\n plugins: [\n pageScanPlugin,\n new ServerPlugin({ onServerComponentChanges }),\n // Bake the framework settings from rshono.config.ts into the bundle (read as __RSHONO_CONFIG__\n // in entry.rsc.tsx) — the server-side counterpart to the client's publicEnv injection.\n new rspack.DefinePlugin({ __RSHONO_CONFIG__: JSON.stringify(resolveServerConfig(config, { isDev })) }),\n ],\n performance: false,\n };\n\n // The platform's own overrides, then the user's hook — so an app can still adjust whatever a\n // preset decided.\n preset.configureServer?.(serverConfig);\n\n const rspackHook = config.rspack;\n if (rspackHook) {\n return [\n rspackHook(clientConfig, { isServer: false, isDev }) ?? clientConfig,\n rspackHook(serverConfig, { isServer: true, isDev }) ?? serverConfig,\n ];\n }\n return [clientConfig, serverConfig];\n}\n"]}
1
+ {"version":3,"file":"rspack-config.js","sourceRoot":"","sources":["../../src/builder/rspack-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { rspack, type Compiler, type RspackOptions, type RuleSetRule } from '@rspack/core';\nimport { ReactRefreshRspackPlugin } from '@rspack/plugin-react-refresh';\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { basename, dirname, join, sep, win32 } from 'node:path';\nimport type { RshonoConfig } from '../config.js';\nimport type { DeployPreset } from '../deploy/presets.js';\nimport { resolveServerConfig } from '../server/server-config.js';\nimport { serverOnlyImportPlugin } from './server-only-imports.js';\nimport { scanPageFiles } from './page-files.js';\nimport { publicEnv } from './public-env.js';\nimport { checkReactVersions } from './react-versions.js';\n\n// This file lives in `dist/builder/`, so the entries and loaders below are the *built* framework.\nconst FRAMEWORK_DIST = join(import.meta.dirname, '..');\nconst FRAMEWORK_ROOT = join(FRAMEWORK_DIST, '..');\n\nconst BUNDLED_PACKAGES = /^(@rshono\\/core|react|react-dom|react-server-dom-rspack|hono|@hono\\/node-server)(\\/|$)/;\n\n/**\n * The directory a command writes its bundles to, relative to the project root — one per command, never\n * shared: a `rshono build` alongside a running `rshono dev` would `clean` away the route chunks that\n * server imports lazily, and Rspack's watch-mode emit never re-emits a file it believes it wrote.\n *\n * Both names are a single level under the root, which `deploy/filesystem.ts` relies on to derive the\n * project root from where the bundle ended up.\n */\nexport const BUILD_OUT_DIR = 'dist';\nexport const DEV_OUT_DIR = '.rshono';\n\n/**\n * Packages the app's source ends up importing without ever naming them: the RSC transform rewrites a\n * `'use client'` module, a page or an action into imports of the RSC runtime, resolved from the app's `src/`.\n */\nconst INJECTED_PACKAGES = ['react-server-dom-rspack'];\n\n/** The `node_modules` directory a resolved file sits in — `…/node_modules/pkg/index.js` → `…/node_modules`. */\nfunction enclosingNodeModules(file: string): string | undefined {\n let dir = dirname(file);\n while (basename(dir) !== 'node_modules') {\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n return dir;\n}\n\n/**\n * Where packages are resolved from: the app's own `node_modules`, then the framework's — which is what makes\n * {@link INJECTED_PACKAGES} resolvable from app source under pnpm, where the framework's dependencies are\n * installed as siblings of `@rshono/core` rather than hoisted into the app's tree.\n *\n * Asking Node where the framework resolves the package finds it in any layout, and a search path rather than\n * an alias keeps the package's own `exports` conditions in play — the RSC layer, the SSR layer and each\n * deploy target need a different build of it.\n */\nfunction resolveModules(): string[] {\n const require = createRequire(import.meta.url);\n const dirs = new Set<string>();\n for (const name of INJECTED_PACKAGES) {\n try {\n const dir = enclosingNodeModules(require.resolve(name));\n if (dir) dirs.add(dir);\n } catch {\n // Left to the resolver to report against the request that actually needed it.\n }\n }\n // A checkout or a vendored copy, where the framework has real dependencies of its own.\n dirs.add(join(FRAMEWORK_ROOT, 'node_modules'));\n return ['node_modules', ...dirs];\n}\n\n/**\n * Whether a request names a file rather than a package, and so belongs in the bundle.\n *\n * Rspack's RSC plugins ask for their proxies by absolute path, which on Windows carries a drive letter —\n * externalizing one emits an `import(\"D:\\\\…\")` Node rejects. `win32.isAbsolute` accepts the POSIX form too,\n * so it runs on every platform and the Windows shapes stay testable off Windows.\n */\nfunction isPathRequest(request: string): boolean {\n return request.startsWith('.') || win32.isAbsolute(request);\n}\n\nconst BROWSER_TARGETS = ['last 2 versions', '> 0.2%', 'not dead', 'Firefox ESR'];\nconst NODE_TARGETS = ['node >= 22'];\n\n/**\n * Rspack's native CSS pipeline, which both compilers get. It parses *finished* CSS, so a stylesheet needing\n * PostCSS — Tailwind, most obviously — adds that loader through the {@link RshonoConfig.rspack} hook, which\n * keeps `postcss` a dependency of the apps that want one.\n *\n * A fresh object per compiler, so an app changing this rule does not silently change the other bundle's.\n */\nfunction cssRule(): RuleSetRule {\n return { test: /\\.css$/i, type: 'css/auto' };\n}\n\nexport interface RspackConfigOptions {\n rootDir: string;\n isDev: boolean;\n /** The project's resolved config — supplies the {@link RshonoConfig.rspack} hook and the runtime settings baked into the server bundle. */\n config: RshonoConfig;\n /** The platform being built for: decides the `@rshono/deploy` runtime and any server-compiler overrides. */\n preset: DeployPreset;\n onServerComponentChanges?: () => void;\n}\n\n/** Source files worth reading for {@link mentions} — the app's own code, whatever dialect it is written in. */\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\n\n/**\n * Whether `identifier` appears anywhere in the app's own source. The whole tree rather than `src/server.ts`\n * alone, so middleware registered from a helper module counts.\n *\n * A textual scan on purpose. What is being answered is \"did the author think about this at all\", which is a\n * question a build warning is allowed to get wrong: the failure mode is a warning that is unwarranted, never a\n * build that fails or a behaviour that changes. Resolving it properly would mean reading the module graph, and\n * a middleware registered from a package outside `src/` would still slip past.\n */\nfunction mentions(srcDir: string, identifier: string): boolean {\n try {\n for (const entry of readdirSync(srcDir, { recursive: true, withFileTypes: true })) {\n if (!entry.isFile() || !SOURCE_FILE.test(entry.name)) continue;\n if (readFileSync(join(entry.parentPath, entry.name), 'utf8').includes(identifier)) return true;\n }\n } catch {\n // An unreadable `src/` is not this function's to report: the build is about to fail on `routes.ts`.\n }\n return false;\n}\n\nexport function createConfigs(options: RspackConfigOptions): [RspackOptions, RspackOptions] {\n const { rootDir, isDev, config, preset, onServerComponentChanges } = options;\n const srcDir = join(rootDir, 'src');\n const mode = isDev ? 'development' : 'production';\n const outDir = isDev ? DEV_OUT_DIR : BUILD_OUT_DIR;\n\n // Before anything is compiled: a react/react-dom split fails inside React at render time, and this is the\n // one place that can say so with the two versions in hand.\n checkReactVersions(rootDir);\n\n const routesFile = ['routes.ts', 'routes.tsx'].map((f) => join(srcDir, f)).find(existsSync);\n if (!routesFile) {\n throw new Error(`[rshono] src/routes.ts not found in ${rootDir} — it is the one required file.`);\n }\n const serverAppFile = ['server.ts', 'server.tsx'].map((f) => join(srcDir, f)).find(existsSync);\n const serverAppAlias = serverAppFile ?? join(FRAMEWORK_DIST, 'runtime', 'empty-server-app.js');\n // Optional, and staying optional — but per-request security is Hono middleware registered there, so an app\n // without one has opted out of all of it, which is worth hearing once rather than discovering. Builds only:\n // `dev` would print it on every rebuild, and it is not news on a developer's machine.\n if (!isDev) {\n if (!serverAppFile) {\n console.warn(\n ' ⚠ No src/server.ts — this build has no CSRF check and no request body cap. Both are Hono middleware\\n' +\n ' (`csrf()`, `bodyLimit()`); `npx @rshono/create` scaffolds a src/server.ts with them registered.',\n );\n } else if (!mentions(srcDir, 'bodyLimit')) {\n // Having a src/server.ts is not the same as having the cap in it, and this half used to be warned about\n // nowhere: every `'use server'` export is a public POST endpoint by design, and the action path buffers\n // the whole body before it can decide anything about it.\n console.warn(\n \" ⚠ No bodyLimit() anywhere in src/ — this build has no request body cap. Every 'use server' export is\\n\" +\n ' a public POST endpoint, and the action path buffers whatever arrives before it can reject it.\\n' +\n ' Add `server.use(bodyLimit({ maxSize: 1024 * 1024 }))` to src/server.ts (from `hono/body-limit`).',\n );\n }\n }\n\n const rscEntry = join(FRAMEWORK_DIST, 'runtime', 'entry.rsc.js');\n const ssrEntry = join(FRAMEWORK_DIST, 'runtime', 'entry.ssr.js');\n const clientEntry = join(FRAMEWORK_DIST, 'runtime', 'entry.client.js');\n\n const swcRule = (targets: string[]): RuleSetRule => ({\n test: /\\.[cm]?[jt]sx?$/,\n exclude: /[\\\\/]core-js[\\\\/]/,\n use: {\n loader: 'builtin:swc-loader',\n options: {\n detectSyntax: 'auto',\n jsc: {\n transform: { react: { runtime: 'automatic', development: isDev } },\n experimental: { keepImportAttributes: true },\n },\n env: { targets },\n rspackExperiments: { reactServerComponents: true },\n },\n },\n type: 'javascript/auto',\n });\n\n const resolveBase = {\n extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'],\n extensionAlias: { '.js': ['.ts', '.tsx', '.js'] },\n modules: resolveModules(),\n };\n\n // Ahead of whatever the Rspack target implies ('...'), which is what hands the server bundle the right\n // build of React and the RSC runtime. Unset for Node, whose target already implies the `node` condition.\n const runtimeConditions = preset.resolveConditions ?? [];\n const serverResolveBase = runtimeConditions.length > 0 ? { ...resolveBase, conditionNames: [...runtimeConditions, '...'] } : resolveBase;\n const rscConditionNames = ['react-server', ...runtimeConditions, '...'];\n\n const { ServerPlugin, ClientPlugin } = rspack.experiments.rsc.createPlugins();\n const { Layers } = rspack.experiments.rsc;\n\n const pageFiles = new Set<string>();\n scanPageFiles(routesFile, srcDir, pageFiles);\n const pageScanPlugin = {\n apply(compiler: Compiler) {\n const refresh = () => scanPageFiles(routesFile, srcDir, pageFiles);\n compiler.hooks.beforeRun.tap('rshono/page-scan', refresh);\n compiler.hooks.watchRun.tap('rshono/page-scan', refresh);\n },\n };\n\n const clientConfig: RspackOptions = {\n name: 'client',\n mode,\n target: 'web',\n context: rootDir,\n // Never in a build: a client map is served from `/_static` like everything else beside it, and it would\n // publish the original source of the app's own modules. Dev binds 127.0.0.1 only.\n devtool: isDev ? 'source-map' : false,\n entry: { main: clientEntry },\n output: {\n path: join(rootDir, outDir, 'static'),\n publicPath: '/_static/',\n clean: !isDev,\n filename: isDev ? 'chunks/main.js' : 'chunks/main.[contenthash].js',\n chunkFilename: isDev ? 'chunks/[name].js' : 'chunks/[name].[contenthash].js',\n cssFilename: isDev ? 'chunks/[name].css' : 'chunks/[name].[contenthash].css',\n cssChunkFilename: isDev ? 'chunks/[name].css' : 'chunks/[name].[contenthash].css',\n assetModuleFilename: 'assets/[name].[hash][ext]',\n },\n optimization: {\n moduleIds: isDev ? 'named' : 'hashed',\n },\n resolve: {\n ...resolveBase,\n alias: { '@': srcDir },\n },\n module: {\n rules: [swcRule(BROWSER_TARGETS), cssRule(), { test: /\\.(png|jpe?g|gif|webp|avif|ico|svg|woff2?|ttf|otf)$/i, type: 'asset' }],\n },\n plugins: [\n new ClientPlugin(),\n serverOnlyImportPlugin(rootDir),\n new rspack.DefinePlugin({ 'process.env': JSON.stringify(publicEnv(isDev)) }),\n ...(isDev ? [new rspack.HotModuleReplacementPlugin(), new ReactRefreshRspackPlugin()] : []),\n ],\n performance: false,\n };\n\n const serverConfig: RspackOptions = {\n name: 'server',\n mode,\n target: 'node',\n context: rootDir,\n // In a build too, unlike the client's: the bundle is minified, and without a map every stack trace that\n // reaches `onServerError` — the error-tracker funnel — is unmappable minified frames. A server map is\n // never served to anyone; `dist/server` is not on a public path, and the runtime enables Node's own\n // mapping so no host has to pass a flag.\n devtool: 'source-map',\n entry: { main: rscEntry },\n output: {\n path: join(rootDir, outDir, 'server'),\n // Off in dev: route chunks are imported lazily, so a superseded one on disk is harmless where deleting\n // one mid-request is not. `rshono dev` empties the directory on startup instead.\n clean: !isDev,\n module: true,\n chunkFormat: 'module',\n chunkLoading: 'import',\n library: { type: 'module' },\n filename: 'main.mjs',\n chunkFilename: 'chunks/[name].mjs',\n publicPath: '/_static/',\n assetModuleFilename: 'assets/[name].[hash][ext]',\n },\n optimization: {\n minimize: isDev ? false : true,\n },\n externalsType: 'module-import',\n externals: [\n ({ request, contextInfo }, callback) => {\n if (\n !request ||\n isPathRequest(request) ||\n request.startsWith('@/') ||\n request.startsWith('@rshono/') ||\n request.startsWith('builtin:') ||\n request.includes('!') ||\n BUNDLED_PACKAGES.test(request) ||\n // Anything reached from the SSR layer, which is where `'use client'` components are rendered on the\n // server. It has to be *compiled* rather than imported by name, because the env-shadow loader is what\n // keeps a secret out of SSR'd HTML and a loader cannot run on a module the bundle only names — an\n // externalized third-party client component is loaded raw at request time and reads the real\n // `process.env`. Widening the loader's rule to cover `node_modules` did not reach this: there was no\n // module for it to run on.\n //\n // Free of the usual bundling trade-off, too. Every module in this layer is in the *browser* bundle as\n // well, so it is already required to be bundleable — a native addon or a `__dirname` file read could\n // not have been a client component in the first place. The RSC layer, where server-only dependencies\n // live, keeps the externals policy.\n contextInfo?.issuerLayer === Layers.ssr\n ) {\n return callback();\n }\n callback(undefined, `module-import ${request}`);\n },\n ],\n resolve: {\n ...serverResolveBase,\n alias: {\n '@rshono/routes$': routesFile,\n '@rshono/server-app$': serverAppAlias,\n // Split on '/' so a preset can declare a POSIX-looking path and still resolve on Windows.\n '@rshono/deploy$': join(FRAMEWORK_DIST, ...preset.runtimeModule.split('/')),\n '@': srcDir,\n },\n },\n module: {\n rules: [\n {\n test: (resource: string) => pageFiles.has(resource),\n enforce: 'pre',\n use: [{ loader: join(FRAMEWORK_DIST, 'builder', 'page-entry-loader.cjs') }],\n },\n {\n // Deliberately not scoped to `srcDir`: a `'use client'` component from `node_modules` is SSR'd in\n // the same layer, and scoping this to the app's own source left those rendering against the real\n // `process.env` while the browser bundle saw the `PUBLIC_`-only view — a hydration mismatch on\n // anything the host sets, and a leak for anything secret. The loader's own layer check is what\n // decides; every other module gets one `includes('process')` scan.\n test: /\\.[cm]?[tj]sx?$/,\n enforce: 'pre',\n use: [\n {\n loader: join(FRAMEWORK_DIST, 'builder', 'env-shadow-loader.cjs'),\n // `Object.create` over the real `process`, not a bare `{ env }`: the prelude shadows the whole\n // binding for that module, and `react-dom/server` is in this layer too — anything reading\n // `process.nextTick` or `process.platform` has to still find it. Not a `{ __proto__: … }`\n // literal, whose meaning would change if a minifier ever quoted the key.\n options: {\n prelude: `const process = Object.assign(Object.create(globalThis.process ?? Object.prototype), { env: ${JSON.stringify(publicEnv(isDev))} }); `,\n layer: Layers.ssr,\n // The prelude's binding hides `process.env.NODE_ENV` from DefinePlugin, which is what erases\n // React's `if (NODE_ENV === 'production')` wrappers. The loader substitutes it back so the\n // dead half still goes; without it the SSR layer ships both builds of every React package.\n nodeEnv: mode,\n // Only the app's own source is warned about when it reaches `process` through the global\n // object, which no prelude can shadow — a library feature-detecting `globalThis.process` is\n // doing nothing wrong and has no app secret to read. The separator is part of the prefix so\n // a sibling directory named `src-old` is not mistaken for it.\n appSrcPrefix: srcDir + sep,\n },\n },\n ],\n },\n swcRule([...(preset.syntaxTargets ?? NODE_TARGETS)]),\n cssRule(),\n {\n test: /\\.(png|jpe?g|gif|webp|avif|ico|svg|woff2?|ttf|otf)$/i,\n type: 'asset',\n generator: { emit: false },\n },\n { resource: ssrEntry, layer: Layers.ssr },\n {\n resource: rscEntry,\n layer: Layers.rsc,\n resolve: { conditionNames: rscConditionNames },\n },\n {\n issuerLayer: Layers.rsc,\n exclude: ssrEntry,\n resolve: { conditionNames: rscConditionNames },\n },\n ],\n },\n plugins: [\n pageScanPlugin,\n new ServerPlugin({ onServerComponentChanges }),\n // Bakes rshono.config.ts into the bundle, read back as `__RSHONO_CONFIG__` at request time.\n new rspack.DefinePlugin({\n __RSHONO_CONFIG__: JSON.stringify(resolveServerConfig(config, { isDev, outDir, envBindings: preset.envBindings })),\n }),\n ],\n performance: false,\n };\n\n // The platform's overrides first, then the user's hook — so an app can adjust whatever a preset decided.\n preset.configureServer?.(serverConfig);\n\n const rspackHook = config.rspack;\n if (rspackHook) {\n return [\n rspackHook(clientConfig, { isServer: false, isDev }) ?? clientConfig,\n rspackHook(serverConfig, { isServer: true, isDev }) ?? serverConfig,\n ];\n }\n return [clientConfig, serverConfig];\n}\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The one import mistake the client compiler could not explain: `@rshono/core/server` reached from a
3
+ * `'use client'` module.
4
+ *
5
+ * The entry point is server-only by construction — `getRequestContext()` is an `AsyncLocalStorage` lookup —
6
+ * so the browser bundle cannot have it, and the build is right to fail. What it used to fail *with* was the
7
+ * resolver's own report of the first `node:` builtin three modules down:
8
+ *
9
+ * ```
10
+ * ERROR in node:async_hooks
11
+ * × Reading from "node:async_hooks" is not handled by plugins (Unhandled scheme).
12
+ * ```
13
+ *
14
+ * No file path, no issuer, and no mention of rshono or of the import that caused it. Everything else in the
15
+ * framework names the file to open; this was the one common mistake that did not, and the docs warn against it
16
+ * often enough to suggest people make it.
17
+ *
18
+ * So the request is caught before it resolves, where the *issuer* is still known, and reported as itself.
19
+ */
20
+ import type { Compiler } from '@rspack/core';
21
+ /**
22
+ * Reports a server-only import by naming the module that made it.
23
+ *
24
+ * Two halves, because the resolver and the error list are reachable at different times. `beforeResolve` is
25
+ * where `contextInfo.issuer` is still on the request, and returning `false` there drops the dependency — which
26
+ * is what keeps the raw `node:` scheme error from being the build's only output. The error itself is pushed
27
+ * once the compilation exists to hold it, so it prints as an error against the compilation rather than as a
28
+ * throw out of a resolver hook.
29
+ */
30
+ export declare function serverOnlyImportPlugin(rootDir: string): {
31
+ apply(compiler: Compiler): void;
32
+ };
@@ -0,0 +1,51 @@
1
+ import { relative } from 'node:path';
2
+ /** Requests that exist only on the server, and what a browser module should reach for instead. */
3
+ const SERVER_ONLY = {
4
+ '@rshono/core/server': [
5
+ 'It is server-only by construction: `getRequestContext()` reads an AsyncLocalStorage store that exists only while a request is being handled.',
6
+ "In a 'use client' component, read the URL and params with `useNavigation()` from '@rshono/core/client', navigate with the `router` it returns, and take anything else as a prop from the server component that renders this one.",
7
+ "If this module was not meant to be a client component, the 'use client' directive at the top of it — or of a module it imports — is what put it in the browser bundle.",
8
+ ].join('\n'),
9
+ };
10
+ /**
11
+ * Reports a server-only import by naming the module that made it.
12
+ *
13
+ * Two halves, because the resolver and the error list are reachable at different times. `beforeResolve` is
14
+ * where `contextInfo.issuer` is still on the request, and returning `false` there drops the dependency — which
15
+ * is what keeps the raw `node:` scheme error from being the build's only output. The error itself is pushed
16
+ * once the compilation exists to hold it, so it prints as an error against the compilation rather than as a
17
+ * throw out of a resolver hook.
18
+ */
19
+ export function serverOnlyImportPlugin(rootDir) {
20
+ const NAME = 'rshono/server-only-imports';
21
+ return {
22
+ apply(compiler) {
23
+ /** Issuer → the request it made. A Map, so one module importing the same thing twice is reported once. */
24
+ const offenders = new Map();
25
+ compiler.hooks.normalModuleFactory.tap(NAME, (factory) => {
26
+ factory.hooks.beforeResolve.tap(NAME, (data) => {
27
+ // `hasOwn`, not `in`: a module named `toString` would otherwise inherit a match from Object.prototype.
28
+ if (!Object.hasOwn(SERVER_ONLY, data.request))
29
+ return;
30
+ offenders.set(data.contextInfo?.issuer ?? '', data.request);
31
+ // Dropped rather than left to fail: the resolver's report would name a `node:` builtin the author
32
+ // never wrote, and it would arrive beside this one.
33
+ return false;
34
+ });
35
+ });
36
+ // Cleared per compilation, so a watch rebuild that fixed the import stops reporting it.
37
+ compiler.hooks.thisCompilation.tap(NAME, () => offenders.clear());
38
+ compiler.hooks.afterCompile.tap(NAME, (compilation) => {
39
+ for (const [issuer, request] of offenders) {
40
+ const where = issuer ? relative(rootDir, issuer) : 'a module in the browser bundle';
41
+ // `file` is what Rspack puts on its own "ERROR in …" line, so the path lands where a reader looks
42
+ // for one instead of being the first words of the message.
43
+ compilation.errors.push(Object.assign(new Error(`[rshono] ${where} imports '${request}', which the browser bundle cannot have.\n${SERVER_ONLY[request]}`), {
44
+ file: where,
45
+ }));
46
+ }
47
+ });
48
+ },
49
+ };
50
+ }
51
+ //# sourceMappingURL=server-only-imports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-only-imports.js","sourceRoot":"","sources":["../../src/builder/server-only-imports.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,kGAAkG;AAClG,MAAM,WAAW,GAA2B;IAC1C,qBAAqB,EAAE;QACrB,8IAA8I;QAC9I,kOAAkO;QAClO,wKAAwK;KACzK,CAAC,IAAI,CAAC,IAAI,CAAC;CACb,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe;IACpD,MAAM,IAAI,GAAG,4BAA4B,CAAC;IAC1C,OAAO;QACL,KAAK,CAAC,QAAkB;YACtB,0GAA0G;YAC1G,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;YAE5C,QAAQ,CAAC,KAAK,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE;gBACvD,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;oBAC7C,uGAAuG;oBACvG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC;wBAAE,OAAO;oBACtD,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,IAAI,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;oBAC5D,kGAAkG;oBAClG,oDAAoD;oBACpD,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,wFAAwF;YACxF,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;YAElE,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE;gBACpD,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,SAAS,EAAE,CAAC;oBAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,gCAAgC,CAAC;oBACpF,kGAAkG;oBAClG,2DAA2D;oBAC3D,WAAW,CAAC,MAAM,CAAC,IAAI,CACrB,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,YAAY,KAAK,aAAa,OAAO,6CAA6C,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;wBACjI,IAAI,EAAE,KAAK;qBACZ,CAAC,CACH,CAAC;gBACJ,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The one import mistake the client compiler could not explain: `@rshono/core/server` reached from a\n * `'use client'` module.\n *\n * The entry point is server-only by construction — `getRequestContext()` is an `AsyncLocalStorage` lookup —\n * so the browser bundle cannot have it, and the build is right to fail. What it used to fail *with* was the\n * resolver's own report of the first `node:` builtin three modules down:\n *\n * ```\n * ERROR in node:async_hooks\n * × Reading from \"node:async_hooks\" is not handled by plugins (Unhandled scheme).\n * ```\n *\n * No file path, no issuer, and no mention of rshono or of the import that caused it. Everything else in the\n * framework names the file to open; this was the one common mistake that did not, and the docs warn against it\n * often enough to suggest people make it.\n *\n * So the request is caught before it resolves, where the *issuer* is still known, and reported as itself.\n */\nimport type { Compiler } from '@rspack/core';\nimport { relative } from 'node:path';\n\n/** Requests that exist only on the server, and what a browser module should reach for instead. */\nconst SERVER_ONLY: Record<string, string> = {\n '@rshono/core/server': [\n 'It is server-only by construction: `getRequestContext()` reads an AsyncLocalStorage store that exists only while a request is being handled.',\n \"In a 'use client' component, read the URL and params with `useNavigation()` from '@rshono/core/client', navigate with the `router` it returns, and take anything else as a prop from the server component that renders this one.\",\n \"If this module was not meant to be a client component, the 'use client' directive at the top of it — or of a module it imports — is what put it in the browser bundle.\",\n ].join('\\n'),\n};\n\n/**\n * Reports a server-only import by naming the module that made it.\n *\n * Two halves, because the resolver and the error list are reachable at different times. `beforeResolve` is\n * where `contextInfo.issuer` is still on the request, and returning `false` there drops the dependency — which\n * is what keeps the raw `node:` scheme error from being the build's only output. The error itself is pushed\n * once the compilation exists to hold it, so it prints as an error against the compilation rather than as a\n * throw out of a resolver hook.\n */\nexport function serverOnlyImportPlugin(rootDir: string): { apply(compiler: Compiler): void } {\n const NAME = 'rshono/server-only-imports';\n return {\n apply(compiler: Compiler): void {\n /** Issuer → the request it made. A Map, so one module importing the same thing twice is reported once. */\n const offenders = new Map<string, string>();\n\n compiler.hooks.normalModuleFactory.tap(NAME, (factory) => {\n factory.hooks.beforeResolve.tap(NAME, (data) => {\n // `hasOwn`, not `in`: a module named `toString` would otherwise inherit a match from Object.prototype.\n if (!Object.hasOwn(SERVER_ONLY, data.request)) return;\n offenders.set(data.contextInfo?.issuer ?? '', data.request);\n // Dropped rather than left to fail: the resolver's report would name a `node:` builtin the author\n // never wrote, and it would arrive beside this one.\n return false;\n });\n });\n\n // Cleared per compilation, so a watch rebuild that fixed the import stops reporting it.\n compiler.hooks.thisCompilation.tap(NAME, () => offenders.clear());\n\n compiler.hooks.afterCompile.tap(NAME, (compilation) => {\n for (const [issuer, request] of offenders) {\n const where = issuer ? relative(rootDir, issuer) : 'a module in the browser bundle';\n // `file` is what Rspack puts on its own \"ERROR in …\" line, so the path lands where a reader looks\n // for one instead of being the first words of the message.\n compilation.errors.push(\n Object.assign(new Error(`[rshono] ${where} imports '${request}', which the browser bundle cannot have.\\n${SERVER_ONLY[request]}`), {\n file: where,\n }),\n );\n }\n });\n },\n };\n}\n"]}
@@ -1,11 +1,10 @@
1
- import type { RSHonoConfig } from '../config.js';
1
+ import type { RshonoConfig } from '../config.js';
2
2
  import type { DeployPreset } from '../deploy/presets.js';
3
3
  interface BuildOptions {
4
4
  rootDir: string;
5
- config: RSHonoConfig;
5
+ config: RshonoConfig;
6
6
  /** The platform to build for, already resolved from the flag, the environment and the config. */
7
7
  preset: DeployPreset;
8
8
  }
9
9
  export declare function buildCommand(options: BuildOptions): Promise<void>;
10
10
  export {};
11
- //# sourceMappingURL=build.d.ts.map