@multiplatform.one/config 6.7.0 → 7.1.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/lib/index.js +2 -2
- package/lib/storybook-CrdwuS_Y.js +274 -0
- package/lib/storybook.js +1 -204
- package/lib/{vite-C6psj1Lz.js → vite-C7884n3X.js} +114 -3
- package/lib/vite.js +2 -2
- package/package.json +5 -4
- package/src/shims/web3Stub.native.js +18 -0
- package/src/storybook.ts +19 -9
- package/src/unexportedDepAliases.spec.ts +90 -0
- package/src/unexportedDepAliases.ts +77 -0
- package/src/vite.spec.ts +148 -0
- package/src/vite.ts +183 -0
- package/types/storybook.d.ts.map +1 -1
- package/types/unexportedDepAliases.d.ts +16 -0
- package/types/unexportedDepAliases.d.ts.map +1 -0
- package/types/vite.d.ts +33 -0
- package/types/vite.d.ts.map +1 -1
- /package/lib/{workspacePublicPackages-CkDbm3QT.js → workspacePublicPackages-COicQSj4.js} +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No-op `@multiplatform.one/web3(/native)` shim for NATIVE runs.
|
|
3
|
+
*
|
|
4
|
+
* `@multiplatform.one/core`'s loadWalletProvider.native.ts lazily
|
|
5
|
+
* `import("@multiplatform.one/web3/native")` — an OPTIONAL package many apps
|
|
6
|
+
* do not install. vxrn's rolldown native bundler leaves the unresolvable
|
|
7
|
+
* specifier as a bare dynamic `import()` expression in the classic-script
|
|
8
|
+
* bundle, which Hermes rejects at lazy-compile time with "SyntaxError:
|
|
9
|
+
* Invalid expression encountered", killing the root _layout route.
|
|
10
|
+
* createViteConfig's registerNativeWeb3Stub redirects the specifier here so
|
|
11
|
+
* the import inlines and parses; it auto-skips when web3 is installed.
|
|
12
|
+
*
|
|
13
|
+
* Plain JS (not TS): vxrn's native pipeline does not transform node_modules
|
|
14
|
+
* TypeScript.
|
|
15
|
+
*/
|
|
16
|
+
export function WalletProvider({ children }) {
|
|
17
|
+
return children ?? null;
|
|
18
|
+
}
|
package/src/storybook.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import type { Plugin, UserConfig } from "vite";
|
|
4
|
+
import { unexportedDepAliases } from "./unexportedDepAliases.js";
|
|
4
5
|
import { discoverPublicPackageRoots, resolvePackageMainSource } from "./workspacePublicPackages.js";
|
|
5
6
|
|
|
6
7
|
export interface CreateStorybookViteConfigOptions {
|
|
@@ -54,6 +55,7 @@ export function createStorybookViteConfig(
|
|
|
54
55
|
const aliases = {
|
|
55
56
|
...discoverPackageAliases(packagesDir),
|
|
56
57
|
...discoverPublicPackageViteAliases(workspaceRoot),
|
|
58
|
+
...unexportedDepAliases(workspaceRoot),
|
|
57
59
|
};
|
|
58
60
|
|
|
59
61
|
if (options.aliases) {
|
|
@@ -81,10 +83,18 @@ export function createStorybookViteConfig(
|
|
|
81
83
|
// transitive deps (color, query-string, etc.) to be served raw,
|
|
82
84
|
// causing "require is not defined" / missing-export errors in the browser.
|
|
83
85
|
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
86
|
+
// This list does NOT resolve @react-navigation/core's internals. Those are
|
|
87
|
+
// undeclared subpaths, so no condition can reach them — see
|
|
88
|
+
// unexportedDepAliases, which maps them by filesystem path instead.
|
|
89
|
+
//
|
|
90
|
+
// "default" must be included: Rolldown 8 strictly enforces exports
|
|
91
|
+
// fields and needs "default" in the conditions list to reach a target
|
|
92
|
+
// that is only reachable through the default branch — including the
|
|
93
|
+
// "./lib/module/*" subpath patches/@react-navigation__core@7.17.2.patch
|
|
94
|
+
// adds, which one's SSRNavigationContainer fork deep-imports. Stock
|
|
95
|
+
// 7.17.2 exports only "." and "./package.json", so no condition list
|
|
96
|
+
// alone resolves those files; the patch is what makes them reachable
|
|
97
|
+
// and this entry is what lets the resolver pick its default target.
|
|
88
98
|
conditions: ["default"],
|
|
89
99
|
extensions: [
|
|
90
100
|
".storybook.ts",
|
|
@@ -141,11 +151,11 @@ export function createStorybookViteConfig(
|
|
|
141
151
|
"query-string",
|
|
142
152
|
"react-is",
|
|
143
153
|
],
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
// are
|
|
154
|
+
// Keeps the nav packages out of dep pre-bundling; their CJS transitive deps
|
|
155
|
+
// (color, use-sync-external-store, fast-deep-equal) are already explicitly in
|
|
156
|
+
// include above so they are still pre-bundled. Note this only covers the dev
|
|
157
|
+
// optimizer — the undeclared @react-navigation/core internals that `one`
|
|
158
|
+
// deep-imports are handled for dev AND build by unexportedDepAliases.
|
|
149
159
|
exclude: [
|
|
150
160
|
"one/dist/esm/vite/one-server-only.mjs",
|
|
151
161
|
"@storybook/preview-api",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
6
|
+
import { unexportedDepAliases } from "./unexportedDepAliases";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Builds a throwaway tree carrying the PUBLISHED @react-navigation/core exports
|
|
10
|
+
* map — the one CI installs, before vxrn rewrites it in place. The real tree on
|
|
11
|
+
* a dev machine is usually already patched, so asserting against it would prove
|
|
12
|
+
* nothing about the state this alias exists for.
|
|
13
|
+
*/
|
|
14
|
+
function writePristineFixture(root: string, { withPackage = true } = {}) {
|
|
15
|
+
fs.mkdirSync(root, { recursive: true });
|
|
16
|
+
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "fixture-root" }));
|
|
17
|
+
if (!withPackage) return;
|
|
18
|
+
|
|
19
|
+
const pkgDir = path.join(root, "node_modules", "@react-navigation", "core");
|
|
20
|
+
fs.mkdirSync(path.join(pkgDir, "lib", "module"), { recursive: true });
|
|
21
|
+
fs.writeFileSync(
|
|
22
|
+
path.join(pkgDir, "package.json"),
|
|
23
|
+
JSON.stringify({
|
|
24
|
+
name: "@react-navigation/core",
|
|
25
|
+
version: "7.17.2",
|
|
26
|
+
main: "./lib/module/index.js",
|
|
27
|
+
// Verbatim shape of the published manifest: no ./lib/module/* entries.
|
|
28
|
+
exports: { ".": { default: "./lib/module/index.js" }, "./package.json": "./package.json" },
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
fs.writeFileSync(
|
|
32
|
+
path.join(pkgDir, "lib", "module", "EnsureSingleNavigator.js"),
|
|
33
|
+
"export const SingleNavigatorContext = {};\n",
|
|
34
|
+
);
|
|
35
|
+
fs.writeFileSync(path.join(pkgDir, "lib", "module", "index.js"), "export const x = 1;\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe("unexportedDepAliases", () => {
|
|
39
|
+
// realpath: macOS /var is a symlink to /private/var, and require.resolve
|
|
40
|
+
// reports the resolved path.
|
|
41
|
+
const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "unexported-dep-aliases-")));
|
|
42
|
+
const deepSpecifier = "@react-navigation/core/lib/module/EnsureSingleNavigator";
|
|
43
|
+
|
|
44
|
+
beforeAll(() => writePristineFixture(root));
|
|
45
|
+
afterAll(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
46
|
+
|
|
47
|
+
it("positive control: the published exports map really does reject the deep import", () => {
|
|
48
|
+
// Without this, every assertion below could be green against a package that
|
|
49
|
+
// resolves fine on its own — i.e. an alias guarding nothing.
|
|
50
|
+
const requireFrom = createRequire(path.join(root, "package.json"));
|
|
51
|
+
expect(() => requireFrom.resolve(deepSpecifier)).toThrowError(
|
|
52
|
+
expect.objectContaining({ code: "ERR_PACKAGE_PATH_NOT_EXPORTED" }),
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("maps the internals `one` deep-imports onto a directory that holds them", () => {
|
|
57
|
+
const aliases = unexportedDepAliases(root);
|
|
58
|
+
const target = aliases["@react-navigation/core/lib/module"];
|
|
59
|
+
|
|
60
|
+
expect(target).toBe(
|
|
61
|
+
path.join(root, "node_modules", "@react-navigation", "core", "lib", "module"),
|
|
62
|
+
);
|
|
63
|
+
// The alias is only worth anything if rewriting the specifier lands on a
|
|
64
|
+
// real file — Vite replaces the matched key and resolves the rest by path.
|
|
65
|
+
const rewritten = deepSpecifier.replace("@react-navigation/core/lib/module", target);
|
|
66
|
+
expect(fs.existsSync(`${rewritten}.js`)).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("scopes the alias to the subtree, leaving the package entry on its exports map", () => {
|
|
70
|
+
const keys = Object.keys(unexportedDepAliases(root));
|
|
71
|
+
|
|
72
|
+
// Vite matches an alias key exactly or as a `key + "/"` prefix. A bare
|
|
73
|
+
// "@react-navigation/core" key would swallow the package's own entry, and a
|
|
74
|
+
// trailing slash would stop the key matching at all.
|
|
75
|
+
expect(keys).toEqual(["@react-navigation/core/lib/module"]);
|
|
76
|
+
expect(keys[0]).not.toMatch(/\/$/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("emits nothing when the package is not installed", () => {
|
|
80
|
+
const bare = fs.realpathSync(
|
|
81
|
+
fs.mkdtempSync(path.join(os.tmpdir(), "unexported-dep-aliases-bare-")),
|
|
82
|
+
);
|
|
83
|
+
writePristineFixture(bare, { withPackage: false });
|
|
84
|
+
try {
|
|
85
|
+
expect(unexportedDepAliases(bare)).toEqual({});
|
|
86
|
+
} finally {
|
|
87
|
+
fs.rmSync(bare, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Package subtrees that consumers deep-import but the publisher never declared
|
|
7
|
+
* in its `exports` map.
|
|
8
|
+
*
|
|
9
|
+
* `one`'s `fork/SSRNavigationContainer` (every dist flavour plus its source)
|
|
10
|
+
* imports three `@react-navigation/core` internals:
|
|
11
|
+
*
|
|
12
|
+
* @react-navigation/core/lib/module/NavigationBuilderContext
|
|
13
|
+
* @react-navigation/core/lib/module/NavigationStateContext
|
|
14
|
+
* @react-navigation/core/lib/module/EnsureSingleNavigator
|
|
15
|
+
*
|
|
16
|
+
* The published package exports only "." and "./package.json", so those are
|
|
17
|
+
* undeclared subpaths of a bare specifier — Node rejects them with
|
|
18
|
+
* ERR_PACKAGE_PATH_NOT_EXPORTED and Rolldown (Vite 8) rejects them at build
|
|
19
|
+
* time. Both are behaving correctly; the deep imports are the bug.
|
|
20
|
+
*
|
|
21
|
+
* vxrn papers over it by rewriting the dependency's package.json in place
|
|
22
|
+
* (`vxrn` builtInDepPatches, which leaves a package.json.vxrn.original behind),
|
|
23
|
+
* but that only runs when something invokes vxrn — never during a plain
|
|
24
|
+
* `vite build` or `storybook build`, and never on a fresh CI install. Mapping
|
|
25
|
+
* the subtree to its real directory resolves those files by path, which is
|
|
26
|
+
* exactly where the missing exports entries would have pointed.
|
|
27
|
+
*/
|
|
28
|
+
const UNEXPORTED_SUBTREES: readonly string[] = ["@react-navigation/core/lib/module"];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Vite `resolve.alias` entries that let known-undeclared package internals
|
|
32
|
+
* resolve by filesystem path.
|
|
33
|
+
*
|
|
34
|
+
* Scoped to one subtree of one package: Vite alias keys match the whole
|
|
35
|
+
* specifier or a `key + "/"` prefix, so sibling paths (`lib/moduleOther`) and
|
|
36
|
+
* the package's own entry (`@react-navigation/core`) keep going through the
|
|
37
|
+
* `exports` map. Nothing here disables exports enforcement globally.
|
|
38
|
+
*
|
|
39
|
+
* Entries are omitted when the package (or the target directory) is absent, so
|
|
40
|
+
* consumers that do not install `one`/react-navigation are unaffected.
|
|
41
|
+
*
|
|
42
|
+
* @param root Directory whose `node_modules` the packages resolve from.
|
|
43
|
+
*/
|
|
44
|
+
export function unexportedDepAliases(root: string = process.cwd()): Record<string, string> {
|
|
45
|
+
const aliases: Record<string, string> = {};
|
|
46
|
+
const requireFrom = createRequire(path.join(root, "package.json"));
|
|
47
|
+
|
|
48
|
+
for (const specifier of UNEXPORTED_SUBTREES) {
|
|
49
|
+
const [pkgName, subpath] = splitPackageSubpath(specifier);
|
|
50
|
+
const pkgDir = resolvePackageDir(pkgName, root, requireFrom);
|
|
51
|
+
if (!pkgDir) continue;
|
|
52
|
+
const target = path.join(pkgDir, subpath);
|
|
53
|
+
if (fs.existsSync(target)) aliases[specifier] = target;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return aliases;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function splitPackageSubpath(specifier: string): [pkgName: string, subpath: string] {
|
|
60
|
+
const segments = specifier.split("/");
|
|
61
|
+
const nameLength = specifier.startsWith("@") ? 2 : 1;
|
|
62
|
+
return [segments.slice(0, nameLength).join("/"), segments.slice(nameLength).join("/")];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolvePackageDir(
|
|
66
|
+
pkgName: string,
|
|
67
|
+
root: string,
|
|
68
|
+
requireFrom: NodeRequire,
|
|
69
|
+
): string | undefined {
|
|
70
|
+
try {
|
|
71
|
+
return path.dirname(requireFrom.resolve(`${pkgName}/package.json`));
|
|
72
|
+
} catch {
|
|
73
|
+
// Packages that don't export "./package.json" can't be resolved that way.
|
|
74
|
+
const hoisted = path.join(root, "node_modules", ...pkgName.split("/"));
|
|
75
|
+
return fs.existsSync(path.join(hoisted, "package.json")) ? hoisted : undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/vite.spec.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createViteConfig — absorbed consumer workarounds (ssr CJS externals +
|
|
3
|
+
* native Hermes fixes). These used to live per-app (apps/one and downstream
|
|
4
|
+
* consumer vite configs); the spec pins the shared defaults and their
|
|
5
|
+
* escape hatches.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
9
|
+
import type { Plugin } from "vite";
|
|
10
|
+
import { createViteConfig } from "./vite.js";
|
|
11
|
+
|
|
12
|
+
type NativePlugin = {
|
|
13
|
+
name?: string;
|
|
14
|
+
renderChunk?: (code: string) => { code: string; map: null };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const globalWithNativePlugins = globalThis as unknown as {
|
|
18
|
+
__vxrnAddNativePlugins?: NativePlugin[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function findPlugin(config: ReturnType<typeof createViteConfig>, name: string): Plugin | undefined {
|
|
22
|
+
return (config.plugins as Plugin[]).flat().find((p) => p && (p as Plugin).name === name) as
|
|
23
|
+
| Plugin
|
|
24
|
+
| undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function runConfigResolved(plugin: Plugin, ssrExternal: string[] | boolean | undefined) {
|
|
28
|
+
const fakeConfig = { ssr: { external: ssrExternal } } as any;
|
|
29
|
+
(plugin.configResolved as any)(fakeConfig);
|
|
30
|
+
return fakeConfig.ssr.external;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
// Native fix registration is global and deduped — reset between tests.
|
|
35
|
+
delete globalWithNativePlugins.__vxrnAddNativePlugins;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("ssrCjsExternal", () => {
|
|
39
|
+
it("appends the default CJS-only ids to ssr.external after one() replaces the list", () => {
|
|
40
|
+
const config = createViteConfig({ one: true });
|
|
41
|
+
const plugin = findPlugin(config, "multiplatform-ssr-cjs-external-fix");
|
|
42
|
+
expect(plugin).toBeDefined();
|
|
43
|
+
// Simulate one() having replaced ssr.external with its own list.
|
|
44
|
+
const external = runConfigResolved(plugin!, ["one-framework-entry"]) as string[];
|
|
45
|
+
expect(external).toContain("one-framework-entry");
|
|
46
|
+
expect(external).toContain("use-sync-external-store");
|
|
47
|
+
expect(external).toContain("use-sync-external-store/shim/with-selector");
|
|
48
|
+
expect(external).toContain("highlight.js/lib/core");
|
|
49
|
+
expect(external).toContain("turndown");
|
|
50
|
+
expect(external).toContain("@mixmark-io/domino");
|
|
51
|
+
expect(external).toContain("qrcode");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("does not duplicate ids already present", () => {
|
|
55
|
+
const config = createViteConfig({ one: true });
|
|
56
|
+
const plugin = findPlugin(config, "multiplatform-ssr-cjs-external-fix");
|
|
57
|
+
const external = runConfigResolved(plugin!, ["use-sync-external-store"]) as string[];
|
|
58
|
+
expect(external.filter((e) => e === "use-sync-external-store")).toHaveLength(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("extends the default list with consumer extras", () => {
|
|
62
|
+
const config = createViteConfig({ one: true, ssrCjsExternal: ["my-cjs-dep"] });
|
|
63
|
+
const plugin = findPlugin(config, "multiplatform-ssr-cjs-external-fix");
|
|
64
|
+
const external = runConfigResolved(plugin!, []) as string[];
|
|
65
|
+
expect(external).toContain("my-cjs-dep");
|
|
66
|
+
expect(external).toContain("use-sync-external-store");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("leaves ssr.external: true untouched (everything already external)", () => {
|
|
70
|
+
const config = createViteConfig({ one: true });
|
|
71
|
+
const plugin = findPlugin(config, "multiplatform-ssr-cjs-external-fix");
|
|
72
|
+
expect(runConfigResolved(plugin!, true)).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("can be disabled with ssrCjsExternal: false", () => {
|
|
76
|
+
const config = createViteConfig({ one: true, ssrCjsExternal: false });
|
|
77
|
+
expect(findPlugin(config, "multiplatform-ssr-cjs-external-fix")).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("is not added without the one framework", () => {
|
|
81
|
+
const config = createViteConfig({ one: false });
|
|
82
|
+
expect(findPlugin(config, "multiplatform-ssr-cjs-external-fix")).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("nativeFixes.protoShadow", () => {
|
|
87
|
+
it("registers the Hermes proto-shadow renderChunk fix by default", () => {
|
|
88
|
+
createViteConfig({ one: true });
|
|
89
|
+
const plugin = globalWithNativePlugins.__vxrnAddNativePlugins?.find(
|
|
90
|
+
(p) => p.name === "vxrn-proto-shadow-fix",
|
|
91
|
+
);
|
|
92
|
+
expect(plugin).toBeDefined();
|
|
93
|
+
const result = plugin!.renderChunk!("var x = 1;");
|
|
94
|
+
// The restore snippet is PREPENDED so it runs before any module code.
|
|
95
|
+
expect(result.code.endsWith("var x = 1;")).toBe(true);
|
|
96
|
+
expect(result.code).toContain("Object.prototype");
|
|
97
|
+
expect(result.code).toContain("hasOwnProperty");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("can be disabled with nativeFixes.protoShadow: false", () => {
|
|
101
|
+
createViteConfig({ one: true, nativeFixes: { protoShadow: false } });
|
|
102
|
+
expect(
|
|
103
|
+
globalWithNativePlugins.__vxrnAddNativePlugins?.some(
|
|
104
|
+
(p) => p.name === "vxrn-proto-shadow-fix",
|
|
105
|
+
),
|
|
106
|
+
).toBeFalsy();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("nativeFixes.web3Stub", () => {
|
|
111
|
+
it("auto-skips when @multiplatform.one/web3 is installed (monorepo has it)", () => {
|
|
112
|
+
createViteConfig({ one: true });
|
|
113
|
+
expect(
|
|
114
|
+
globalWithNativePlugins.__vxrnAddNativePlugins?.some((p) => p.name === "vxrn-web3-stub"),
|
|
115
|
+
).toBeFalsy();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("public config bake", () => {
|
|
120
|
+
const bakedKeys = "VITE_MP_PUBLIC_CONFIG_KEYS";
|
|
121
|
+
const bakedValues = "VITE_MP_CONFIG";
|
|
122
|
+
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
delete process.env[bakedKeys];
|
|
125
|
+
delete process.env[bakedValues];
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("bakes the public key NAMES so the SSR server can resolve their values at runtime", () => {
|
|
129
|
+
// Values stay out of the image (an environment-agnostic build); the names
|
|
130
|
+
// travel instead, and double as the allowlist that keeps a private key out
|
|
131
|
+
// of the served document (@multiplatform.one/platform runtimeConfig).
|
|
132
|
+
createViteConfig({ publicConfigKeys: ["MARKETPLACE_API_URL", "FRAPPE_ENABLED"] });
|
|
133
|
+
expect(JSON.parse(process.env[bakedKeys]!)).toEqual(["MARKETPLACE_API_URL", "FRAPPE_ENABLED"]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("still bakes the build-environment VALUES so apps that bake keep working", () => {
|
|
137
|
+
process.env.MPO_VITE_SPEC_PUBLIC = "baked-in";
|
|
138
|
+
createViteConfig({ publicConfigKeys: ["MPO_VITE_SPEC_PUBLIC"] });
|
|
139
|
+
expect(JSON.parse(process.env[bakedValues]!)).toEqual({ MPO_VITE_SPEC_PUBLIC: "baked-in" });
|
|
140
|
+
delete process.env.MPO_VITE_SPEC_PUBLIC;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("bakes nothing when the app declares no public keys", () => {
|
|
144
|
+
createViteConfig({});
|
|
145
|
+
expect(process.env[bakedKeys]).toBeUndefined();
|
|
146
|
+
expect(process.env[bakedValues]).toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
});
|
package/src/vite.ts
CHANGED
|
@@ -114,6 +114,41 @@ export interface CreateViteConfigOptions {
|
|
|
114
114
|
* Only used when `one` is enabled.
|
|
115
115
|
*/
|
|
116
116
|
ssrEntries?: string[];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* CJS-only package ids force-appended to `ssr.external` AFTER the one()
|
|
120
|
+
* plugin replaces the list (via configResolved), so Node's native CJS
|
|
121
|
+
* loader handles them instead of Vite's ESM evaluator (which chokes on
|
|
122
|
+
* `module.exports` with "module is not defined"). Only used when `one` is
|
|
123
|
+
* enabled. Defaults to a list covering use-sync-external-store,
|
|
124
|
+
* highlight.js, turndown, and friends; pass an array to EXTEND the default
|
|
125
|
+
* list or `false` to disable entirely.
|
|
126
|
+
*/
|
|
127
|
+
ssrCjsExternal?: string[] | false;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Native (vxrn rolldown / Hermes) workaround toggles. Only used when `one`
|
|
131
|
+
* is enabled; all default to true.
|
|
132
|
+
*/
|
|
133
|
+
nativeFixes?: {
|
|
134
|
+
/**
|
|
135
|
+
* Prepend a snippet to the classic-script native bundle that restores
|
|
136
|
+
* Object.prototype methods shadowed by hoisted top-level vars (Hermes V1
|
|
137
|
+
* spec-compliant var hoisting turns a flattened module's
|
|
138
|
+
* `var hasOwnProperty` into `globalThis.hasOwnProperty = undefined` at
|
|
139
|
+
* parse time, and React Native core dies at boot calling it).
|
|
140
|
+
*/
|
|
141
|
+
protoShadow?: boolean;
|
|
142
|
+
/**
|
|
143
|
+
* When `@multiplatform.one/web3` is NOT installed, redirect the OPTIONAL
|
|
144
|
+
* `@multiplatform.one/web3/native` import (from core's
|
|
145
|
+
* loadWalletProvider.native.ts) to a no-op shim — rolldown otherwise
|
|
146
|
+
* leaves the unresolvable specifier as a bare dynamic `import()` in the
|
|
147
|
+
* classic-script bundle, which Hermes rejects at lazy-compile time.
|
|
148
|
+
* Auto-skipped when web3 is installed.
|
|
149
|
+
*/
|
|
150
|
+
web3Stub?: boolean;
|
|
151
|
+
};
|
|
117
152
|
}
|
|
118
153
|
|
|
119
154
|
/**
|
|
@@ -150,6 +185,8 @@ export function createViteConfig(options: CreateViteConfigOptions = {}): UserCon
|
|
|
150
185
|
externalReactRouter,
|
|
151
186
|
ssrDeps,
|
|
152
187
|
ssrEntries,
|
|
188
|
+
ssrCjsExternal,
|
|
189
|
+
nativeFixes,
|
|
153
190
|
} = options;
|
|
154
191
|
|
|
155
192
|
const projectRoot = _projectRoot || findProjectRoot();
|
|
@@ -158,6 +195,13 @@ export function createViteConfig(options: CreateViteConfigOptions = {}): UserCon
|
|
|
158
195
|
dotenv.config({ path: path.resolve(projectRoot, ".env") });
|
|
159
196
|
if (publicConfigKeys.length > 0) {
|
|
160
197
|
process.env.VITE_MP_CONFIG = JSON.stringify(resolveConfig(publicConfigKeys));
|
|
198
|
+
// Bake only the NAMES of the public keys, never a value: this is what lets
|
|
199
|
+
// the SSR node process resolve their values from ITS OWN process.env at
|
|
200
|
+
// request time and publish them into the served document
|
|
201
|
+
// (@multiplatform.one/platform runtimeConfig.ts). Values stay out of the
|
|
202
|
+
// image, so one build is deployable to every environment; the names are
|
|
203
|
+
// also the allowlist that keeps a private key from ever being serialized.
|
|
204
|
+
process.env.VITE_MP_PUBLIC_CONFIG_KEYS = JSON.stringify(publicConfigKeys);
|
|
161
205
|
}
|
|
162
206
|
|
|
163
207
|
const vitePlugins: UserConfig["plugins"] = [];
|
|
@@ -189,6 +233,19 @@ export function createViteConfig(options: CreateViteConfigOptions = {}): UserCon
|
|
|
189
233
|
// react-native-svg, so source keeps importing @phosphor-icons/react and
|
|
190
234
|
// native transparently gets the RN implementation.
|
|
191
235
|
registerNativePhosphorIcons();
|
|
236
|
+
// Hermes V1 hoisted-var Object.prototype shadow fix (see the nativeFixes
|
|
237
|
+
// option docs). Every rolldown classic-script native bundle is exposed.
|
|
238
|
+
if (nativeFixes?.protoShadow !== false) registerNativeProtoShadowFix();
|
|
239
|
+
// Optional-web3 stub for apps that don't install @multiplatform.one/web3
|
|
240
|
+
// (auto-skipped when the package resolves).
|
|
241
|
+
if (nativeFixes?.web3Stub !== false) registerNativeWeb3Stub();
|
|
242
|
+
// CJS-only ids must land on ssr.external AFTER the one() plugin replaces
|
|
243
|
+
// the list during config — configResolved runs last, so the appends stick.
|
|
244
|
+
if (ssrCjsExternal !== false) {
|
|
245
|
+
vitePlugins.push(
|
|
246
|
+
ssrCjsExternalFixPlugin([...DEFAULT_SSR_CJS_EXTERNAL, ...(ssrCjsExternal ?? [])]),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
192
249
|
}
|
|
193
250
|
|
|
194
251
|
// Tamagui plugin
|
|
@@ -571,6 +628,132 @@ function registerNativeWorkspaceEntries(): void {
|
|
|
571
628
|
});
|
|
572
629
|
}
|
|
573
630
|
|
|
631
|
+
/**
|
|
632
|
+
* CJS-only ids (no usable ESM build) that escape Vite's SSR dep optimizer and
|
|
633
|
+
* hit the ESM evaluator ("module is not defined" / "exports is not defined")
|
|
634
|
+
* unless Node's native CJS loader handles them. Union of the lists the
|
|
635
|
+
* monorepo's apps/one and downstream consumer apps (desibox) carried as
|
|
636
|
+
* app-side ssrExternalFix plugins; ids for packages an app never imports are
|
|
637
|
+
* inert, so one shared default list is safe.
|
|
638
|
+
*/
|
|
639
|
+
const DEFAULT_SSR_CJS_EXTERNAL = [
|
|
640
|
+
"better-sqlite3",
|
|
641
|
+
"dotenv",
|
|
642
|
+
"color",
|
|
643
|
+
"use-sync-external-store",
|
|
644
|
+
"use-sync-external-store/shim",
|
|
645
|
+
"use-sync-external-store/shim/with-selector",
|
|
646
|
+
"highlight.js",
|
|
647
|
+
"highlight.js/lib/core",
|
|
648
|
+
"turndown",
|
|
649
|
+
"@mixmark-io/domino",
|
|
650
|
+
"lowlight",
|
|
651
|
+
// CJS-only (module.exports / exports, no usable ESM build) pulled in by
|
|
652
|
+
// connectkit/wagmi/framer-motion.
|
|
653
|
+
"shallowequal",
|
|
654
|
+
"@emotion/is-prop-valid",
|
|
655
|
+
"hoist-non-react-statics",
|
|
656
|
+
"qrcode",
|
|
657
|
+
];
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Force-append CJS-only ids to `ssr.external`. The one() plugin REPLACES
|
|
661
|
+
* ssr.external with its own list during the config hook, dropping anything
|
|
662
|
+
* set statically by createViteConfig — configResolved runs after all config
|
|
663
|
+
* hooks are merged, so appends here survive.
|
|
664
|
+
*/
|
|
665
|
+
function ssrCjsExternalFixPlugin(extras: string[]): Plugin {
|
|
666
|
+
return {
|
|
667
|
+
name: "multiplatform-ssr-cjs-external-fix",
|
|
668
|
+
configResolved(config) {
|
|
669
|
+
const external = config.ssr.external ?? [];
|
|
670
|
+
if (!Array.isArray(external)) return; // true = everything external already
|
|
671
|
+
for (const e of extras) {
|
|
672
|
+
if (!external.includes(e)) external.push(e);
|
|
673
|
+
}
|
|
674
|
+
(config.ssr as { external?: string[] | boolean }).external = external;
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Restore Object.prototype methods shadowed by hoisted globals on NATIVE.
|
|
681
|
+
*
|
|
682
|
+
* vxrn's rolldown native mode emits the whole dev bundle as ONE classic
|
|
683
|
+
* script and forces Hermes V1. Hermes V1 implements spec-compliant global
|
|
684
|
+
* var hoisting, so a flattened module's top-level `var hasOwnProperty`
|
|
685
|
+
* (e.g. acorn via an mdx pipeline) creates
|
|
686
|
+
* `globalThis.hasOwnProperty = undefined` at parse time, shadowing
|
|
687
|
+
* Object.prototype.hasOwnProperty for the entire runtime. React Native core
|
|
688
|
+
* calls `global.hasOwnProperty()` during boot (FuseboxSessionObserver) and
|
|
689
|
+
* dies with "undefined is not a function". Prepend a snippet that copies any
|
|
690
|
+
* Object.prototype method back over an undefined shadowing own-property
|
|
691
|
+
* before any module code runs — the shadowed vars are reassigned by their
|
|
692
|
+
* own modules on init, so this only bridges the parse-to-init window.
|
|
693
|
+
* Registered through globalThis.__vxrnAddNativePlugins (vxrn's native engine
|
|
694
|
+
* ignores vite resolve/config). Web/SSR bundles are ESM (module-scoped vars)
|
|
695
|
+
* and unaffected.
|
|
696
|
+
*/
|
|
697
|
+
function registerNativeProtoShadowFix(): void {
|
|
698
|
+
const g = globalThis as unknown as { __vxrnAddNativePlugins?: unknown[] };
|
|
699
|
+
const name = "vxrn-proto-shadow-fix";
|
|
700
|
+
g.__vxrnAddNativePlugins = g.__vxrnAddNativePlugins ?? [];
|
|
701
|
+
if (g.__vxrnAddNativePlugins.some((p) => (p as { name?: string })?.name === name)) return;
|
|
702
|
+
const snippet =
|
|
703
|
+
`(function(){var p=Object.prototype,h=p.hasOwnProperty,ns=Object.getOwnPropertyNames(p);` +
|
|
704
|
+
`for(var i=0;i<ns.length;i++){var k=ns[i];` +
|
|
705
|
+
`if(typeof p[k]==="function"&&globalThis[k]===void 0&&h.call(globalThis,k)){` +
|
|
706
|
+
`try{globalThis[k]=p[k]}catch(e){}}}})();`;
|
|
707
|
+
g.__vxrnAddNativePlugins.push({
|
|
708
|
+
name,
|
|
709
|
+
renderChunk(code: string) {
|
|
710
|
+
return { code: `${snippet}\n${code}`, map: null };
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Redirect the OPTIONAL `@multiplatform.one/web3` native import to a no-op
|
|
717
|
+
* shim on NATIVE when the package is not installed (see the nativeFixes
|
|
718
|
+
* option docs). Rolldown leaves the unresolvable specifier as a bare dynamic
|
|
719
|
+
* `import()` in the classic-script native bundle — Hermes rejects that at
|
|
720
|
+
* lazy-compile time ("SyntaxError: Invalid expression encountered"), killing
|
|
721
|
+
* the root _layout route. Web/SSR resolve the same specifier through vite's
|
|
722
|
+
* normal pipeline and are unaffected.
|
|
723
|
+
*/
|
|
724
|
+
function registerNativeWeb3Stub(): void {
|
|
725
|
+
const g = globalThis as unknown as { __vxrnAddNativePlugins?: unknown[] };
|
|
726
|
+
const name = "vxrn-web3-stub";
|
|
727
|
+
g.__vxrnAddNativePlugins = g.__vxrnAddNativePlugins ?? [];
|
|
728
|
+
if (g.__vxrnAddNativePlugins.some((p) => (p as { name?: string })?.name === name)) return;
|
|
729
|
+
try {
|
|
730
|
+
// web3 installed — its real native entry resolves; no stub needed.
|
|
731
|
+
require.resolve("@multiplatform.one/web3", { paths: [findProjectRoot(), process.cwd()] });
|
|
732
|
+
return;
|
|
733
|
+
} catch {
|
|
734
|
+
// not installed — stub below
|
|
735
|
+
}
|
|
736
|
+
// The shim ships as plain JS (vxrn does not transform node_modules TS).
|
|
737
|
+
// Resolve it relative to this module: `src/shims/` when running from
|
|
738
|
+
// source, `../src/shims/` from the built `lib/` bundle (src ships in the
|
|
739
|
+
// published tarball).
|
|
740
|
+
const here = path.dirname(new URL(import.meta.url).pathname);
|
|
741
|
+
const shim = [
|
|
742
|
+
path.resolve(here, "shims/web3Stub.native.js"),
|
|
743
|
+
path.resolve(here, "../src/shims/web3Stub.native.js"),
|
|
744
|
+
].find((candidate) => fs.existsSync(candidate));
|
|
745
|
+
if (!shim) return;
|
|
746
|
+
g.__vxrnAddNativePlugins.push({
|
|
747
|
+
name,
|
|
748
|
+
resolveId(id: string) {
|
|
749
|
+
if (id === "@multiplatform.one/web3/native" || id === "@multiplatform.one/web3") {
|
|
750
|
+
return shim;
|
|
751
|
+
}
|
|
752
|
+
return null;
|
|
753
|
+
},
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
574
757
|
function findProjectRoot(): string {
|
|
575
758
|
try {
|
|
576
759
|
const { execSync } = require("node:child_process");
|
package/types/storybook.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storybook.d.ts","sourceRoot":"","sources":["../src/storybook.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"storybook.d.ts","sourceRoot":"","sources":["../src/storybook.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,MAAM,CAAC;AAI/C,MAAM,WAAW,gCAAgC;IAC/C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAEhC;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,gCAAqC,GAC7C,UAAU,CA0HZ"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite `resolve.alias` entries that let known-undeclared package internals
|
|
3
|
+
* resolve by filesystem path.
|
|
4
|
+
*
|
|
5
|
+
* Scoped to one subtree of one package: Vite alias keys match the whole
|
|
6
|
+
* specifier or a `key + "/"` prefix, so sibling paths (`lib/moduleOther`) and
|
|
7
|
+
* the package's own entry (`@react-navigation/core`) keep going through the
|
|
8
|
+
* `exports` map. Nothing here disables exports enforcement globally.
|
|
9
|
+
*
|
|
10
|
+
* Entries are omitted when the package (or the target directory) is absent, so
|
|
11
|
+
* consumers that do not install `one`/react-navigation are unaffected.
|
|
12
|
+
*
|
|
13
|
+
* @param root Directory whose `node_modules` the packages resolve from.
|
|
14
|
+
*/
|
|
15
|
+
export declare function unexportedDepAliases(root?: string): Record<string, string>;
|
|
16
|
+
//# sourceMappingURL=unexportedDepAliases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unexportedDepAliases.d.ts","sourceRoot":"","sources":["../src/unexportedDepAliases.ts"],"names":[],"mappings":"AA6BA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,MAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAazF"}
|