@tanstack/redact 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/dist/core/index.d.ts +2 -0
  2. package/dist/core/index.js +4 -0
  3. package/dist/core/index.js.map +7 -0
  4. package/dist/core/internal.d.ts +76 -0
  5. package/dist/core/internal.js +59 -0
  6. package/dist/core/internal.js.map +7 -0
  7. package/dist/core/types.d.ts +21 -0
  8. package/dist/core/types.js +10 -0
  9. package/dist/core/types.js.map +7 -0
  10. package/dist/dom/_all.d.ts +17 -0
  11. package/dist/dom/_all.js +51 -0
  12. package/dist/dom/_all.js.map +7 -0
  13. package/dist/dom/client.d.ts +3 -0
  14. package/dist/dom/client.js +8 -0
  15. package/dist/dom/client.js.map +7 -0
  16. package/dist/dom/dispatcher.d.ts +20 -0
  17. package/dist/dom/dispatcher.js +267 -0
  18. package/dist/dom/dispatcher.js.map +7 -0
  19. package/dist/dom/dom.d.ts +10 -0
  20. package/dist/dom/dom.js +262 -0
  21. package/dist/dom/dom.js.map +7 -0
  22. package/dist/dom/event-replay.d.ts +13 -0
  23. package/dist/dom/event-replay.js +46 -0
  24. package/dist/dom/event-replay.js.map +7 -0
  25. package/dist/dom/features/class/full.d.ts +1 -0
  26. package/dist/dom/features/class/full.js +91 -0
  27. package/dist/dom/features/class/full.js.map +7 -0
  28. package/dist/dom/features/class/index.d.ts +1 -0
  29. package/dist/dom/features/class/index.js +3 -0
  30. package/dist/dom/features/class/index.js.map +7 -0
  31. package/dist/dom/features/class/stub.d.ts +1 -0
  32. package/dist/dom/features/class/stub.js +50 -0
  33. package/dist/dom/features/class/stub.js.map +7 -0
  34. package/dist/dom/features/context/full.d.ts +1 -0
  35. package/dist/dom/features/context/full.js +49 -0
  36. package/dist/dom/features/context/full.js.map +7 -0
  37. package/dist/dom/features/context/index.d.ts +1 -0
  38. package/dist/dom/features/context/index.js +3 -0
  39. package/dist/dom/features/context/index.js.map +7 -0
  40. package/dist/dom/features/context/stub.d.ts +1 -0
  41. package/dist/dom/features/context/stub.js +22 -0
  42. package/dist/dom/features/context/stub.js.map +7 -0
  43. package/dist/dom/features/forward-ref/full.d.ts +1 -0
  44. package/dist/dom/features/forward-ref/full.js +51 -0
  45. package/dist/dom/features/forward-ref/full.js.map +7 -0
  46. package/dist/dom/features/forward-ref/index.d.ts +1 -0
  47. package/dist/dom/features/forward-ref/index.js +3 -0
  48. package/dist/dom/features/forward-ref/index.js.map +7 -0
  49. package/dist/dom/features/forward-ref/stub.d.ts +1 -0
  50. package/dist/dom/features/forward-ref/stub.js +22 -0
  51. package/dist/dom/features/forward-ref/stub.js.map +7 -0
  52. package/dist/dom/features/hydration/full.d.ts +61 -0
  53. package/dist/dom/features/hydration/full.js +264 -0
  54. package/dist/dom/features/hydration/full.js.map +7 -0
  55. package/dist/dom/features/hydration/index.d.ts +1 -0
  56. package/dist/dom/features/hydration/index.js +3 -0
  57. package/dist/dom/features/hydration/index.js.map +7 -0
  58. package/dist/dom/features/hydration/stub.d.ts +28 -0
  59. package/dist/dom/features/hydration/stub.js +66 -0
  60. package/dist/dom/features/hydration/stub.js.map +7 -0
  61. package/dist/dom/features/index.d.ts +7 -0
  62. package/dist/dom/features/index.js +9 -0
  63. package/dist/dom/features/index.js.map +7 -0
  64. package/dist/dom/features/lazy/full.d.ts +1 -0
  65. package/dist/dom/features/lazy/full.js +78 -0
  66. package/dist/dom/features/lazy/full.js.map +7 -0
  67. package/dist/dom/features/lazy/index.d.ts +1 -0
  68. package/dist/dom/features/lazy/index.js +3 -0
  69. package/dist/dom/features/lazy/index.js.map +7 -0
  70. package/dist/dom/features/lazy/stub.d.ts +1 -0
  71. package/dist/dom/features/lazy/stub.js +36 -0
  72. package/dist/dom/features/lazy/stub.js.map +7 -0
  73. package/dist/dom/features/memo/full.d.ts +1 -0
  74. package/dist/dom/features/memo/full.js +59 -0
  75. package/dist/dom/features/memo/full.js.map +7 -0
  76. package/dist/dom/features/memo/index.d.ts +1 -0
  77. package/dist/dom/features/memo/index.js +3 -0
  78. package/dist/dom/features/memo/index.js.map +7 -0
  79. package/dist/dom/features/memo/stub.d.ts +1 -0
  80. package/dist/dom/features/memo/stub.js +36 -0
  81. package/dist/dom/features/memo/stub.js.map +7 -0
  82. package/dist/dom/features/portal/full.d.ts +1 -0
  83. package/dist/dom/features/portal/full.js +19 -0
  84. package/dist/dom/features/portal/full.js.map +7 -0
  85. package/dist/dom/features/portal/index.d.ts +1 -0
  86. package/dist/dom/features/portal/index.js +3 -0
  87. package/dist/dom/features/portal/index.js.map +7 -0
  88. package/dist/dom/features/portal/stub.d.ts +1 -0
  89. package/dist/dom/features/portal/stub.js +7 -0
  90. package/dist/dom/features/portal/stub.js.map +7 -0
  91. package/dist/dom/features/suspense/full.d.ts +1 -0
  92. package/dist/dom/features/suspense/full.js +146 -0
  93. package/dist/dom/features/suspense/full.js.map +7 -0
  94. package/dist/dom/features/suspense/index.d.ts +1 -0
  95. package/dist/dom/features/suspense/index.js +3 -0
  96. package/dist/dom/features/suspense/index.js.map +7 -0
  97. package/dist/dom/features/suspense/stub.d.ts +1 -0
  98. package/dist/dom/features/suspense/stub.js +6 -0
  99. package/dist/dom/features/suspense/stub.js.map +7 -0
  100. package/dist/dom/index.d.ts +25 -0
  101. package/dist/dom/index.js +45 -0
  102. package/dist/dom/index.js.map +7 -0
  103. package/dist/dom/portal.d.ts +2 -0
  104. package/dist/dom/portal.js +15 -0
  105. package/dist/dom/portal.js.map +7 -0
  106. package/dist/dom/reconcile.d.ts +40 -0
  107. package/dist/dom/reconcile.js +795 -0
  108. package/dist/dom/reconcile.js.map +7 -0
  109. package/dist/dom/root.d.ts +15 -0
  110. package/dist/dom/root.js +85 -0
  111. package/dist/dom/root.js.map +7 -0
  112. package/dist/dom/test-utils.d.ts +1 -0
  113. package/dist/dom/test-utils.js +10 -0
  114. package/dist/dom/test-utils.js.map +7 -0
  115. package/dist/react/children.d.ts +8 -0
  116. package/dist/react/children.js +75 -0
  117. package/dist/react/children.js.map +7 -0
  118. package/dist/react/class.d.ts +31 -0
  119. package/dist/react/class.js +40 -0
  120. package/dist/react/class.js.map +7 -0
  121. package/dist/react/context.d.ts +27 -0
  122. package/dist/react/context.js +31 -0
  123. package/dist/react/context.js.map +7 -0
  124. package/dist/react/element.d.ts +10 -0
  125. package/dist/react/element.js +64 -0
  126. package/dist/react/element.js.map +7 -0
  127. package/dist/react/hooks.d.ts +37 -0
  128. package/dist/react/hooks.js +146 -0
  129. package/dist/react/hooks.js.map +7 -0
  130. package/dist/react/index.d.ts +76 -0
  131. package/dist/react/index.js +187 -0
  132. package/dist/react/index.js.map +7 -0
  133. package/dist/react/jsx-runtime.d.ts +5 -0
  134. package/dist/react/jsx-runtime.js +21 -0
  135. package/dist/react/jsx-runtime.js.map +7 -0
  136. package/dist/react/memo.d.ts +29 -0
  137. package/dist/react/memo.js +47 -0
  138. package/dist/react/memo.js.map +7 -0
  139. package/dist/react/portal.d.ts +1 -0
  140. package/dist/react/portal.js +6 -0
  141. package/dist/react/portal.js.map +7 -0
  142. package/dist/react/shared-internals.d.ts +33 -0
  143. package/dist/react/shared-internals.js +29 -0
  144. package/dist/react/shared-internals.js.map +7 -0
  145. package/dist/react/suspense.d.ts +15 -0
  146. package/dist/react/suspense.js +16 -0
  147. package/dist/react/suspense.js.map +7 -0
  148. package/dist/scheduler/index.d.ts +44 -0
  149. package/dist/scheduler/index.js +109 -0
  150. package/dist/scheduler/index.js.map +7 -0
  151. package/dist/server/bootstrap-script.d.ts +22 -0
  152. package/dist/server/bootstrap-script.js +16 -0
  153. package/dist/server/bootstrap-script.js.map +7 -0
  154. package/dist/server/dispatcher.d.ts +37 -0
  155. package/dist/server/dispatcher.js +140 -0
  156. package/dist/server/dispatcher.js.map +7 -0
  157. package/dist/server/escape.d.ts +7 -0
  158. package/dist/server/escape.js +159 -0
  159. package/dist/server/escape.js.map +7 -0
  160. package/dist/server/index.d.ts +16 -0
  161. package/dist/server/index.js +26 -0
  162. package/dist/server/index.js.map +7 -0
  163. package/dist/server/renderToString.d.ts +7 -0
  164. package/dist/server/renderToString.js +29 -0
  165. package/dist/server/renderToString.js.map +7 -0
  166. package/dist/server/stream.d.ts +40 -0
  167. package/dist/server/stream.js +200 -0
  168. package/dist/server/stream.js.map +7 -0
  169. package/dist/server/walk.d.ts +33 -0
  170. package/dist/server/walk.js +298 -0
  171. package/dist/server/walk.js.map +7 -0
  172. package/dist/vite/index.d.ts +96 -0
  173. package/dist/vite/index.js +217 -0
  174. package/dist/vite/index.js.map +7 -0
  175. package/package.json +83 -0
  176. package/src/core/index.ts +2 -0
  177. package/src/core/internal.ts +107 -0
  178. package/src/core/types.ts +36 -0
  179. package/src/dom/_all.ts +54 -0
  180. package/src/dom/client.ts +4 -0
  181. package/src/dom/dispatcher.ts +318 -0
  182. package/src/dom/dom.ts +316 -0
  183. package/src/dom/event-replay.ts +55 -0
  184. package/src/dom/features/class/full.ts +104 -0
  185. package/src/dom/features/class/index.ts +1 -0
  186. package/src/dom/features/class/stub.ts +65 -0
  187. package/src/dom/features/context/full.ts +57 -0
  188. package/src/dom/features/context/index.ts +1 -0
  189. package/src/dom/features/context/stub.ts +32 -0
  190. package/src/dom/features/forward-ref/full.ts +54 -0
  191. package/src/dom/features/forward-ref/index.ts +1 -0
  192. package/src/dom/features/forward-ref/stub.ts +28 -0
  193. package/src/dom/features/hydration/full.ts +345 -0
  194. package/src/dom/features/hydration/index.ts +1 -0
  195. package/src/dom/features/hydration/stub.ts +72 -0
  196. package/src/dom/features/index.ts +11 -0
  197. package/src/dom/features/lazy/full.ts +98 -0
  198. package/src/dom/features/lazy/index.ts +1 -0
  199. package/src/dom/features/lazy/stub.ts +45 -0
  200. package/src/dom/features/memo/full.ts +74 -0
  201. package/src/dom/features/memo/index.ts +1 -0
  202. package/src/dom/features/memo/stub.ts +42 -0
  203. package/src/dom/features/portal/full.ts +22 -0
  204. package/src/dom/features/portal/index.ts +1 -0
  205. package/src/dom/features/portal/stub.ts +11 -0
  206. package/src/dom/features/suspense/full.ts +191 -0
  207. package/src/dom/features/suspense/index.ts +1 -0
  208. package/src/dom/features/suspense/stub.ts +12 -0
  209. package/src/dom/index.ts +33 -0
  210. package/src/dom/portal.ts +16 -0
  211. package/src/dom/reconcile.ts +1169 -0
  212. package/src/dom/root.ts +107 -0
  213. package/src/dom/test-utils.ts +6 -0
  214. package/src/react/children.ts +85 -0
  215. package/src/react/class.ts +56 -0
  216. package/src/react/context.ts +50 -0
  217. package/src/react/element.ts +80 -0
  218. package/src/react/hooks.ts +190 -0
  219. package/src/react/index.ts +132 -0
  220. package/src/react/jsx-runtime.ts +16 -0
  221. package/src/react/memo.ts +45 -0
  222. package/src/react/portal.ts +1 -0
  223. package/src/react/shared-internals.ts +79 -0
  224. package/src/react/suspense.ts +18 -0
  225. package/src/scheduler/index.ts +124 -0
  226. package/src/server/bootstrap-script.ts +52 -0
  227. package/src/server/dispatcher.ts +158 -0
  228. package/src/server/escape.ts +187 -0
  229. package/src/server/index.ts +26 -0
  230. package/src/server/renderToString.ts +33 -0
  231. package/src/server/stream.ts +302 -0
  232. package/src/server/walk.ts +448 -0
  233. package/src/vite/index.ts +401 -0
