@multiplatform.one/config 7.4.0 → 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/README.md +27 -0
- package/lib/vitest.js +56 -17
- package/lint.d.ts +19 -0
- package/lint.mjs +778 -0
- package/oxlint.d.ts +12 -0
- package/oxlint.mjs +183 -0
- package/package.json +16 -3
- package/pin-react-require.cjs +42 -0
- package/src/lint.spec.ts +52 -0
- package/src/oxlint.spec.ts +61 -0
- package/src/vitest.spec.ts +151 -0
- package/src/vitest.ts +138 -26
- package/types/vitest.d.ts.map +1 -1
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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
|
},
|
|
@@ -217,33 +319,43 @@ export function createVitestConfig(options: CreateVitestConfigOptions = {}): Use
|
|
|
217
319
|
plugins,
|
|
218
320
|
test: testConfig,
|
|
219
321
|
resolve: {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
".ts",
|
|
228
|
-
".tsx",
|
|
229
|
-
".js",
|
|
230
|
-
".jsx",
|
|
231
|
-
],
|
|
322
|
+
// Never list .test.* here. Vite tries these suffixes in order for an
|
|
323
|
+
// extensionless specifier, so a leading ".test.ts" makes `import "./foo"`
|
|
324
|
+
// resolve to foo.test.ts instead of foo.ts whenever both exist. That drags
|
|
325
|
+
// the test file (and whatever it imports, e.g. node:test) into the module
|
|
326
|
+
// graph and vitest dies with "Cannot bundle Node.js built-in node:test".
|
|
327
|
+
// Test files are collected by test.include globs, not by resolution.
|
|
328
|
+
extensions: [".web.ts", ".web.tsx", ".web.js", ".web.jsx", ".ts", ".tsx", ".js", ".jsx"],
|
|
232
329
|
mainFields: ["browser", "module", "main"],
|
|
233
330
|
alias: {
|
|
234
331
|
...resolveAliases,
|
|
235
332
|
...aliases,
|
|
236
333
|
},
|
|
237
334
|
conditions,
|
|
238
|
-
|
|
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: [],
|
|
239
341
|
},
|
|
240
342
|
define: {
|
|
241
343
|
__DEV__: true,
|
|
242
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.
|
|
243
348
|
process: JSON.stringify({
|
|
244
349
|
env: {
|
|
245
350
|
NODE_ENV: "test",
|
|
246
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
|
+
: {}),
|
|
247
359
|
},
|
|
248
360
|
platform: process.platform,
|
|
249
361
|
version: process.version,
|
package/types/vitest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"
|
|
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"}
|