@multiplatform.one/config 7.6.3 → 7.7.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.
- package/lib/index.js +2 -2
- package/lib/{storybook-BnfZmzIL.js → storybook-BuO1e8Sb.js} +98 -1
- package/lib/storybook.js +1 -1
- package/lib/{vite-W9hIeg2_.js → vite-uwqTTN8M.js} +115 -2
- package/lib/vite.js +1 -1
- package/lint.mjs +448 -2
- package/package.json +2 -2
- package/src/baseTsconfigTypes.spec.ts +42 -0
- package/src/emittingTsconfigPaths.spec.ts +85 -0
- package/src/lint.spec.ts +379 -0
- package/src/singletonDepAliases.spec.ts +83 -0
- package/src/singletonDepAliases.ts +105 -0
- package/src/storybook.ts +16 -0
- package/src/tamaguiWorkspacePaths.spec.ts +34 -0
- package/src/vite.ts +165 -1
- package/tsconfig/base.json +10 -1
- package/types/singletonDepAliases.d.ts +12 -0
- package/types/singletonDepAliases.d.ts.map +1 -0
- package/types/storybook.d.ts.map +1 -1
- package/types/vite.d.ts.map +1 -1
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as createViteConfig } from "./vite-
|
|
2
|
-
import { t as createStorybookViteConfig } from "./storybook-
|
|
1
|
+
import { t as createViteConfig } from "./vite-uwqTTN8M.js";
|
|
2
|
+
import { t as createStorybookViteConfig } from "./storybook-BuO1e8Sb.js";
|
|
3
3
|
import { n as tamaguiWorkspacePathsFile, t as ensureTamaguiWorkspacePaths } from "./tamaguiWorkspacePaths-ZIcqybtn.js";
|
|
4
4
|
import { createVitestConfig } from "./vitest.js";
|
|
5
5
|
|
|
@@ -4,6 +4,100 @@ import { createRequire } from "node:module";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
|
+
//#region src/singletonDepAliases.ts
|
|
8
|
+
/**
|
|
9
|
+
* Packages that create a React context at module scope and must therefore
|
|
10
|
+
* resolve to exactly ONE module record across the whole bundle.
|
|
11
|
+
*
|
|
12
|
+
* MPO-215. `react-cookie` runs `React.createContext(null)` when its entry
|
|
13
|
+
* evaluates. `@multiplatform.one/storybook` renders `<CookiesProvider>` in the
|
|
14
|
+
* framework decorator and `@multiplatform.one/theme`'s `useTheme` reads it via
|
|
15
|
+
* `useCookies`, so provider and consumer live in two different workspace
|
|
16
|
+
* packages. When those two packages resolve `react-cookie` to two different
|
|
17
|
+
* realpaths — a hoisted `node_modules/react-cookie` for the importer that does
|
|
18
|
+
* not declare it, the pnpm store copy for the one that does — the entry
|
|
19
|
+
* evaluates twice, there are two contexts, and `useCookies` throws
|
|
20
|
+
* `Missing <CookiesProvider>` with the provider sitting two lines above it in
|
|
21
|
+
* the same tree.
|
|
22
|
+
*
|
|
23
|
+
* Nothing catches that today. The dev server pre-bundles bare imports by
|
|
24
|
+
* package NAME, which collapses both realpaths onto one `deps/react-cookie.js`
|
|
25
|
+
* and renders fine; the production build has no optimizer, so both paths
|
|
26
|
+
* survive, every story renders Storybook's error panel, and
|
|
27
|
+
* `storybook build` still exits 0.
|
|
28
|
+
*
|
|
29
|
+
* `resolve.dedupe` does not fix it: measured against Vite 8 / Rolldown, a
|
|
30
|
+
* build with `dedupe: ["react-cookie"]` emitted a byte-identical iframe chunk
|
|
31
|
+
* and the story still threw. An alias to one absolute directory does, which is
|
|
32
|
+
* the same instrument the storybook config already uses to pin react,
|
|
33
|
+
* react-dom and react-is.
|
|
34
|
+
*
|
|
35
|
+
* MPO-217. `@tamagui/web` is the same shape. It owns the theme context, the
|
|
36
|
+
* style registry and the config singleton; `@tamagui/core` re-exports it and
|
|
37
|
+
* `tamagui` re-exports that, so every tamagui component in the tree reads the
|
|
38
|
+
* theme through whichever `@tamagui/web` record its own import chain reached.
|
|
39
|
+
*
|
|
40
|
+
* Measured on the build's module graph at e19a0fda3: `tamagui` resolved to
|
|
41
|
+
* two realpaths. 562 importers (public/components, public/backoffice,
|
|
42
|
+
* public/storybook and the rest) took the pnpm store copy, whose chain is
|
|
43
|
+
* store `tamagui` -> store `@tamagui/core` -> store `@tamagui/web`; five took
|
|
44
|
+
* the hoisted `node_modules/tamagui`, which has no nested node_modules and so
|
|
45
|
+
* walks up to the hoisted `@tamagui/core` and `@tamagui/web`. Those five were
|
|
46
|
+
* public/frappe's story files — the only modules in the repo that import
|
|
47
|
+
* `tamagui` from a package with no copy of its own — and their 14 stories were
|
|
48
|
+
* the 14 that threw `Missing theme.` on storybook-static while rendering on
|
|
49
|
+
* dev. The theme provider is mounted from the store record; their `Button`,
|
|
50
|
+
* `Text` and `YStack` read the hoisted record's context, which no provider
|
|
51
|
+
* ever wrote to.
|
|
52
|
+
*
|
|
53
|
+
* `tamagui` itself is deliberately NOT pinned. A directory alias resolves
|
|
54
|
+
* `tamagui/linear-gradient` to `node_modules/tamagui/linear-gradient/`, a
|
|
55
|
+
* metro-compat stub that `require`s the CJS build, instead of the ESM target
|
|
56
|
+
* the package's exports map picks for the browser. Pinning core and web is
|
|
57
|
+
* enough: both `tamagui` records import `@tamagui/core` by bare specifier, so
|
|
58
|
+
* they funnel into one web record and one theme context.
|
|
59
|
+
*/
|
|
60
|
+
const CONTEXT_SINGLETONS = [
|
|
61
|
+
"react-cookie",
|
|
62
|
+
"@tamagui/core",
|
|
63
|
+
"@tamagui/web"
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Vite `resolve.alias` entries pinning each context singleton to one directory.
|
|
67
|
+
*
|
|
68
|
+
* Alias keys match the whole specifier or a `key + "/"` prefix, so subpath
|
|
69
|
+
* imports follow the same copy instead of resolving independently. Entries are
|
|
70
|
+
* omitted when the package is not installed, so a consumer that does not use
|
|
71
|
+
* cookies is unaffected.
|
|
72
|
+
*
|
|
73
|
+
* @param root Directory whose `node_modules` the packages resolve from.
|
|
74
|
+
*/
|
|
75
|
+
function singletonDepAliases(root = process.cwd()) {
|
|
76
|
+
const aliases = {};
|
|
77
|
+
const requireFrom = createRequire(path.join(root, "package.json"));
|
|
78
|
+
for (const pkgName of CONTEXT_SINGLETONS) {
|
|
79
|
+
const pkgDir = resolvePackageDir$1(pkgName, root, requireFrom);
|
|
80
|
+
if (pkgDir) aliases[pkgName] = pkgDir;
|
|
81
|
+
}
|
|
82
|
+
return aliases;
|
|
83
|
+
}
|
|
84
|
+
function resolvePackageDir$1(pkgName, root, requireFrom) {
|
|
85
|
+
try {
|
|
86
|
+
return path.dirname(requireFrom.resolve(`${pkgName}/package.json`));
|
|
87
|
+
} catch {
|
|
88
|
+
try {
|
|
89
|
+
let dir = path.dirname(requireFrom.resolve(pkgName));
|
|
90
|
+
while (dir !== path.dirname(dir)) {
|
|
91
|
+
if (fs.existsSync(path.join(dir, "package.json"))) return dir;
|
|
92
|
+
dir = path.dirname(dir);
|
|
93
|
+
}
|
|
94
|
+
} catch {}
|
|
95
|
+
const hoisted = path.join(root, "node_modules", ...pkgName.split("/"));
|
|
96
|
+
return fs.existsSync(path.join(hoisted, "package.json")) ? hoisted : void 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
7
101
|
//#region src/unexportedDepAliases.ts
|
|
8
102
|
/**
|
|
9
103
|
* Package subtrees that consumers deep-import but the publisher never declared
|
|
@@ -95,7 +189,8 @@ function createStorybookViteConfig(options = {}) {
|
|
|
95
189
|
const aliases = {
|
|
96
190
|
...discoverPackageAliases(path.join(workspaceRoot, "packages")),
|
|
97
191
|
...discoverPublicPackageViteAliases(workspaceRoot),
|
|
98
|
-
...unexportedDepAliases(workspaceRoot)
|
|
192
|
+
...unexportedDepAliases(workspaceRoot),
|
|
193
|
+
...singletonDepAliases(workspaceRoot)
|
|
99
194
|
};
|
|
100
195
|
if (options.aliases) Object.assign(aliases, options.aliases);
|
|
101
196
|
const sortedAliases = {};
|
|
@@ -157,6 +252,8 @@ function createStorybookViteConfig(options = {}) {
|
|
|
157
252
|
"@tamagui/themes",
|
|
158
253
|
"@tamagui/toast",
|
|
159
254
|
"@tamagui/use-presence",
|
|
255
|
+
"react-cookie",
|
|
256
|
+
"tamagui/linear-gradient",
|
|
160
257
|
"@mdx-js/react",
|
|
161
258
|
"i18next",
|
|
162
259
|
"react-i18next",
|
package/lib/storybook.js
CHANGED
|
@@ -92,6 +92,7 @@ function createViteConfig(options = {}) {
|
|
|
92
92
|
vitePlugins.push(ssrReactNativeAliasPlugin());
|
|
93
93
|
vitePlugins.push(clientBrokenEsmPlugin());
|
|
94
94
|
registerNativeEngineIoBrowserTransports();
|
|
95
|
+
registerNativeSinglePackageInstances();
|
|
95
96
|
registerNativeWorkspaceEntries();
|
|
96
97
|
registerNativePhosphorIcons();
|
|
97
98
|
if (nativeFixes?.protoShadow !== false) registerNativeProtoShadowFix();
|
|
@@ -189,7 +190,13 @@ function createViteConfig(options = {}) {
|
|
|
189
190
|
return {
|
|
190
191
|
css: { modules: { localsConvention: "camelCase" } },
|
|
191
192
|
build: { chunkSizeWarningLimit: 600 },
|
|
192
|
-
define: {
|
|
193
|
+
define: {
|
|
194
|
+
"process.env.VITE_FRAPPE_ENABLED": JSON.stringify(process.env.VITE_FRAPPE_ENABLED === "true" ? "true" : "false"),
|
|
195
|
+
...publicConfigKeys.length > 0 ? {
|
|
196
|
+
"process.env.VITE_MP_CONFIG": JSON.stringify(process.env.VITE_MP_CONFIG ?? ""),
|
|
197
|
+
"process.env.VITE_MP_PUBLIC_CONFIG_KEYS": JSON.stringify(process.env.VITE_MP_PUBLIC_CONFIG_KEYS ?? "")
|
|
198
|
+
} : {}
|
|
199
|
+
},
|
|
193
200
|
ssr: ssr || defaultSsr,
|
|
194
201
|
resolve: {
|
|
195
202
|
alias: {
|
|
@@ -276,6 +283,107 @@ function clientBrokenEsmPlugin() {
|
|
|
276
283
|
* This re-asserts the browser redirect with no node_modules patch. Web is
|
|
277
284
|
* unaffected (engine.io is SSR-externalized and the web client already maps `browser`).
|
|
278
285
|
*/
|
|
286
|
+
/**
|
|
287
|
+
* Collapse duplicate PHYSICAL copies of the same package@version to one path
|
|
288
|
+
* in the NATIVE graph, so React (and every other stateful singleton) exists
|
|
289
|
+
* exactly once in the Hermes bundle.
|
|
290
|
+
*
|
|
291
|
+
* The workspace's node_modules is a MIXED install. `.npmrc` says
|
|
292
|
+
* `node-linker=hoisted`, so the root `node_modules/<pkg>` entries are real
|
|
293
|
+
* directories; but an earlier isolated install left a `node_modules/.pnpm`
|
|
294
|
+
* virtual store plus per-package symlink farms behind — e.g. each package's
|
|
295
|
+
* own `node_modules/react` still points at
|
|
296
|
+
* `../../../node_modules/.pnpm/react@19.2.5/node_modules/react`.
|
|
297
|
+
* Both trees are fully populated with byte-identical copies, and which one an
|
|
298
|
+
* importer reaches depends only on where that importer sits on disk:
|
|
299
|
+
*
|
|
300
|
+
* apps/one/app -> node_modules/.pnpm/react@19.2.5/node_modules/react
|
|
301
|
+
* public/forms/src -> node_modules/react (no local symlink)
|
|
302
|
+
*
|
|
303
|
+
* Rolldown resolves symlinks, so the two land as two DIFFERENT absolute paths
|
|
304
|
+
* and it bundles both. React 19's `useContext` is
|
|
305
|
+
* `ReactSharedInternals.H.useContext(Context)` — `H` is the dispatcher the
|
|
306
|
+
* renderer installs during render, and it is only ever set on the copy the
|
|
307
|
+
* renderer imported. A component from the other copy therefore reads `H` as
|
|
308
|
+
* null and the app dies with "Cannot read property 'useContext' of null".
|
|
309
|
+
* That is the whole bug; the same split had also duplicated react-native (427
|
|
310
|
+
* modules each), @tamagui/web, @react-navigation/core, one, scheduler and 58
|
|
311
|
+
* more packages.
|
|
312
|
+
*
|
|
313
|
+
* The rule: an id that resolves inside the virtual store is rewritten to the
|
|
314
|
+
* hoisted root copy when the root copy exists, carries the SAME version, and
|
|
315
|
+
* actually has the file. Version equality is the safety guard — genuine
|
|
316
|
+
* multi-version installs (two @babel/runtime, two viem) keep both copies.
|
|
317
|
+
*
|
|
318
|
+
* On a correctly installed tree this is a no-op by construction: `hoisted`
|
|
319
|
+
* leaves no `.pnpm` store to match, and `isolated` makes the root entry a
|
|
320
|
+
* symlink INTO the store, which realpaths back to the same directory (checked
|
|
321
|
+
* explicitly). It only fires on the mixed tree, and it is cheap because only
|
|
322
|
+
* BARE specifiers are inspected — once a package's entry is canonical, its own
|
|
323
|
+
* relative imports resolve inside the canonical copy for free.
|
|
324
|
+
*/
|
|
325
|
+
function registerNativeSinglePackageInstances() {
|
|
326
|
+
const g = globalThis;
|
|
327
|
+
const name = "vxrn-single-package-instances";
|
|
328
|
+
g.__vxrnAddNativePlugins = g.__vxrnAddNativePlugins ?? [];
|
|
329
|
+
if (g.__vxrnAddNativePlugins.some((p) => p?.name === name)) return;
|
|
330
|
+
const rootModules = path.join(findProjectRoot(), "node_modules");
|
|
331
|
+
const storePath = /[\\/]node_modules[\\/]\.pnpm[\\/][^\\/]+[\\/]node_modules[\\/]((?:@[^\\/]+[\\/])?[^\\/]+)(?:[\\/](.*))?$/;
|
|
332
|
+
function versionOf(dir) {
|
|
333
|
+
try {
|
|
334
|
+
const raw = fs.readFileSync(path.join(dir, "package.json"), "utf8");
|
|
335
|
+
return JSON.parse(raw).version ?? null;
|
|
336
|
+
} catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const hoisted = /* @__PURE__ */ new Map();
|
|
341
|
+
function hoistedTwin(pkg, storeDir) {
|
|
342
|
+
if (!hoisted.has(pkg)) {
|
|
343
|
+
const rootDir = path.join(rootModules, pkg);
|
|
344
|
+
let twin = null;
|
|
345
|
+
if (fs.existsSync(rootDir)) {
|
|
346
|
+
const rootVersion = versionOf(rootDir);
|
|
347
|
+
const storeVersion = versionOf(storeDir);
|
|
348
|
+
if (rootVersion && rootVersion === storeVersion) try {
|
|
349
|
+
if (fs.realpathSync(rootDir) !== fs.realpathSync(storeDir)) twin = rootDir;
|
|
350
|
+
} catch {
|
|
351
|
+
twin = null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
hoisted.set(pkg, twin);
|
|
355
|
+
}
|
|
356
|
+
return hoisted.get(pkg) ?? null;
|
|
357
|
+
}
|
|
358
|
+
const seen = /* @__PURE__ */ new Map();
|
|
359
|
+
g.__vxrnAddNativePlugins.push({
|
|
360
|
+
name,
|
|
361
|
+
async resolveId(id, importer) {
|
|
362
|
+
if (!id || id.startsWith(".") || id.startsWith("/") || id.startsWith("\0")) return null;
|
|
363
|
+
const key = `${id} ${importer ?? ""}`;
|
|
364
|
+
const cached = seen.get(key);
|
|
365
|
+
if (cached !== void 0) return cached;
|
|
366
|
+
let out = null;
|
|
367
|
+
try {
|
|
368
|
+
const resolved = await this.resolve(id, importer, { skipSelf: true });
|
|
369
|
+
const match = resolved?.external ? null : resolved?.id?.match(storePath);
|
|
370
|
+
if (resolved && match) {
|
|
371
|
+
const pkg = match[1].split(path.sep).join("/");
|
|
372
|
+
const rest = match[2] ?? "";
|
|
373
|
+
const twin = hoistedTwin(pkg, resolved.id.slice(0, resolved.id.length - (rest ? rest.length + 1 : 0)));
|
|
374
|
+
if (twin) {
|
|
375
|
+
const candidate = rest ? path.join(twin, rest) : twin;
|
|
376
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) out = candidate;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} catch {
|
|
380
|
+
out = null;
|
|
381
|
+
}
|
|
382
|
+
seen.set(key, out);
|
|
383
|
+
return out;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
279
387
|
function registerNativeEngineIoBrowserTransports() {
|
|
280
388
|
const g = globalThis;
|
|
281
389
|
const name = "vxrn-engineio-browser-transports";
|
|
@@ -322,7 +430,12 @@ function registerNativeWorkspaceEntries() {
|
|
|
322
430
|
const root = roots.get(id);
|
|
323
431
|
if (root) {
|
|
324
432
|
const nativeEntry = path.join(root, "dist/esm/index.native.js");
|
|
325
|
-
|
|
433
|
+
if (fs.existsSync(nativeEntry)) return nativeEntry;
|
|
434
|
+
const srcTs = path.join(root, "src/index.ts");
|
|
435
|
+
if (fs.existsSync(srcTs)) return srcTs;
|
|
436
|
+
const srcTsx = path.join(root, "src/index.tsx");
|
|
437
|
+
if (fs.existsSync(srcTsx)) return srcTsx;
|
|
438
|
+
return null;
|
|
326
439
|
}
|
|
327
440
|
if (id.endsWith("/dist/esm/index.mjs")) {
|
|
328
441
|
const nativeEntry = `${id.slice(0, -4)}.native.js`;
|
package/lib/vite.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createViteConfig } from "./vite-
|
|
1
|
+
import { t as createViteConfig } from "./vite-uwqTTN8M.js";
|
|
2
2
|
import { n as publicPackageViteSourceAliases, t as discoverPublicPackageRoots } from "./workspacePublicPackages-COicQSj4.js";
|
|
3
3
|
|
|
4
4
|
export { createViteConfig, discoverPublicPackageRoots, publicPackageViteSourceAliases };
|