@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
package/bin/rshono.mjs CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
- // Thin launcher for the compiled CLI. Everything it does has to happen *before* the CLI module
3
- // graph loads, which is why the import below is dynamic rather than a static one at the top.
2
+ // Thin launcher for the compiled CLI. Everything here has to happen before the CLI module graph loads, which is
3
+ // why the import at the bottom is dynamic.
4
4
  const args = process.argv.slice(2);
5
5
 
6
- // `build` and `start` are the production commands, so they default NODE_ENV accordingly the
7
- // value is read during config resolution and baked into the bundle, so it has to be set this early.
6
+ // The production commands default NODE_ENV, which is read during config resolution and baked into the bundle.
8
7
  if ((args[0] === 'build' || args[0] === 'start') && !process.env.NODE_ENV) {
9
8
  process.env.NODE_ENV = 'production';
10
9
  }
@@ -1,18 +1,103 @@
1
1
  'use strict';
2
- const DIRECTIVE_PROLOGUE = /^(?:\s|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)*(?:(['"])use [a-z -]+\1\s*;?)?/;
2
+
3
+ /** Whitespace and comments — what may sit before or between directives without ending the prologue. */
4
+ const TRIVIA = '(?:\\s|//[^\\n]*(?:\\n|$)|/\\*[\\s\\S]*?\\*/)*';
5
+ /** One directive: `'use strict'`, `"use client"`, `'use server'`. The backreference keeps the quotes matched. */
6
+ const DIRECTIVE = '([\'"])use [a-z -]+\\1\\s*;?';
7
+
8
+ /**
9
+ * The whole directive prologue, so the prelude below is inserted *after* every directive rather than after the
10
+ * first one.
11
+ *
12
+ * The repetition is load-bearing. A module with two directives — `"use strict"` beside `'use client'`, which is
13
+ * ordinary output from a published component library, in either order — would otherwise take the prelude
14
+ * *between* them, and a directive preceded by a statement is just an expression: whichever came second would
15
+ * silently stop being a directive. That was survivable while this loader only saw the app's own hand-written
16
+ * source, where `'use client'` comes first and alone; it stopped being survivable when the rule widened to
17
+ * cover `node_modules` (see the SSR-layer rule in `rspack-config.ts`).
18
+ *
19
+ * Each repetition has to consume a whole directive, so the nested quantifiers cannot loop on an empty match.
20
+ */
21
+ const DIRECTIVE_PROLOGUE = new RegExp(`^${TRIVIA}(?:${DIRECTIVE}${TRIVIA})*`);
22
+
23
+ /**
24
+ * The gate: a standalone `process` identifier anywhere in the module, not the literal text `process.env`.
25
+ *
26
+ * The prelude replaces the whole `process` *binding*, so every way of reaching the env through that binding is
27
+ * covered once it is emitted — but a gate on `process.env` only ever saw one of them:
28
+ *
29
+ * | source shape | `process.env` | this |
30
+ * | --------------------------------------- | ------------- | ---- |
31
+ * | `process.env.DATABASE_URL` | yes | yes |
32
+ * | `const { DATABASE_URL } = process.env` | yes | yes |
33
+ * | `process?.env.DATABASE_URL` | **no** | yes |
34
+ * | `process['env'].DATABASE_URL` | **no** | yes |
35
+ * | `const { env } = process` | **no** | yes |
36
+ * | `const p = process; p.env.DATABASE_URL` | **no** | yes |
37
+ *
38
+ * `process?.env` is not a contrived shape — it is how env access is written in code meant to run in a browser
39
+ * and on a server, which is exactly what a `'use client'` component is. Every "no" above rendered a secret into
40
+ * the SSR'd HTML while the browser bundle saw the `PUBLIC_`-only view.
41
+ *
42
+ * `\b` on both sides, so `preprocess`, `processEnv` and `child_process` do not drag the prelude in. The cost of
43
+ * being wrong either way is only bytes: a module that mentions `process` in a comment gets a prelude it does
44
+ * not need.
45
+ *
46
+ * The one shape this cannot cover is a module that declares its own module-scope `process` binding — the
47
+ * prelude would be a redeclaration, and the build fails on it rather than shipping. Naming a module-scope
48
+ * binding after a well-known global is what has to give.
49
+ */
50
+ const MENTIONS_PROCESS = /\bprocess\b/;
51
+
52
+ /**
53
+ * A read of `process` through the global object rather than through the binding — `globalThis.process.env`,
54
+ * `globalThis?.process`, `global['process']`, and the `self` spelling Workers use.
55
+ *
56
+ * A prelude declares a module-scoped binding, so it cannot reach any of these: `globalThis.process` is the real
57
+ * `process` however the module names it. `typeof globalThis.process !== 'undefined' && …` is a real isomorphic
58
+ * idiom, so this is worth saying out loud rather than leaving to be discovered in a rendered page — but only
59
+ * for the app's own source, since a library feature-detecting `globalThis.process?.env?.NODE_ENV` is doing
60
+ * nothing wrong and has no app secret to read.
61
+ */
62
+ const GLOBAL_PROCESS = /\b(?:globalThis|global|self)\s*(?:\?\.|\.)?\s*(?:process\b|\[\s*(['"`])process\1\s*\])/;
63
+
64
+ /**
65
+ * A free `process.env.NODE_ENV` read — the one expression the prelude has to hand back to the optimiser —
66
+ * and the three places that same text can sit without being one.
67
+ *
68
+ * The last alternative is the target. The lookbehind rejects a member read off something else
69
+ * (`this.process.env.NODE_ENV`, `foo.process…`) and a longer identifier (`myprocess`); the trailing `\b`
70
+ * rejects `NODE_ENVIRONMENT`. That is the shape DefinePlugin substitutes, minus the ones it also declines —
71
+ * an optional chain and a computed key are left alone here exactly as they are left alone there.
72
+ *
73
+ * The alternatives ahead of it are what keeps this a substitution of *code*. DefinePlugin works on the parsed
74
+ * module and so cannot rewrite a string; this loader is text, and without them
75
+ * `throw new Error('set process.env.NODE_ENV')` would come out as `throw new Error('set "production"')`.
76
+ * Comments come first so an apostrophe in one (`// don't`) cannot open a string that swallows the code after
77
+ * it, then strings, then templates.
78
+ *
79
+ * It is a scanner, not a parser, and two things are outside it: a regex literal spelled with unescaped dots
80
+ * (`/process.env.NODE_ENV/`), and an interpolation inside a template, which is consumed with the template
81
+ * around it. The first is the residual risk and a strange thing to write; the second only declines a
82
+ * substitution that would have been valid, which costs bytes and changes nothing. Any position it gets wrong
83
+ * leaves the text exactly as a bare `.replace()` would have, so this can only improve on one.
84
+ */
85
+ const NODE_ENV_SCAN = /\/\/[^\n]*|\/\*[\s\S]*?\*\/|(['"])(?:\\[\s\S]|(?!\1)[^\\\n])*\1|`(?:\\[\s\S]|[^\\`])*`|(?<![.$\w])process\.env\.NODE_ENV\b/g;
3
86
 
4
87
  /**
5
88
  * Shadows `process.env` with the PUBLIC_-only view inside SSR-layer modules, so a secret read from a
6
- * `'use client'` component renders empty on the server instead of leaking into the HTML stream (and
7
- * SSR output keeps agreeing with hydration, which sees the same view via DefinePlugin).
89
+ * `'use client'` component renders empty on the server rather than leaking into the HTML stream and SSR
90
+ * keeps agreeing with hydration, which sees the same view via DefinePlugin.
8
91
  *
9
- * This is a security control, so it fails the build rather than passing source through when it can't
10
- * confirm which layer a module is in — `_module` is a private Rspack field, and silently no-op'ing
11
- * if a future version renames it would drop the guarantee with nothing to notice it.
92
+ * A security control, so it fails the build rather than passing source through when it cannot confirm a
93
+ * module's layer: `_module` is a private Rspack field, and a silent no-op if it were renamed would drop the
94
+ * guarantee with nothing to notice.
12
95
  */
13
96
  module.exports = function envShadowLoader(source) {
14
- if (!source.includes('process.env')) return source;
15
- const { prelude, layer } = this.getOptions();
97
+ // Two steps rather than the regex alone: this loader sees every module in the bundle, and most of them do
98
+ // not contain the substring at all, which `includes` settles far more cheaply than a scan for word breaks.
99
+ if (!source.includes('process') || !MENTIONS_PROCESS.test(source)) return source;
100
+ const { prelude, layer, appSrcPrefix, nodeEnv } = this.getOptions();
16
101
  if (!this._module) {
17
102
  throw new Error(
18
103
  "[rshono] env-shadow-loader could not read the module's layer (Rspack's `this._module` is unavailable). " +
@@ -21,6 +106,36 @@ module.exports = function envShadowLoader(source) {
21
106
  );
22
107
  }
23
108
  if (this._module.layer !== layer) return source;
109
+
110
+ if (appSrcPrefix && this.resourcePath?.startsWith(appSrcPrefix) && GLOBAL_PROCESS.test(source)) {
111
+ // Rspack prints the module path ahead of this, so the message is only the part it does not know.
112
+ this.emitWarning(
113
+ new Error(
114
+ '[rshono] This module reads `process` through the global object. It is rendered on the server as part ' +
115
+ 'of a client component, where `process.env` is shadowed with the `PUBLIC_`-only view — and that ' +
116
+ 'shadow is a binding, so it cannot cover `globalThis.process`. A variable read that way is the real ' +
117
+ 'one, and it ends up in the HTML. Read `process.env` directly instead.',
118
+ ),
119
+ );
120
+ }
121
+
122
+ // Hand `process.env.NODE_ENV` back to the optimiser before the prelude takes it away. DefinePlugin — the
123
+ // pass that erases `if (process.env.NODE_ENV === 'production')` and everything on the dead side of it —
124
+ // respects lexical scope, and the prelude below declares a module-scope `process`. So without this, every
125
+ // React entry wrapper in this layer stays a runtime branch and *both* halves are bundled: six development
126
+ // builds of react / react-dom / react-server-dom-rspack, about half of `dist/server/main.mjs`.
127
+ //
128
+ // Substituting is not a behaviour change. `NODE_ENV` is the one key of the prelude's own `env` literal that
129
+ // is not read from the host environment — it is this build's mode — so the value written here is the value
130
+ // the shadow would have returned anyway, and the shadow's security property is untouched.
131
+ //
132
+ // Textual, like the prelude insertion beside it, but not blindly so: {@link NODE_ENV_SCAN} walks the
133
+ // comments, strings and templates as well, and hands back everything that is not code untouched.
134
+ if (nodeEnv) {
135
+ const literal = JSON.stringify(nodeEnv);
136
+ source = source.replace(NODE_ENV_SCAN, (match) => (match.startsWith('process') ? literal : match));
137
+ }
138
+
24
139
  const prologue = source.match(DIRECTIVE_PROLOGUE)[0];
25
140
  return prologue + prelude + source.slice(prologue.length);
26
141
  };
@@ -1,5 +1,32 @@
1
1
  'use strict';
2
- const OPENS_WITH_DIRECTIVE = /^(?:\s|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)*(['"])use (?:client|server|server-entry)\1\s*(?:;|\n|$)/;
2
+
3
+ /** Whitespace and comments — what may sit before or between directives without ending the prologue. */
4
+ const TRIVIA = '(?:\\s|//[^\\n]*(?:\\n|$)|/\\*[\\s\\S]*?\\*/)*';
5
+ /**
6
+ * A directive that is *not* one of the three the RSC compilers act on. `'use strict'` above all, which is
7
+ * what a transpiler and many a published component library put at the top of a module; `'use cache'` and
8
+ * whatever else React adds later fall here too, which is the point of writing this as "not those three"
9
+ * rather than as a list.
10
+ */
11
+ const OTHER_DIRECTIVE = '([\'"])use (?!(?:client|server|server-entry)\\1)[a-z -]+\\1\\s*;?';
12
+ /** One of the three. `\\2`, because {@link OTHER_DIRECTIVE} ahead of it owns group 1. */
13
+ const RSC_DIRECTIVE = '([\'"])use (?:client|server|server-entry)\\2\\s*;?';
14
+
15
+ /**
16
+ * Whether the module already declares one of the three RSC directives — anywhere in its directive prologue,
17
+ * which is the fix: this used to match only a directive that came *first*.
18
+ *
19
+ * A page opening `'use strict';` ahead of its `'use client';` therefore had `'use server-entry';` prepended,
20
+ * and the injected directive is the one the compiler then acts on. Measured against a real build: a page
21
+ * whose first line is `'use client'` fails with `assertPageModule`'s message, and the same page with
22
+ * `'use strict'` above it exits 0 and ships — so the one directive the framework relies on to refuse a client
23
+ * page was silently neutered, and the page failed inside React at request time instead.
24
+ *
25
+ * Structural rather than a substring test, so a comment that *mentions* `'use client'` — plausible at the top
26
+ * of a page that used to be one — is consumed as trivia rather than read as a directive. And the same shape
27
+ * as `env-shadow-loader.cjs`'s prologue match, which was widened for this exact class of module.
28
+ */
29
+ const OPENS_WITH_DIRECTIVE = new RegExp(`^${TRIVIA}(?:${OTHER_DIRECTIVE}${TRIVIA})*${RSC_DIRECTIVE}`);
3
30
 
4
31
  module.exports = function pageEntryLoader(source) {
5
32
  if (OPENS_WITH_DIRECTIVE.test(source)) return source;
@@ -1,2 +1 @@
1
1
  export declare function scanPageFiles(routesFile: string, srcDir: string, into: Set<string>): void;
2
- //# sourceMappingURL=page-files.d.ts.map
@@ -1,10 +1,14 @@
1
+ /**
2
+ * Finds the page modules named by inline `component: () => import('…')` thunks in `routes.ts`, so the server
3
+ * compiler can put `page-entry-loader.cjs` in front of exactly those files. A page wired up any other way
4
+ * declares its `'use server-entry'` directive itself.
5
+ */
1
6
  import { readFileSync, statSync } from 'node:fs';
2
7
  import { dirname, join, resolve } from 'node:path';
3
8
  const COMPONENT_THUNK = /component:\s*(?:async\s*)?\(\s*\)\s*=>\s*import\(\s*(['"])([^'"]+)\1\s*\)/g;
4
9
  const EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];
5
- // The index candidates go through `join` rather than `base + '/index…'`: these paths end up in a Set
6
- // that rspack compares against its own resource paths, and a hardcoded `/` resolves fine on Windows
7
- // while storing a path no rspack resource will ever equal.
10
+ // `join` rather than `base + '/index…'`: these are compared against Rspack's own resource paths, which on
11
+ // Windows carry backslashes.
8
12
  function resolveCandidates(base) {
9
13
  return [...EXTENSIONS.map((ext) => base + ext), ...EXTENSIONS.map((ext) => join(base, `index${ext}`)), base];
10
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"page-files.js","sourceRoot":"","sources":["../../src/builder/page-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,eAAe,GAAG,4EAA4E,CAAC;AAErG,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAElD,qGAAqG;AACrG,oGAAoG;AACpG,2DAA2D;AAC3D,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAC/G,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,MAAc,EAAE,IAAiB;IACjF,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAwB,CAAC;QAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;aAC/D,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,KAAK,MAAM,SAAS,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACpB,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import { readFileSync, statSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nconst COMPONENT_THUNK = /component:\\s*(?:async\\s*)?\\(\\s*\\)\\s*=>\\s*import\\(\\s*(['\"])([^'\"]+)\\1\\s*\\)/g;\n\nconst EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];\n\n// The index candidates go through `join` rather than `base + '/index…'`: these paths end up in a Set\n// that rspack compares against its own resource paths, and a hardcoded `/` resolves fine on Windows\n// while storing a path no rspack resource will ever equal.\nfunction resolveCandidates(base: string): string[] {\n return [...EXTENSIONS.map((ext) => base + ext), ...EXTENSIONS.map((ext) => join(base, `index${ext}`)), base];\n}\n\nfunction isFile(path: string): boolean {\n try {\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n\nexport function scanPageFiles(routesFile: string, srcDir: string, into: Set<string>): void {\n into.clear();\n let source: string;\n try {\n source = readFileSync(routesFile, 'utf8');\n } catch {\n return;\n }\n for (const match of source.matchAll(COMPONENT_THUNK)) {\n const spec = match[2];\n let base: string | undefined;\n if (spec.startsWith('.')) base = resolve(dirname(routesFile), spec);\n else if (spec.startsWith('@/')) base = resolve(srcDir, spec.slice(2));\n if (!base) continue;\n for (const candidate of resolveCandidates(base)) {\n if (isFile(candidate)) {\n into.add(candidate);\n break;\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"page-files.js","sourceRoot":"","sources":["../../src/builder/page-files.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,eAAe,GAAG,4EAA4E,CAAC;AAErG,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAElD,0GAA0G;AAC1G,6BAA6B;AAC7B,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAC/G,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,MAAc,EAAE,IAAiB;IACjF,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAwB,CAAC;QAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;aAC/D,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,KAAK,MAAM,SAAS,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACpB,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["/**\n * Finds the page modules named by inline `component: () => import('…')` thunks in `routes.ts`, so the server\n * compiler can put `page-entry-loader.cjs` in front of exactly those files. A page wired up any other way\n * declares its `'use server-entry'` directive itself.\n */\nimport { readFileSync, statSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nconst COMPONENT_THUNK = /component:\\s*(?:async\\s*)?\\(\\s*\\)\\s*=>\\s*import\\(\\s*(['\"])([^'\"]+)\\1\\s*\\)/g;\n\nconst EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];\n\n// `join` rather than `base + '/index…'`: these are compared against Rspack's own resource paths, which on\n// Windows carry backslashes.\nfunction resolveCandidates(base: string): string[] {\n return [...EXTENSIONS.map((ext) => base + ext), ...EXTENSIONS.map((ext) => join(base, `index${ext}`)), base];\n}\n\nfunction isFile(path: string): boolean {\n try {\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n\nexport function scanPageFiles(routesFile: string, srcDir: string, into: Set<string>): void {\n into.clear();\n let source: string;\n try {\n source = readFileSync(routesFile, 'utf8');\n } catch {\n return;\n }\n for (const match of source.matchAll(COMPONENT_THUNK)) {\n const spec = match[2];\n let base: string | undefined;\n if (spec.startsWith('.')) base = resolve(dirname(routesFile), spec);\n else if (spec.startsWith('@/')) base = resolve(srcDir, spec.slice(2));\n if (!base) continue;\n for (const candidate of resolveCandidates(base)) {\n if (isFile(candidate)) {\n into.add(candidate);\n break;\n }\n }\n }\n}\n"]}
@@ -1,2 +1,8 @@
1
+ /**
2
+ * The `process.env` view compiled into the browser bundle: `NODE_ENV` plus the `PUBLIC_`-prefixed variables.
3
+ * A stray read of anything else in client code becomes `undefined` rather than shipping the value.
4
+ *
5
+ * Also what `env-shadow-loader.cjs` shadows SSR-layer modules with, so the server renders what the browser
6
+ * hydrates.
7
+ */
1
8
  export declare function publicEnv(isDev: boolean): Record<string, string>;
2
- //# sourceMappingURL=public-env.d.ts.map
@@ -1,3 +1,10 @@
1
+ /**
2
+ * The `process.env` view compiled into the browser bundle: `NODE_ENV` plus the `PUBLIC_`-prefixed variables.
3
+ * A stray read of anything else in client code becomes `undefined` rather than shipping the value.
4
+ *
5
+ * Also what `env-shadow-loader.cjs` shadows SSR-layer modules with, so the server renders what the browser
6
+ * hydrates.
7
+ */
1
8
  export function publicEnv(isDev) {
2
9
  const entries = Object.entries(process.env).filter((entry) => entry[0].startsWith('PUBLIC_') && entry[1] !== undefined);
3
10
  return { NODE_ENV: isDev ? 'development' : 'production', ...Object.fromEntries(entries) };
@@ -1 +1 @@
1
- {"version":3,"file":"public-env.js","sourceRoot":"","sources":["../../src/builder/public-env.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAA6B,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACnJ,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;AAC5F,CAAC","sourcesContent":["export function publicEnv(isDev: boolean): Record<string, string> {\n const entries = Object.entries(process.env).filter((entry): entry is [string, string] => entry[0].startsWith('PUBLIC_') && entry[1] !== undefined);\n return { NODE_ENV: isDev ? 'development' : 'production', ...Object.fromEntries(entries) };\n}\n"]}
1
+ {"version":3,"file":"public-env.js","sourceRoot":"","sources":["../../src/builder/public-env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAA6B,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACnJ,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;AAC5F,CAAC","sourcesContent":["/**\n * The `process.env` view compiled into the browser bundle: `NODE_ENV` plus the `PUBLIC_`-prefixed variables.\n * A stray read of anything else in client code becomes `undefined` rather than shipping the value.\n *\n * Also what `env-shadow-loader.cjs` shadows SSR-layer modules with, so the server renders what the browser\n * hydrates.\n */\nexport function publicEnv(isDev: boolean): Record<string, string> {\n const entries = Object.entries(process.env).filter((entry): entry is [string, string] => entry[0].startsWith('PUBLIC_') && entry[1] !== undefined);\n return { NODE_ENV: isDev ? 'development' : 'production', ...Object.fromEntries(entries) };\n}\n"]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Fails the build when `react` and `react-dom` resolve to different versions, and warns when they are not the
3
+ * version this release was tested with.
4
+ *
5
+ * The mismatch is an error because it never works. A different minor is only a warning: upstream declares
6
+ * `^19.1.0` and may well be right, and refusing to build would make rshono the reason an app cannot take a
7
+ * React release — a worse failure than the one it would prevent.
8
+ *
9
+ * @param rootDir - The project root, so resolution follows the *app's* `node_modules` rather than the
10
+ * framework's.
11
+ */
12
+ export declare function checkReactVersions(rootDir: string): void;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The one dependency check the framework makes on an app: that `react` and `react-dom` are the same build.
3
+ *
4
+ * React Server Components couple the two across bundles — `react-server-dom-rspack` reaches into
5
+ * `ReactSharedInternals` and emits a flight payload the *matching* `react-dom` has to parse — so a split
6
+ * resolution is not version skew to be tolerated. It is a crash inside minified React at render time, with
7
+ * nothing in the message pointing at the cause.
8
+ *
9
+ * The monorepo prevents it with `overrides` in `pnpm-workspace.yaml` and `packages/create` pins both exactly
10
+ * for the same reason, because a generated app has no overrides to rely on. Neither helps an app that grew
11
+ * past its scaffold, which is why the check lives here — on the one path every `dev` and `build` takes.
12
+ */
13
+ import { createRequire } from 'node:module';
14
+ import { join } from 'node:path';
15
+ /**
16
+ * The React this release was tested against, read off the framework's own manifest rather than restated as a
17
+ * constant — the pin and the check cannot drift apart that way. `undefined` if it cannot be read, which only
18
+ * turns the advisory warning off.
19
+ */
20
+ function testedReactVersion() {
21
+ try {
22
+ const manifest = createRequire(import.meta.url)('../../package.json');
23
+ return manifest.devDependencies?.react;
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ /** The version of an installed package as the *app* resolves it, or `undefined` if it has none. */
30
+ function installedVersion(require, name) {
31
+ try {
32
+ return require(`${name}/package.json`).version;
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
38
+ /** `19.2.8` → `19.2`, for comparing minors without pulling in a semver parser. */
39
+ function minor(version) {
40
+ return version.split('.', 2).join('.');
41
+ }
42
+ /**
43
+ * Fails the build when `react` and `react-dom` resolve to different versions, and warns when they are not the
44
+ * version this release was tested with.
45
+ *
46
+ * The mismatch is an error because it never works. A different minor is only a warning: upstream declares
47
+ * `^19.1.0` and may well be right, and refusing to build would make rshono the reason an app cannot take a
48
+ * React release — a worse failure than the one it would prevent.
49
+ *
50
+ * @param rootDir - The project root, so resolution follows the *app's* `node_modules` rather than the
51
+ * framework's.
52
+ */
53
+ export function checkReactVersions(rootDir) {
54
+ // Resolved from the project's manifest, so a pnpm layout answers with what the app actually gets.
55
+ const require = createRequire(join(rootDir, 'package.json'));
56
+ const react = installedVersion(require, 'react');
57
+ const reactDom = installedVersion(require, 'react-dom');
58
+ // Missing altogether is the resolver's to report, against the import that needed it and with the path.
59
+ if (!react || !reactDom)
60
+ return;
61
+ if (react !== reactDom) {
62
+ throw new Error(`[rshono] react ${react} and react-dom ${reactDom} resolve to different versions.\n` +
63
+ ' React Server Components couple them: the RSC runtime emits a payload the matching react-dom parses, ' +
64
+ 'so a split resolution fails inside React while rendering rather than here.\n' +
65
+ ' Install both at the same version. If a transitive dependency is what split them, pin them — ' +
66
+ '`overrides` in npm, `pnpm.overrides` in pnpm, `resolutions` in yarn.');
67
+ }
68
+ const tested = testedReactVersion();
69
+ if (tested && minor(tested) !== minor(react)) {
70
+ console.warn(` ⚠ react ${react} is not the ${minor(tested)}.x this rshono was tested against. The RSC runtime is pinned\n` +
71
+ ' to an exact version and reaches into React internals, so a different minor can fail at render time.');
72
+ }
73
+ }
74
+ //# sourceMappingURL=react-versions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-versions.js","sourceRoot":"","sources":["../../src/builder/react-versions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;GAIG;AACH,SAAS,kBAAkB;IACzB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAEnE,CAAC;QACF,OAAO,QAAQ,CAAC,eAAe,EAAE,KAAK,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,mGAAmG;AACnG,SAAS,gBAAgB,CAAC,OAAuB,EAAE,IAAY;IAC7D,IAAI,CAAC;QACH,OAAQ,OAAO,CAAC,GAAG,IAAI,eAAe,CAA0B,CAAC,OAAO,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,kFAAkF;AAClF,SAAS,KAAK,CAAC,OAAe;IAC5B,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,kGAAkG;IAClG,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACxD,uGAAuG;IACvG,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ;QAAE,OAAO;IAEhC,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,kBAAkB,KAAK,kBAAkB,QAAQ,mCAAmC;YAClF,wGAAwG;YACxG,8EAA8E;YAC9E,gGAAgG;YAChG,sEAAsE,CACzE,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAC;IACpC,IAAI,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7C,OAAO,CAAC,IAAI,CACV,aAAa,KAAK,eAAe,KAAK,CAAC,MAAM,CAAC,gEAAgE;YAC5G,yGAAyG,CAC5G,CAAC;IACJ,CAAC;AACH,CAAC","sourcesContent":["/**\n * The one dependency check the framework makes on an app: that `react` and `react-dom` are the same build.\n *\n * React Server Components couple the two across bundles — `react-server-dom-rspack` reaches into\n * `ReactSharedInternals` and emits a flight payload the *matching* `react-dom` has to parse — so a split\n * resolution is not version skew to be tolerated. It is a crash inside minified React at render time, with\n * nothing in the message pointing at the cause.\n *\n * The monorepo prevents it with `overrides` in `pnpm-workspace.yaml` and `packages/create` pins both exactly\n * for the same reason, because a generated app has no overrides to rely on. Neither helps an app that grew\n * past its scaffold, which is why the check lives here — on the one path every `dev` and `build` takes.\n */\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\n\n/**\n * The React this release was tested against, read off the framework's own manifest rather than restated as a\n * constant — the pin and the check cannot drift apart that way. `undefined` if it cannot be read, which only\n * turns the advisory warning off.\n */\nfunction testedReactVersion(): string | undefined {\n try {\n const manifest = createRequire(import.meta.url)('../../package.json') as {\n devDependencies?: Record<string, string>;\n };\n return manifest.devDependencies?.react;\n } catch {\n return undefined;\n }\n}\n\n/** The version of an installed package as the *app* resolves it, or `undefined` if it has none. */\nfunction installedVersion(require: NodeJS.Require, name: string): string | undefined {\n try {\n return (require(`${name}/package.json`) as { version?: string }).version;\n } catch {\n return undefined;\n }\n}\n\n/** `19.2.8` → `19.2`, for comparing minors without pulling in a semver parser. */\nfunction minor(version: string): string {\n return version.split('.', 2).join('.');\n}\n\n/**\n * Fails the build when `react` and `react-dom` resolve to different versions, and warns when they are not the\n * version this release was tested with.\n *\n * The mismatch is an error because it never works. A different minor is only a warning: upstream declares\n * `^19.1.0` and may well be right, and refusing to build would make rshono the reason an app cannot take a\n * React release — a worse failure than the one it would prevent.\n *\n * @param rootDir - The project root, so resolution follows the *app's* `node_modules` rather than the\n * framework's.\n */\nexport function checkReactVersions(rootDir: string): void {\n // Resolved from the project's manifest, so a pnpm layout answers with what the app actually gets.\n const require = createRequire(join(rootDir, 'package.json'));\n const react = installedVersion(require, 'react');\n const reactDom = installedVersion(require, 'react-dom');\n // Missing altogether is the resolver's to report, against the import that needed it and with the path.\n if (!react || !reactDom) return;\n\n if (react !== reactDom) {\n throw new Error(\n `[rshono] react ${react} and react-dom ${reactDom} resolve to different versions.\\n` +\n ' React Server Components couple them: the RSC runtime emits a payload the matching react-dom parses, ' +\n 'so a split resolution fails inside React while rendering rather than here.\\n' +\n ' Install both at the same version. If a transitive dependency is what split them, pin them — ' +\n '`overrides` in npm, `pnpm.overrides` in pnpm, `resolutions` in yarn.',\n );\n }\n\n const tested = testedReactVersion();\n if (tested && minor(tested) !== minor(react)) {\n console.warn(\n ` ⚠ react ${react} is not the ${minor(tested)}.x this rshono was tested against. The RSC runtime is pinned\\n` +\n ' to an exact version and reaches into React internals, so a different minor can fail at render time.',\n );\n }\n}\n"]}
@@ -1,14 +1,23 @@
1
1
  import { type RspackOptions } from '@rspack/core';
2
- import type { RSHonoConfig } from '../config.js';
2
+ import type { RshonoConfig } from '../config.js';
3
3
  import type { DeployPreset } from '../deploy/presets.js';
4
+ /**
5
+ * The directory a command writes its bundles to, relative to the project root — one per command, never
6
+ * shared: a `rshono build` alongside a running `rshono dev` would `clean` away the route chunks that
7
+ * server imports lazily, and Rspack's watch-mode emit never re-emits a file it believes it wrote.
8
+ *
9
+ * Both names are a single level under the root, which `deploy/filesystem.ts` relies on to derive the
10
+ * project root from where the bundle ended up.
11
+ */
12
+ export declare const BUILD_OUT_DIR = "dist";
13
+ export declare const DEV_OUT_DIR = ".rshono";
4
14
  export interface RspackConfigOptions {
5
15
  rootDir: string;
6
16
  isDev: boolean;
7
- /** The project's resolved config — supplies the {@link RSHonoConfig.rspack} hook and the runtime settings baked into the server bundle. */
8
- config: RSHonoConfig;
17
+ /** The project's resolved config — supplies the {@link RshonoConfig.rspack} hook and the runtime settings baked into the server bundle. */
18
+ config: RshonoConfig;
9
19
  /** The platform being built for: decides the `@rshono/deploy` runtime and any server-compiler overrides. */
10
20
  preset: DeployPreset;
11
21
  onServerComponentChanges?: () => void;
12
22
  }
13
23
  export declare function createConfigs(options: RspackConfigOptions): [RspackOptions, RspackOptions];
14
- //# sourceMappingURL=rspack-config.d.ts.map