@multiplatform.one/config 7.4.1 → 7.5.0

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.
package/src/vitest.ts CHANGED
@@ -1,8 +1,61 @@
1
+ import fs from "node:fs";
2
+ import Module from "node:module";
1
3
  import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
2
5
  import { tamaguiPlugin } from "@tamagui/vite-plugin";
3
6
  import react from "@vitejs/plugin-react";
4
7
  import type { UserConfig } from "vite";
5
8
 
9
+ const pinReactRequireSetup = path.resolve(
10
+ path.dirname(fileURLToPath(import.meta.url)),
11
+ "../pin-react-require.cjs",
12
+ );
13
+
14
+ /**
15
+ * CJS `require('react')` does not go through Vite aliases. `use-sync-external-store`
16
+ * is a valid Node import, so Vitest leaves it external even when we ask to inline
17
+ * it, and Node then loads the hoisted root copy — a different inode than the
18
+ * `.pnpm` copy Vite binds. Patching Module._resolveFilename in the vitest
19
+ * process (config eval + workers that re-load this factory) makes that
20
+ * require land on the same physical copy as the aliases.
21
+ */
22
+ function pinNodeReactRequire(reactDir: string, reactDomDir: string, schedulerDir: string): void {
23
+ const ctor = Module as typeof Module & {
24
+ _resolveFilename: (
25
+ request: string,
26
+ parent: unknown,
27
+ isMain: boolean,
28
+ options?: unknown,
29
+ ) => string;
30
+ _mpoOneReact?: boolean;
31
+ };
32
+ if (ctor._mpoOneReact) return;
33
+ ctor._mpoOneReact = true;
34
+ const orig = ctor._resolveFilename;
35
+ ctor._resolveFilename = function pinned(request, parent, isMain, options) {
36
+ const next =
37
+ request === "react"
38
+ ? reactDir
39
+ : request.startsWith("react/")
40
+ ? path.join(reactDir, request.slice("react/".length))
41
+ : request === "react-dom"
42
+ ? reactDomDir
43
+ : request.startsWith("react-dom/")
44
+ ? path.join(reactDomDir, request.slice("react-dom/".length))
45
+ : request === "scheduler"
46
+ ? schedulerDir
47
+ : null;
48
+ if (next) {
49
+ try {
50
+ return orig.call(this, next, parent, isMain, options);
51
+ } catch {
52
+ // package layout didn't match — keep Node's original lookup
53
+ }
54
+ }
55
+ return orig.call(this, request, parent, isMain, options);
56
+ };
57
+ }
58
+
6
59
  export interface CreateVitestConfigOptions {
7
60
  /**
8
61
  * Path to the Tamagui config file for test environments.
@@ -168,22 +221,54 @@ export function createVitestConfig(options: CreateVitestConfigOptions = {}): Use
168
221
  resolveAliases["react-native-svg"] = "@tamagui/react-native-svg";
169
222
  }
170
223
  if (dedupeReact) {
171
- resolveAliases.react = path.resolve(rootNodeModules, "react");
172
- resolveAliases["react-dom"] = path.resolve(rootNodeModules, "react-dom");
173
- resolveAliases["react-dom/client"] = path.resolve(rootNodeModules, "react-dom/client");
174
- resolveAliases["react-dom/server"] = path.resolve(rootNodeModules, "react-dom/server");
175
- resolveAliases["react/jsx-runtime"] = path.resolve(rootNodeModules, "react/jsx-runtime");
176
- resolveAliases["react/jsx-dev-runtime"] = path.resolve(
177
- rootNodeModules,
178
- "react/jsx-dev-runtime",
179
- );
180
- resolveAliases.scheduler = path.resolve(rootNodeModules, "scheduler");
224
+ // MPO-37. `.npmrc` sets `node-linker=hoisted`, so the workspace ROOT gets
225
+ // a real `node_modules/react` directory while each package's own
226
+ // `node_modules/react` is a symlink into `.pnpm/react@<v>/...`. Those are
227
+ // two PHYSICALLY distinct copies of the same version, and Vite resolves
228
+ // symlinks to their real path — so aliasing at the root while a dependency
229
+ // (say @tanstack/react-query, whose source Vite transforms) resolves
230
+ // through the package symlink puts two Reacts in one module graph.
231
+ //
232
+ // react-dom then installs its dispatcher into one copy while the component
233
+ // tree reads the other, and every render dies on
234
+ // `Cannot read properties of null (reading 'useEffect')` — the whole
235
+ // components render suite, and the render half of forms.
236
+ //
237
+ // So resolve each package to ONE physical copy, preferring the one the
238
+ // package under test actually resolves. react and react-dom live in
239
+ // DIFFERENT `.pnpm` directories (`react@19.2.5` and
240
+ // `react-dom@19.2.5_react@19.2.5`), so this is per-package: a shared
241
+ // parent directory does not exist and assuming one loses react-dom.
242
+ const onePhysicalCopy = (pkg: string): string => {
243
+ for (const base of [path.resolve(process.cwd(), "node_modules"), rootNodeModules]) {
244
+ try {
245
+ return fs.realpathSync(path.resolve(base, pkg));
246
+ } catch {
247
+ // not installed at this level — fall through to the next base
248
+ }
249
+ }
250
+ return path.resolve(rootNodeModules, pkg);
251
+ };
252
+ const reactDir = onePhysicalCopy("react");
253
+ const reactDomDir = onePhysicalCopy("react-dom");
254
+ resolveAliases.react = reactDir;
255
+ resolveAliases["react-dom"] = reactDomDir;
256
+ resolveAliases["react-dom/client"] = path.join(reactDomDir, "client");
257
+ resolveAliases["react-dom/server"] = path.join(reactDomDir, "server");
258
+ resolveAliases["react/jsx-runtime"] = path.join(reactDir, "jsx-runtime");
259
+ resolveAliases["react/jsx-dev-runtime"] = path.join(reactDir, "jsx-dev-runtime");
260
+ const schedulerDir = onePhysicalCopy("scheduler");
261
+ resolveAliases.scheduler = schedulerDir;
262
+ process.env.MPO_VITEST_REACT = reactDir;
263
+ process.env.MPO_VITEST_REACT_DOM = reactDomDir;
264
+ process.env.MPO_VITEST_SCHEDULER = schedulerDir;
265
+ pinNodeReactRequire(reactDir, reactDomDir, schedulerDir);
181
266
  }
182
267
 
183
268
  const testConfig: Record<string, unknown> = {
184
269
  environment,
185
270
  globals: true,
186
- setupFiles,
271
+ setupFiles: dedupeReact ? [pinReactRequireSetup, ...setupFiles] : setupFiles,
187
272
  testTimeout,
188
273
  coverage: {
189
274
  provider: "v8" as const,
@@ -203,8 +288,25 @@ export function createVitestConfig(options: CreateVitestConfigOptions = {}): Use
203
288
  },
204
289
  server: {
205
290
  deps: {
206
- inline: ["react-native-web", "one", /@tamagui\//, "tamagui", ...inlineDeps],
207
- ...(externalDeps.length > 0 ? { external: externalDeps } : {}),
291
+ // `@tanstack/*` ships `.ts` source; Vite already transforms it, but its
292
+ // CJS child `use-sync-external-store` is a valid Node import and stays
293
+ // external unless named here. Node then `require('react')`s the hoisted
294
+ // root directory — a different inode than the `.pnpm` copy the aliases
295
+ // bind. Measured Gantt stack after rebase onto main (forms Button now
296
+ // calls `@tanstack/react-store`): useCallback from node_modules/react,
297
+ // useSelector from @tanstack/react-store/src, react-dom from .pnpm.
298
+ inline: [
299
+ "react-native-web",
300
+ "one",
301
+ /@tamagui\//,
302
+ "tamagui",
303
+ /@tanstack\//,
304
+ "use-sync-external-store",
305
+ ...inlineDeps,
306
+ ],
307
+ ...(externalDeps.length > 0
308
+ ? { external: [pinReactRequireSetup, ...externalDeps] }
309
+ : { external: [pinReactRequireSetup] }),
208
310
  interopDefault: true,
209
311
  },
210
312
  },
@@ -230,15 +332,30 @@ export function createVitestConfig(options: CreateVitestConfigOptions = {}): Use
230
332
  ...aliases,
231
333
  },
232
334
  conditions,
233
- dedupe: dedupeReact ? ["react", "react-dom", "scheduler"] : [],
335
+ // Do not also `dedupe` react. Vite's dedupe always picks the project-root
336
+ // copy, which under `node-linker=hoisted` is a different inode than the
337
+ // `.pnpm` copy the aliases bind. That split is the MPO-37 crash: the
338
+ // alias sends react-dom to `.pnpm`, dedupe sends @tanstack/react-store's
339
+ // `import 'react'` to the hoisted root, dispatcher is null.
340
+ dedupe: [],
234
341
  },
235
342
  define: {
236
343
  __DEV__: true,
237
344
  global: "globalThis",
345
+ // This replaces the `process` identifier in transformed modules. Keep the
346
+ // MPO_VITEST_* paths on that stub so pin-react-require.cjs still sees
347
+ // them if Vite ever transforms the setup file.
238
348
  process: JSON.stringify({
239
349
  env: {
240
350
  NODE_ENV: "test",
241
351
  NODE_DEBUG: false,
352
+ ...(dedupeReact
353
+ ? {
354
+ MPO_VITEST_REACT: process.env.MPO_VITEST_REACT,
355
+ MPO_VITEST_REACT_DOM: process.env.MPO_VITEST_REACT_DOM,
356
+ MPO_VITEST_SCHEDULER: process.env.MPO_VITEST_SCHEDULER,
357
+ }
358
+ : {}),
242
359
  },
243
360
  platform: process.platform,
244
361
  version: process.version,
@@ -1 +1 @@
1
- {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAE/B;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;IAE7C;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IAEF;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,UAAU,CA8ItF"}
1
+ {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAoDvC,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAE/B;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;IAE7C;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IAEF;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,UAAU,CA8MtF"}