@@ -0,0 +1,401 @@
1
+ import { existsSync, readFileSync, realpathSync } from 'node:fs'
2
+ import { dirname, resolve as resolvePath } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ export type RedactPreset = 'nano' | 'full'
6
+
7
+ /**
8
+ * Opt-in feature set. Each flag toggles whether the feature's real
9
+ * implementation ships (`true`) or is swapped with a stub module that
10
+ * degrades gracefully (`false`). Missing keys fall back to the preset's
11
+ * default. Adding a feature to this interface propagates to consumer
12
+ * configs as autocompleted options.
13
+ */
14
+ export interface RedactFeatures {
15
+ /**
16
+ * `createPortal`. When `false`, portal elements render in place as a
17
+ * Fragment (the `container` prop is ignored). `renderPortal` and its
18
+ * deps are stripped from the bundle.
19
+ */
20
+ portal?: boolean
21
+ /**
22
+ * `createContext` / `useContext` / `<Provider>` / `<Consumer>`. When
23
+ * `false`, Providers render as Fragments (value never propagates),
24
+ * Consumers invoke their function-children with the context's default
25
+ * value, and `useContext` returns the default. Provider-walk logic and
26
+ * `renderProvider` are stripped.
27
+ */
28
+ context?: boolean
29
+ /**
30
+ * `<Suspense>` boundaries + streaming hydration. When `false`, Suspense
31
+ * elements render as Fragments (children mount inline, `fallback` is
32
+ * ignored). Thrown thenables still schedule a re-render on settle, so
33
+ * eventual consistency works — just no fallback UI during the pending
34
+ * window. Boundary-handler stack and hydration integration are stripped.
35
+ */
36
+ suspense?: boolean
37
+ /**
38
+ * `React.memo`. When `false`, memoized components still render but without
39
+ * the prop-equality gate — every parent rerender passes through.
40
+ * `shallowEqual` and the force-rerender bypass are stripped.
41
+ */
42
+ memo?: boolean
43
+ /**
44
+ * `React.forwardRef`. When `false`, forwardRef components still render but
45
+ * the ref prop isn't forwarded to the inner function. React 19+ treats
46
+ * refs as normal props on function components anyway, so most apps can
47
+ * drop this. The dispatcher save/restore machinery is stripped.
48
+ */
49
+ forwardRef?: boolean
50
+ /**
51
+ * `React.lazy`. When `false`, lazy elements still resolve if their payload
52
+ * is already available synchronously (e.g. pre-awaited RSC Flight); async
53
+ * resolution throws a clear error. The hydration-deferred-reveal path and
54
+ * Suspense coordination are stripped.
55
+ */
56
+ lazy?: boolean
57
+ /**
58
+ * Class components (`extends Component`). When `false`, class components
59
+ * still render but only honor the core contract: constructor + `render()`
60
+ * + `setState`. Dropped: `contextType`, `getDerivedStateFromProps`,
61
+ * `shouldComponentUpdate`, `componentDidMount`/`Update`/`WillUnmount`,
62
+ * `getDerivedStateFromError`/`componentDidCatch` (error boundaries).
63
+ */
64
+ classComponents?: boolean
65
+ /**
66
+ * SSR hydration (`hydrateRoot`). When `false`, `hydrateRoot` throws
67
+ * (use `createRoot` for SPAs). The HydrationCursor / DOM adoption /
68
+ * streaming-boundary coordination / event-replay / scroll-guard
69
+ * machinery is stripped — the biggest single chunk of reducible code.
70
+ */
71
+ hydration?: boolean
72
+ }
73
+
74
+ interface ResolvedFeatures {
75
+ portal: boolean
76
+ context: boolean
77
+ suspense: boolean
78
+ memo: boolean
79
+ forwardRef: boolean
80
+ lazy: boolean
81
+ classComponents: boolean
82
+ hydration: boolean
83
+ }
84
+
85
+ const PRESET_DEFAULTS: Record<RedactPreset, ResolvedFeatures> = {
86
+ // Opt-in: everything off. Turn individual features on via `features`.
87
+ nano: {
88
+ portal: false, context: false, suspense: false, memo: false,
89
+ forwardRef: false, lazy: false, classComponents: false, hydration: false,
90
+ },
91
+ // Opt-out: everything on (drop-in React parity). Turn features off via `features`.
92
+ full: {
93
+ portal: true, context: true, suspense: true, memo: true,
94
+ forwardRef: true, lazy: true, classComponents: true, hydration: true,
95
+ },
96
+ }
97
+
98
+ function resolveFeatures(
99
+ preset: RedactPreset,
100
+ overrides: RedactFeatures,
101
+ ): ResolvedFeatures {
102
+ const p = PRESET_DEFAULTS[preset]
103
+ return {
104
+ portal: overrides.portal ?? p.portal,
105
+ context: overrides.context ?? p.context,
106
+ suspense: overrides.suspense ?? p.suspense,
107
+ memo: overrides.memo ?? p.memo,
108
+ forwardRef: overrides.forwardRef ?? p.forwardRef,
109
+ lazy: overrides.lazy ?? p.lazy,
110
+ classComponents: overrides.classComponents ?? p.classComponents,
111
+ hydration: overrides.hydration ?? p.hydration,
112
+ }
113
+ }
114
+
115
+ export interface RedactOptions {
116
+ /** Skip aliasing specific specifiers, e.g. if a consumer wants real React somewhere. */
117
+ skip?: ReadonlyArray<string>
118
+ /**
119
+ * Override package resolution root. Defaults to the Vite config root. Useful
120
+ * for monorepos where the plugin lives in a different workspace than the
121
+ * consumer app.
122
+ */
123
+ resolveFrom?: string
124
+ /**
125
+ * Explicit package roots, bypassing node_modules lookup. Keys are package
126
+ * names (e.g. `@tanstack/redact`), values are absolute paths to the package
127
+ * directory. Handy for cross-workspace testing / bring-your-own-build setups.
128
+ */
129
+ packageRoots?: Record<string, string>
130
+ /**
131
+ * Starting point for feature selection. `'full'` (default) turns every
132
+ * feature on — drop-in React parity, opt-out individual features via
133
+ * `features`. `'nano'` turns everything off — opt in to what you need.
134
+ */
135
+ preset?: RedactPreset
136
+ /**
137
+ * Per-feature overrides merged on top of the preset's defaults. Enables
138
+ * fine-grained "preset minus X" or "preset plus Y" configurations.
139
+ */
140
+ features?: RedactFeatures
141
+ }
142
+
143
+ // Alias map. ORDER MATTERS — Vite's alias matcher uses first-match against
144
+ // prefix, so more-specific specifiers MUST come before less-specific ones.
145
+ // Without this, `react-dom/server` would prefix-match `react-dom` first and
146
+ // resolve to `@tanstack/redact/dom/server` (wrong) instead of
147
+ // `@tanstack/redact/server`.
148
+ //
149
+ // `use-sync-external-store` aliases are here because its CJS-only React 17
150
+ // compat shim does `var React = require('react')`. That survives Vite's
151
+ // pre-bundling intact and explodes in Cloudflare Workers (no `require`).
152
+ // Modern React has `useSyncExternalStore` built-in, and `@tanstack/redact`
153
+ // additionally exports `useSyncExternalStoreWithSelector` so this alias
154
+ // is safe everywhere.
155
+ const ALIASES: Record<string, string> = {
156
+ // ---- most-specific first ----
157
+ 'use-sync-external-store/shim/with-selector': '@tanstack/redact',
158
+ 'use-sync-external-store/shim/with-selector.js': '@tanstack/redact',
159
+ 'use-sync-external-store/with-selector': '@tanstack/redact',
160
+ 'use-sync-external-store/with-selector.js': '@tanstack/redact',
161
+ 'use-sync-external-store/shim': '@tanstack/redact',
162
+ 'use-sync-external-store': '@tanstack/redact',
163
+
164
+ // React drop-in shim targets. Subpaths first.
165
+ 'react/jsx-runtime': '@tanstack/redact/jsx-runtime',
166
+ 'react/jsx-dev-runtime': '@tanstack/redact/jsx-dev-runtime',
167
+ 'react-dom/client': '@tanstack/redact/dom-client',
168
+ 'react-dom/server': '@tanstack/redact/server',
169
+ 'react-dom/test-utils': '@tanstack/redact/dom-test-utils',
170
+ 'react-dom': '@tanstack/redact/dom',
171
+ react: '@tanstack/redact',
172
+ scheduler: '@tanstack/redact/scheduler',
173
+
174
+ // Self-aliases so Vite resolves `@tanstack/redact/*` imports to the same
175
+ // canonical file path no matter where they originate (worker bundle vs
176
+ // deps_ssr pre-bundle vs source). Without these, Cloudflare's
177
+ // `noExternal: true` worker config inlines one copy while Vite's
178
+ // optimizeDeps pre-bundles another, ending up with two separate
179
+ // ReactSharedInternals instances and a null dispatcher in user hooks.
180
+ // Subpaths first here too.
181
+ '@tanstack/redact/jsx-runtime': '@tanstack/redact/jsx-runtime',
182
+ '@tanstack/redact/jsx-dev-runtime': '@tanstack/redact/jsx-dev-runtime',
183
+ '@tanstack/redact/dom-client': '@tanstack/redact/dom-client',
184
+ '@tanstack/redact/dom-test-utils': '@tanstack/redact/dom-test-utils',
185
+ '@tanstack/redact/server': '@tanstack/redact/server',
186
+ '@tanstack/redact/scheduler': '@tanstack/redact/scheduler',
187
+ '@tanstack/redact/dom': '@tanstack/redact/dom',
188
+ '@tanstack/redact': '@tanstack/redact',
189
+ }
190
+
191
+ function splitSpecifier(specifier: string): { pkg: string; sub: string } {
192
+ if (specifier.startsWith('@')) {
193
+ const slash1 = specifier.indexOf('/')
194
+ const slash2 = specifier.indexOf('/', slash1 + 1)
195
+ if (slash2 < 0) return { pkg: specifier, sub: '' }
196
+ return { pkg: specifier.slice(0, slash2), sub: specifier.slice(slash2 + 1) }
197
+ }
198
+ const slash = specifier.indexOf('/')
199
+ if (slash < 0) return { pkg: specifier, sub: '' }
200
+ return { pkg: specifier.slice(0, slash), sub: specifier.slice(slash + 1) }
201
+ }
202
+
203
+ function findPackageDir(pkg: string, fromDir: string): string | null {
204
+ let dir = fromDir
205
+ while (true) {
206
+ const candidate = resolvePath(dir, 'node_modules', pkg)
207
+ if (existsSync(resolvePath(candidate, 'package.json'))) return candidate
208
+ const parent = dirname(dir)
209
+ if (parent === dir) return null
210
+ dir = parent
211
+ }
212
+ }
213
+
214
+ function resolveExport(packageDir: string, sub: string): string | null {
215
+ const pkgJsonPath = resolvePath(packageDir, 'package.json')
216
+ let pkg: any
217
+ try {
218
+ pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
219
+ } catch {
220
+ return null
221
+ }
222
+ const key = sub ? './' + sub : '.'
223
+ const exp = pkg.exports?.[key]
224
+ // Prefer published `import` (dist/.js) over `source` — dist is a single
225
+ // transformed bundle so Vite's dep optimizer doesn't thrash on dozens of
226
+ // individual source files. The package keeps cross-subpath imports
227
+ // external, so there's still only one runtime instance.
228
+ const pick = (v: any): string | null => {
229
+ if (typeof v === 'string') return v
230
+ if (v && typeof v === 'object') {
231
+ return pick(v.import ?? v.module ?? v.source ?? v.default ?? null)
232
+ }
233
+ return null
234
+ }
235
+ const target = pick(exp)
236
+ if (target) return resolvePath(packageDir, target)
237
+ if (!sub) {
238
+ const main = pkg.module ?? pkg.main
239
+ if (typeof main === 'string') return resolvePath(packageDir, main)
240
+ }
241
+ return null
242
+ }
243
+
244
+ // When installed from npm, `@tanstack/redact` is declared as a `dependency`
245
+ // of consumer apps. Under pnpm's strict mode it ends up nested under the
246
+ // plugin's own `.pnpm/@tanstack+redact@.../node_modules/` rather than
247
+ // hoisted to the consumer's root, so a `findPackageDir` walk starting at
248
+ // the Vite project root won't always find it. Search from the plugin's own
249
+ // directory first (which walks into its nested node_modules), then fall
250
+ // back to the consumer root for hoisted installs.
251
+ const pluginDir = dirname(fileURLToPath(import.meta.url))
252
+
253
+ function resolveSpecifier(
254
+ specifier: string,
255
+ fromDir: string,
256
+ packageRoots: Record<string, string>,
257
+ ): string | null {
258
+ const { pkg, sub } = splitSpecifier(specifier)
259
+ const packageDir =
260
+ packageRoots[pkg] ??
261
+ findPackageDir(pkg, pluginDir) ??
262
+ findPackageDir(pkg, fromDir)
263
+ if (!packageDir) return null
264
+ const target = resolveExport(packageDir, sub)
265
+ if (!target) return null
266
+ // Canonicalize through pnpm symlinks. Under strict pnpm, the package may
267
+ // live nested under `.pnpm/@tanstack+redact@.../node_modules/*`, but each
268
+ // of those is itself a symlink to the flat `.pnpm/@tanstack+redact@.../`
269
+ // entry. Vite's `fetchModule` (used by TanStack Start's server-fn
270
+ // compiler) follows the realpath, so the id seen by the capture-transform
271
+ // differs from the nested id we'd return. That leaves the compiler's
272
+ // moduleCache keyed on the realpath while `getModuleInfo` looks up the
273
+ // nested path → miss → "could not load module info". Returning the
274
+ // canonical realpath here keeps the two sides in agreement.
275
+ try {
276
+ return realpathSync(target)
277
+ } catch {
278
+ return target
279
+ }
280
+ }
281
+
282
+ export function redact(options: RedactOptions = {}): any {
283
+ const skip = new Set(options.skip ?? [])
284
+ const entries = Object.entries(ALIASES).filter(([k]) => !skip.has(k))
285
+ const features = resolveFeatures(options.preset ?? 'full', options.features ?? {})
286
+
287
+ const resolvedMap: Record<string, string> = {}
288
+ let done = false
289
+
290
+ function resolveAll(root: string): void {
291
+ if (done) return
292
+ const fromDir = options.resolveFrom ?? root
293
+ const packageRoots = options.packageRoots ?? {}
294
+ for (const [from, to] of entries) {
295
+ const resolved = resolveSpecifier(to, fromDir, packageRoots)
296
+ if (resolved) resolvedMap[from] = resolved
297
+ }
298
+ done = true
299
+ }
300
+
301
+ return {
302
+ name: 'redact',
303
+ enforce: 'pre',
304
+
305
+ config() {
306
+ const excludeList = entries.map(([k]) => k)
307
+ // Single package — only one name to dedupe / no-external.
308
+ const noExt = ['@tanstack/redact']
309
+ // Top-level resolve.alias so `react` → `@tanstack/redact` happens
310
+ // BEFORE any plugin-based resolveId hook fires. The Cloudflare
311
+ // vite-plugin's rolldown worker-runner pre-scans the worker entry's
312
+ // exports and resolves bare specifiers via Vite's alias map directly
313
+ // (not via plugin hooks), so without this it would resolve `react` to
314
+ // the real npm package and try to `require()` it in a Worker (no CJS
315
+ // support). Object form is required — array form is silently ignored
316
+ // by rolldown's worker-runner.
317
+ const aliasMap = Object.fromEntries(entries.filter(([from, to]) => from !== to))
318
+ // Scope optimizeDeps to client + ssr environments ONLY. Do NOT set a
319
+ // top-level optimizeDeps — in Vite 6+ that's effectively the client
320
+ // env's default but also seeps into the rsc env's `'use client'`
321
+ // analysis, causing flood warnings like "inconsistently optimized".
322
+ // Dedupe `@tanstack/redact` so Vite resolves it to a single instance
323
+ // even when multiple packages (e.g. @tanstack/react-router and user
324
+ // code) drag it into different parts of the module graph.
325
+ const dedupe = noExt
326
+ return {
327
+ resolve: {
328
+ alias: aliasMap,
329
+ dedupe,
330
+ },
331
+ environments: {
332
+ client: {
333
+ optimizeDeps: { exclude: excludeList },
334
+ },
335
+ ssr: {
336
+ optimizeDeps: { exclude: excludeList },
337
+ resolve: { noExternal: noExt },
338
+ },
339
+ },
340
+ ssr: { noExternal: noExt },
341
+ }
342
+ },
343
+
344
+ configResolved(config: any) {
345
+ resolveAll(config.root)
346
+ // With `packageRoots`, package sources live outside the consumer's Vite
347
+ // project root, so the default server.fs.allow list blocks them. Append
348
+ // to the resolved allow list rather than replacing via `config()`, so we
349
+ // keep Vite's defaults (root + node_modules + client runtime).
350
+ const fsAllow = Object.values(options.packageRoots ?? {})
351
+ if (fsAllow.length && config.server?.fs?.allow) {
352
+ for (const p of fsAllow) {
353
+ if (!config.server.fs.allow.includes(p)) {
354
+ config.server.fs.allow.push(p)
355
+ }
356
+ }
357
+ }
358
+ },
359
+
360
+ async resolveId(this: any, id: string, importer?: string, opts?: any) {
361
+ // Skip the RSC environment — it relies on real React internals via
362
+ // @vitejs/plugin-rsc's vendored react-server-dom. Substituting our
363
+ // shim there breaks Flight serialization. Client + SSR envs still swap.
364
+ const envName = this?.environment?.name
365
+ if (envName === 'rsc') return null
366
+
367
+ // Feature-flag swap: when the reconciler's `features/index` module
368
+ // imports a feature by relative path, redirect to that feature's stub
369
+ // if the flag is off. The stub registers a graceful-degradation
370
+ // matcher (e.g. Portal → Fragment) so user code keeps working.
371
+ if (importer && /[\\/]features[\\/]index\.[jt]sx?$/.test(importer)) {
372
+ const m = id.match(/^\.\/([a-z-]+)$/)
373
+ if (m) {
374
+ const name = m[1] as keyof ResolvedFeatures
375
+ if (name in features && !features[name]) {
376
+ const r = await this.resolve(`./${name}/stub`, importer, {
377
+ ...opts,
378
+ skipSelf: true,
379
+ })
380
+ if (r) return r.id
381
+ }
382
+ }
383
+ }
384
+
385
+ // Hydration swap: hydration isn't self-registering, so it's imported
386
+ // from reconcile.ts, root.ts, and the Suspense/Lazy feature modules.
387
+ // Any specifier ending in `/hydration` that resolves to our feature
388
+ // module gets redirected to the stub when the flag is off.
389
+ if (!features.hydration && importer && /[\\/]hydration$/.test(id)) {
390
+ const r = await this.resolve(id, importer, { ...opts, skipSelf: true })
391
+ if (r && /features[\\/]hydration[\\/]index\.(ts|js)$/.test(r.id)) {
392
+ return r.id.replace(/index\.(ts|js)$/, 'stub.$1')
393
+ }
394
+ }
395
+
396
+ return resolvedMap[id] ?? null
397
+ },
398
+ }
399
+ }
400
+
401
+ export default redact