@felipe-software/react-native-gpui 0.0.0-stage → 1.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.
- package/.reanimated-prebuilt/react-native-reanimated.mjs +29373 -0
- package/LICENSE +21 -0
- package/README.md +657 -2
- package/cli/bin.ts +379 -0
- package/cli/bundle.ts +32 -0
- package/cli/commands/do.ts +213 -0
- package/cli/commands/flow.ts +283 -0
- package/cli/commands/get.ts +584 -0
- package/cli/commands/shot.ts +312 -0
- package/cli/commands/trace.ts +293 -0
- package/cli/host.ts +714 -0
- package/cli/hotReload.ts +210 -0
- package/cli/run.ts +66 -0
- package/cli/selectors.ts +205 -0
- package/dist/Animated.d.ts +120 -0
- package/dist/ChatList.d.ts +28 -0
- package/dist/Dimensions.d.ts +31 -0
- package/dist/StyleSheet.d.ts +18 -0
- package/dist/Window.d.ts +33 -0
- package/dist/apis.d.ts +165 -0
- package/dist/app-state.d.ts +27 -0
- package/dist/chat-list-types.d.ts +136 -0
- package/dist/colors.d.ts +32 -0
- package/dist/commands.d.ts +230 -0
- package/dist/components.d.ts +419 -0
- package/dist/hotModules.d.ts +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.development.js +14876 -0
- package/dist/index.js +12 -0
- package/dist/keyboard.d.ts +140 -0
- package/dist/native-menu.d.ts +7 -0
- package/dist/platform-driver.d.ts +32 -0
- package/dist/platform-services.d.ts +35 -0
- package/dist/portal.d.ts +43 -0
- package/dist/raf.d.ts +4 -0
- package/dist/reanimated/colors-processor-shim.d.ts +10 -0
- package/dist/reanimated/reanimated-host.d.ts +10 -0
- package/dist/reanimated/seam.d.ts +34 -0
- package/dist/reanimated/ui-entry.d.ts +2 -0
- package/dist/reanimated/worklet-channel.d.ts +10 -0
- package/dist/reanimated/worklet-runtime.d.ts +466 -0
- package/dist/reanimated/worklets.d.ts +199 -0
- package/dist/reconciler.d.ts +102 -0
- package/dist/refresh.d.ts +1 -0
- package/dist/render.d.ts +37 -0
- package/dist/runtime.d.ts +235 -0
- package/dist/surfaces.d.ts +51 -0
- package/dist/types.d.ts +276 -0
- package/dist/ui-runtime.js +42276 -0
- package/dist/window-context.d.ts +7 -0
- package/dist/window-host.d.ts +41 -0
- package/dist/wire-delta.d.ts +20 -0
- package/package.json +176 -5
- package/scripts/bundle-app.mjs +88 -0
- package/scripts/conformance-utils.mjs +293 -0
- package/scripts/hot-update-alias-plugin.mjs +88 -0
- package/scripts/native-package-exports-plugin.mjs +235 -0
- package/scripts/pixel.mjs +152 -0
- package/scripts/png.mjs +198 -0
- package/scripts/react-refresh-swc.mjs +35 -0
- package/scripts/reanimated-bun-plugin.mjs +188 -0
- package/scripts/service-binary.d.mts +27 -0
- package/scripts/service-binary.mjs +100 -0
- package/src/reanimated/colors-processor-shim.ts +119 -0
- package/src/reanimated/reanimated-host.ts +69 -0
- package/src/reanimated/rngpui-reanimated-prebuilt.d.ts +7 -0
- package/src/reanimated/seam.ts +566 -0
- package/src/reanimated/ui-entry.ts +80 -0
- package/src/reanimated/worklet-channel.ts +64 -0
- package/src/reanimated/worklet-runtime.ts +2569 -0
- package/src/reanimated/worklets.ts +1334 -0
- package/src/refresh.ts +33 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
|
|
3
|
+
export function createReactRefreshSwcTransform() {
|
|
4
|
+
const require = createRequire(import.meta.url)
|
|
5
|
+
const swc = require('@swc/core')
|
|
6
|
+
|
|
7
|
+
return async function transformReactRefresh(source, { filename, isTs, isJsx }) {
|
|
8
|
+
const result = await swc.transform(source, {
|
|
9
|
+
filename,
|
|
10
|
+
sourceMaps: false,
|
|
11
|
+
jsc: {
|
|
12
|
+
target: 'es2022',
|
|
13
|
+
parser: isTs
|
|
14
|
+
? { syntax: 'typescript', tsx: isJsx }
|
|
15
|
+
: { syntax: 'ecmascript', jsx: isJsx },
|
|
16
|
+
transform: {
|
|
17
|
+
react: {
|
|
18
|
+
runtime: 'automatic',
|
|
19
|
+
development: true,
|
|
20
|
+
refresh: {
|
|
21
|
+
refreshReg: '$RNGPUIRefreshReg$',
|
|
22
|
+
refreshSig: '$RefreshSig$',
|
|
23
|
+
emitFullSignatures: false,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
module: {
|
|
29
|
+
type: 'es6',
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
const moduleId = JSON.stringify(filename)
|
|
33
|
+
return `const $RNGPUIRefreshReg$ = (type, id) => globalThis.$RefreshReg$(type, ${moduleId} + " " + id);\n${result.code}`
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Bun-build plugin: wire real react-native-reanimated@4 + react-native-worklets +
|
|
2
|
+
// @tamagui/animations-reanimated for the embedded JavaScriptCore target.
|
|
3
|
+
//
|
|
4
|
+
// HOW IT WORKS (the winning approach — see scripts/prebuild-reanimated.mjs):
|
|
5
|
+
// reanimated's `lib/module` re-export-barrel graph trips Bun's bundler into a
|
|
6
|
+
// tree-shake-then-dangle bug (`import_xN is not defined` at eval). ESBUILD bundles the
|
|
7
|
+
// same graph correctly. So we PRE-BUNDLE reanimated (and @tamagui/animations-reanimated)
|
|
8
|
+
// into self-contained ESM chunks with esbuild (worklets babel transform + native-seam
|
|
9
|
+
// redirect baked in), keeping `react` / `react-native-worklets` / `react-native-gpui`
|
|
10
|
+
// external. This plugin then simply ALIASES:
|
|
11
|
+
// react-native-reanimated → .reanimated-prebuilt/react-native-reanimated.mjs
|
|
12
|
+
// @tamagui/animations-reanimated → .reanimated-prebuilt/tamagui-animations-reanimated.mjs
|
|
13
|
+
// react-native-worklets → ts/src/reanimated/worklets.ts (cross-runtime adapter,
|
|
14
|
+
// which force-imports the seam → installs the globals)
|
|
15
|
+
// Bun then just includes those single files — no deep tree-shake of reanimated's graph.
|
|
16
|
+
//
|
|
17
|
+
// gpui runs separate React and UI JavaScriptCore runtimes. Worklets and shared values cross
|
|
18
|
+
// through the adapter, and `global._updateProps` is the off-thread fast path
|
|
19
|
+
// (→ `__rngpui_setNodeStyle` → Rust animated-style overlay → cx.notify without a React
|
|
20
|
+
// re-commit). The prebuilt chunks contain the real reanimated + Tamagui driver; the seam
|
|
21
|
+
// and worklets adapter provide the thin native seam.
|
|
22
|
+
//
|
|
23
|
+
// Run `bun scripts/prebuild-reanimated.mjs` whenever react-native-reanimated or
|
|
24
|
+
// @tamagui/animations-reanimated changes (or on postinstall). If the prebuilt chunk is
|
|
25
|
+
// missing, this plugin throws with that instruction.
|
|
26
|
+
//
|
|
27
|
+
// Usage:
|
|
28
|
+
// import { reanimatedBunPlugin } from '<rng>/ts/scripts/reanimated-bun-plugin.mjs'
|
|
29
|
+
// plugins: [reanimatedBunPlugin({ rngTsRoot })]
|
|
30
|
+
// rngTsRoot — absolute path to react-native-gpui/ts (seam/worklets/prebuilt live here)
|
|
31
|
+
// prebuiltDir — override the prebuilt chunk dir (default: <rngTsRoot>/.reanimated-prebuilt)
|
|
32
|
+
|
|
33
|
+
import { resolve, sep } from 'node:path'
|
|
34
|
+
import { existsSync } from 'node:fs'
|
|
35
|
+
import { createRequire } from 'node:module'
|
|
36
|
+
|
|
37
|
+
const LIBRARY_SPECIFIER = /^(?:@felipe-software\/)?react-native-gpui$/
|
|
38
|
+
const BABEL_PACKAGES = '@babel/core @babel/plugin-syntax-jsx @babel/preset-typescript react-native-worklets@0.8.1'
|
|
39
|
+
|
|
40
|
+
function defaultLibraryEntry(rngTsRoot) {
|
|
41
|
+
const source = resolve(rngTsRoot, 'src/index.ts')
|
|
42
|
+
return existsSync(source) ? source : resolve(rngTsRoot, 'dist/index.js')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// worklet keywords — gate the babel transform so we only pay for files that use them.
|
|
46
|
+
const WORKLET_KEYWORDS =
|
|
47
|
+
/worklet|useAnimatedStyle|useAnimatedProps|useDerivedValue|useAnimatedReaction|useAnimatedGestureHandler|useAnimatedScrollHandler|useFrameCallback|useWorkletCallback|withTiming|withSpring|withDecay|withDelay|withRepeat|runOnUI|createAnimatedPropAdapter/
|
|
48
|
+
|
|
49
|
+
export function reanimatedBunPlugin(opts = {}) {
|
|
50
|
+
const rngTsRoot = opts.rngTsRoot
|
|
51
|
+
if (!rngTsRoot) throw new Error('reanimatedBunPlugin: rngTsRoot is required')
|
|
52
|
+
const resolveRoot = opts.resolveRoot || rngTsRoot
|
|
53
|
+
const workletsPath = resolve(rngTsRoot, 'src/reanimated/worklets.ts')
|
|
54
|
+
const seamPath = resolve(rngTsRoot, 'src/reanimated/seam.ts')
|
|
55
|
+
const reanimatedHostPath = resolve(rngTsRoot, 'src/reanimated/reanimated-host.ts')
|
|
56
|
+
const prebuiltDir = opts.prebuiltDir || resolve(rngTsRoot, '.reanimated-prebuilt')
|
|
57
|
+
const reanimatedChunk = resolve(prebuiltDir, 'react-native-reanimated.mjs')
|
|
58
|
+
const tamaguiReanimatedChunk = resolve(prebuiltDir, 'tamagui-animations-reanimated.mjs')
|
|
59
|
+
// the prebuilt chunks emit external `import { View } from "react-native"`; resolve
|
|
60
|
+
// `react-native` → the gpui RN-compatible index. When the host bundler already aliases
|
|
61
|
+
// it (the gui app does), pass aliasReactNative:false.
|
|
62
|
+
const libraryEntry = opts.libraryEntry || defaultLibraryEntry(rngTsRoot)
|
|
63
|
+
const reactNativeIndex = opts.reactNativeIndex || libraryEntry
|
|
64
|
+
const aliasReactNative = opts.aliasReactNative !== false
|
|
65
|
+
const transformSource = opts.transformSource
|
|
66
|
+
const libraryBuildDir = resolve(rngTsRoot, 'dist') + sep
|
|
67
|
+
|
|
68
|
+
// babel worklets transform — applied to APP + Tamagui-driver source (NOT the prebuilt
|
|
69
|
+
// reanimated chunk, which already ran it, and NOT reanimated node_modules). This is
|
|
70
|
+
// REQUIRED: `useAnimatedStyle`/`useDerivedValue` read `updater.__closure` to extract
|
|
71
|
+
// the SharedValues a worklet depends on (its mapper inputs); without the babel lift
|
|
72
|
+
// the closure is empty, the mapper subscribes to nothing, a `sv.value = …` write never
|
|
73
|
+
// re-runs the mapper, and a Tamagui spring never advances (snaps to its target). The
|
|
74
|
+
// lifted function carries `this.__closure` in `__initData.code` so the UI runtime can
|
|
75
|
+
// deserialize the mapper with the SharedValues it captured.
|
|
76
|
+
const pluginRequires = [...new Set([resolveRoot, rngTsRoot])].map((dir) => createRequire(resolve(dir, 'package.json')))
|
|
77
|
+
let babel = null
|
|
78
|
+
let workletsPluginPath = null
|
|
79
|
+
let jsxSyntax = null
|
|
80
|
+
let presetTs = null
|
|
81
|
+
const loadBabel = (filename) => {
|
|
82
|
+
if (babel) return
|
|
83
|
+
for (const pluginRequire of pluginRequires) {
|
|
84
|
+
try {
|
|
85
|
+
workletsPluginPath = pluginRequire.resolve('react-native-worklets/plugin')
|
|
86
|
+
jsxSyntax = pluginRequire.resolve('@babel/plugin-syntax-jsx')
|
|
87
|
+
presetTs = pluginRequire.resolve('@babel/preset-typescript')
|
|
88
|
+
babel = pluginRequire('@babel/core')
|
|
89
|
+
return
|
|
90
|
+
} catch {}
|
|
91
|
+
}
|
|
92
|
+
throw new Error(
|
|
93
|
+
`[reanimated] ${filename} uses worklets, which need the worklets Babel transform.\n` +
|
|
94
|
+
` install: bun add -d ${BABEL_PACKAGES}`,
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
name: 'reanimated prebuilt chunks + worklets seam',
|
|
100
|
+
setup(build) {
|
|
101
|
+
if (aliasReactNative) {
|
|
102
|
+
build.onResolve({ filter: /^react-native$/ }, () => ({ path: reactNativeIndex }))
|
|
103
|
+
build.onResolve({ filter: LIBRARY_SPECIFIER }, () => ({ path: libraryEntry }))
|
|
104
|
+
}
|
|
105
|
+
// react-native-worklets (bare + any subpath) → single-runtime stub. The stub
|
|
106
|
+
// force-imports the seam, which installs reanimated's globals
|
|
107
|
+
// (__reanimatedModuleProxy, global._updateProps, __RUNTIME_KIND=2, RN$Bridgeless)
|
|
108
|
+
// before the reanimated chunk evaluates.
|
|
109
|
+
build.onResolve({ filter: /^react-native-worklets(\/.*)?$/ }, () => ({ path: workletsPath }))
|
|
110
|
+
|
|
111
|
+
// react-native-reanimated → the rngpui host wrapper (stamps the animation
|
|
112
|
+
// factories for the off-thread worklet runtime — see reanimated-host.ts),
|
|
113
|
+
// which itself imports the esbuild-prebuilt chunk via the virtual id below.
|
|
114
|
+
build.onResolve({ filter: /^react-native-reanimated$/ }, () => {
|
|
115
|
+
if (!existsSync(reanimatedChunk)) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`[reanimated] prebuilt chunk missing: ${reanimatedChunk}\n` +
|
|
118
|
+
` run: bun scripts/prebuild-reanimated.mjs`,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
return { path: reanimatedHostPath }
|
|
122
|
+
})
|
|
123
|
+
build.onResolve({ filter: /^rngpui-reanimated-prebuilt$/ }, () => ({ path: reanimatedChunk }))
|
|
124
|
+
|
|
125
|
+
// @tamagui/animations-reanimated → the esbuild-prebuilt chunk (when present).
|
|
126
|
+
build.onResolve({ filter: /^@tamagui\/animations-reanimated$/ }, () => {
|
|
127
|
+
if (existsSync(tamaguiReanimatedChunk)) return { path: tamaguiReanimatedChunk }
|
|
128
|
+
// not prebuilt (e.g. a bundle that doesn't use the Tamagui reanimated driver) —
|
|
129
|
+
// let it resolve normally; if the app actually imports it, that surfaces a clear
|
|
130
|
+
// resolve error pointing at the missing chunk.
|
|
131
|
+
return undefined
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// worklet transform on APP + Tamagui-driver source (see the comment above).
|
|
135
|
+
build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async (args) => {
|
|
136
|
+
// skip: the prebuilt chunks (already transformed), the seam/worklets stub, and
|
|
137
|
+
// anything inside reanimated/worklets node_modules (the prebuild owns those).
|
|
138
|
+
if (
|
|
139
|
+
args.path.startsWith(prebuiltDir) ||
|
|
140
|
+
args.path === seamPath ||
|
|
141
|
+
args.path === workletsPath ||
|
|
142
|
+
/node_modules\/(react-native-reanimated|react-native-worklets)\//.test(args.path)
|
|
143
|
+
) {
|
|
144
|
+
return undefined
|
|
145
|
+
}
|
|
146
|
+
const source = await Bun.file(args.path).text()
|
|
147
|
+
let code = source
|
|
148
|
+
let transformed = false
|
|
149
|
+
const isTsx = args.path.endsWith('.tsx')
|
|
150
|
+
const isTs = /\.(tsx?|mts|cts)$/.test(args.path)
|
|
151
|
+
const isLibraryBuild = args.path.startsWith(libraryBuildDir)
|
|
152
|
+
if (!isLibraryBuild && WORKLET_KEYWORDS.test(source)) {
|
|
153
|
+
loadBabel(args.path)
|
|
154
|
+
const result = await babel.transformAsync(source, {
|
|
155
|
+
filename: args.path,
|
|
156
|
+
babelrc: false,
|
|
157
|
+
configFile: false,
|
|
158
|
+
sourceMaps: false,
|
|
159
|
+
presets: isTs
|
|
160
|
+
? [[presetTs, { isTSX: isTsx, allExtensions: isTsx, allowDeclareFields: true }]]
|
|
161
|
+
: [],
|
|
162
|
+
plugins: [[jsxSyntax], [workletsPluginPath, { processNestedWorklets: true }]],
|
|
163
|
+
})
|
|
164
|
+
if (result?.code) {
|
|
165
|
+
code = result.code
|
|
166
|
+
transformed = true
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (transformSource) {
|
|
170
|
+
const result = await transformSource(code, {
|
|
171
|
+
filename: args.path,
|
|
172
|
+
isTs: transformed ? false : isTs,
|
|
173
|
+
isJsx: isTsx || args.path.endsWith('.jsx'),
|
|
174
|
+
})
|
|
175
|
+
if (result !== code) {
|
|
176
|
+
code = result
|
|
177
|
+
transformed = true
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!transformed) return undefined
|
|
181
|
+
return {
|
|
182
|
+
contents: code,
|
|
183
|
+
loader: isTsx || args.path.endsWith('.jsx') ? 'jsx' : 'js',
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type ServiceBinary = {
|
|
2
|
+
path: string;
|
|
3
|
+
source: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type ServiceBinaryOptions = {
|
|
7
|
+
env?: Record<string, string | undefined>;
|
|
8
|
+
root?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type PlatformTarget = {
|
|
12
|
+
os: string;
|
|
13
|
+
cpu: string;
|
|
14
|
+
libc?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export declare const MAIN_PACKAGE: string;
|
|
18
|
+
export declare const SERVICE_NAME: string;
|
|
19
|
+
export declare const UI_RUNTIME_NAME: string;
|
|
20
|
+
export declare const packageRoot: string;
|
|
21
|
+
export declare const PLATFORMS: Record<string, PlatformTarget>;
|
|
22
|
+
|
|
23
|
+
export declare function platformKey(platform?: string, arch?: string): string;
|
|
24
|
+
export declare function platformPackageName(key?: string): string;
|
|
25
|
+
export declare function isRepoCheckout(root?: string): boolean;
|
|
26
|
+
export declare function resolveServiceBinary(options?: ServiceBinaryOptions): ServiceBinary;
|
|
27
|
+
export declare function uiRuntimeEnv(binary: string, options?: ServiceBinaryOptions): { RNGPUI_UI_BUNDLE?: string };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { accessSync, chmodSync, constants, existsSync, statSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
export const MAIN_PACKAGE = "@felipe-software/react-native-gpui";
|
|
7
|
+
export const SERVICE_NAME = "rngpui-service";
|
|
8
|
+
export const UI_RUNTIME_NAME = "ui-runtime.js";
|
|
9
|
+
export const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
|
|
11
|
+
export const PLATFORMS = {
|
|
12
|
+
"linux-x64": { os: "linux", cpu: "x64", libc: "glibc" },
|
|
13
|
+
"linux-arm64": { os: "linux", cpu: "arm64", libc: "glibc" },
|
|
14
|
+
"darwin-arm64": { os: "darwin", cpu: "arm64" },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function platformKey(platform = process.platform, arch = process.arch) {
|
|
18
|
+
return `${platform}-${arch}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function platformPackageName(key = platformKey()) {
|
|
22
|
+
return `${MAIN_PACKAGE}-${key}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isRepoCheckout(root = packageRoot) {
|
|
26
|
+
return existsSync(resolve(root, "..", "rust", "Cargo.toml"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveServiceBinary({ env = process.env, root = packageRoot } = {}) {
|
|
30
|
+
const found = findServiceBinary(env, root);
|
|
31
|
+
if (found.source !== "RNGPUI_SERVICE") ensureExecutable(found.path);
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function uiRuntimeEnv(binary, { env = process.env, root = packageRoot } = {}) {
|
|
36
|
+
if (env.RNGPUI_UI_BUNDLE) return {};
|
|
37
|
+
if (existsSync(join(dirname(binary), UI_RUNTIME_NAME))) return {};
|
|
38
|
+
const packaged = join(root, "dist", UI_RUNTIME_NAME);
|
|
39
|
+
return existsSync(packaged) ? { RNGPUI_UI_BUNDLE: packaged } : {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function findServiceBinary(env, root) {
|
|
43
|
+
if (env.RNGPUI_SERVICE) {
|
|
44
|
+
const explicit = resolve(env.RNGPUI_SERVICE);
|
|
45
|
+
if (!existsSync(explicit)) throw new Error(`RNGPUI_SERVICE points at ${explicit}, which does not exist`);
|
|
46
|
+
return { path: explicit, source: "RNGPUI_SERVICE" };
|
|
47
|
+
}
|
|
48
|
+
const packaged = platformPackageBinary(root);
|
|
49
|
+
if (packaged) return packaged;
|
|
50
|
+
const local = localBinaries(root).find((candidate) => existsSync(candidate.path));
|
|
51
|
+
if (local) return local;
|
|
52
|
+
throw new Error(missingBinaryMessage(root));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function platformPackageBinary(root) {
|
|
56
|
+
const name = platformPackageName();
|
|
57
|
+
try {
|
|
58
|
+
const manifest = createRequire(join(root, "package.json")).resolve(`${name}/package.json`);
|
|
59
|
+
const path = join(dirname(manifest), SERVICE_NAME);
|
|
60
|
+
return existsSync(path) ? { path, source: name } : null;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function localBinaries(root) {
|
|
67
|
+
const native = { path: join(root, "native", SERVICE_NAME), source: "native/" };
|
|
68
|
+
if (!isRepoCheckout(root)) return [native];
|
|
69
|
+
const target = resolve(root, "..", "rust", "target");
|
|
70
|
+
const release = { path: join(target, "release", SERVICE_NAME), source: "rust/target/release" };
|
|
71
|
+
const debug = { path: join(target, "debug", SERVICE_NAME), source: "rust/target/debug" };
|
|
72
|
+
const newestFirst = [native, release].sort((a, b) => modifiedAt(b.path) - modifiedAt(a.path));
|
|
73
|
+
return [...newestFirst, debug];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function modifiedAt(path) {
|
|
77
|
+
try {
|
|
78
|
+
return statSync(path).mtimeMs;
|
|
79
|
+
} catch {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function missingBinaryMessage(root) {
|
|
85
|
+
const key = platformKey();
|
|
86
|
+
const supported = Object.keys(PLATFORMS).join(", ");
|
|
87
|
+
if (!PLATFORMS[key]) {
|
|
88
|
+
return `${MAIN_PACKAGE} has no prebuilt ${SERVICE_NAME} for ${key} (prebuilt: ${supported}). Build it from source and set RNGPUI_SERVICE.`;
|
|
89
|
+
}
|
|
90
|
+
const repoHint = isRepoCheckout(root) ? " or run `npm run build:rust && npm run build:native` in ts/" : "";
|
|
91
|
+
return `${SERVICE_NAME} not found: ${platformPackageName(key)} is not installed. It is an optional dependency of ${MAIN_PACKAGE}; reinstall without --omit=optional / --no-optional, set RNGPUI_SERVICE${repoHint}.`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function ensureExecutable(path) {
|
|
95
|
+
try {
|
|
96
|
+
accessSync(path, constants.X_OK);
|
|
97
|
+
} catch {
|
|
98
|
+
chmodSync(path, 0o755);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Shim for react-native-reanimated's `common/style/processors/colors` module.
|
|
2
|
+
//
|
|
3
|
+
// Why: Bun's bundler mis-orders the init of reanimated's color-processor barrel
|
|
4
|
+
// (`config.ts`'s top-level `const colorAttributes = { process: processColor }` reads
|
|
5
|
+
// the binding before Bun assigns it → "Property 'processColorN' doesn't exist" at
|
|
6
|
+
// bundle eval). The original module also pulls a `'worklet'` `PlatformColor` and the
|
|
7
|
+
// deep `Colors.ts` chain. We replace ONLY this leaf with a self-contained module (all
|
|
8
|
+
// exports are hoisted declarations in one file, so there is no cross-module rename for
|
|
9
|
+
// Bun to drop), preserving the exact public surface the barrel re-exports.
|
|
10
|
+
//
|
|
11
|
+
// Faithfulness: `processColor` returns RN's 32-bit ARGB int (the same form upstream's
|
|
12
|
+
// `processColorInitially` produces), so reanimated's color interpolation + the gpui
|
|
13
|
+
// seam's `processedColorToCss` round-trip identically. `processColorsInProps` mutates
|
|
14
|
+
// color-valued style keys in place to that int form, matching upstream.
|
|
15
|
+
|
|
16
|
+
type StyleProps = Record<string, unknown>
|
|
17
|
+
|
|
18
|
+
const COLOR_KEYS = new Set([
|
|
19
|
+
'color',
|
|
20
|
+
'backgroundColor',
|
|
21
|
+
'borderColor',
|
|
22
|
+
'borderTopColor',
|
|
23
|
+
'borderRightColor',
|
|
24
|
+
'borderBottomColor',
|
|
25
|
+
'borderLeftColor',
|
|
26
|
+
'borderStartColor',
|
|
27
|
+
'borderEndColor',
|
|
28
|
+
'tintColor',
|
|
29
|
+
'shadowColor',
|
|
30
|
+
'overlayColor',
|
|
31
|
+
'textDecorationColor',
|
|
32
|
+
'textShadowColor',
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
// RN-style color string/number → 32-bit ARGB int (a>>24, r>>16, g>>8, b). Mirrors
|
|
36
|
+
// react-native's processColor output so reanimated's color interpolation operates on
|
|
37
|
+
// the same numeric form it expects.
|
|
38
|
+
export function processColor(color: unknown): number | null {
|
|
39
|
+
if (color == null) return null
|
|
40
|
+
if (typeof color === 'number') return color >>> 0
|
|
41
|
+
if (typeof color !== 'string') return null
|
|
42
|
+
const s = color.trim()
|
|
43
|
+
let m: RegExpExecArray | null
|
|
44
|
+
if ((m = /^#([0-9a-f]{6})$/i.exec(s))) return (0xff000000 | parseInt(m[1], 16)) >>> 0
|
|
45
|
+
if ((m = /^#([0-9a-f]{8})$/i.exec(s))) {
|
|
46
|
+
const n = parseInt(m[1], 16)
|
|
47
|
+
const a = n & 0xff
|
|
48
|
+
return (((a << 24) | (n >>> 8)) >>> 0) >>> 0
|
|
49
|
+
}
|
|
50
|
+
if ((m = /^#([0-9a-f]{3})$/i.exec(s))) {
|
|
51
|
+
const h = m[1]
|
|
52
|
+
const r = parseInt(h[0] + h[0], 16)
|
|
53
|
+
const g = parseInt(h[1] + h[1], 16)
|
|
54
|
+
const b = parseInt(h[2] + h[2], 16)
|
|
55
|
+
return (0xff000000 | (r << 16) | (g << 8) | b) >>> 0
|
|
56
|
+
}
|
|
57
|
+
if ((m = /^rgba?\(([^)]+)\)$/i.exec(s))) {
|
|
58
|
+
const p = m[1].split(',').map((x) => x.trim())
|
|
59
|
+
const r = parseInt(p[0]) || 0
|
|
60
|
+
const g = parseInt(p[1]) || 0
|
|
61
|
+
const b = parseInt(p[2]) || 0
|
|
62
|
+
let a = p[3] === undefined ? 1 : parseFloat(p[3])
|
|
63
|
+
if (!(a >= 0)) a = 0
|
|
64
|
+
if (a > 1) a = 1
|
|
65
|
+
return ((((a * 255) & 0xff) << 24) | (r << 16) | (g << 8) | b) >>> 0
|
|
66
|
+
}
|
|
67
|
+
// named colors / unknown → opaque black (matches RN's fallback shape closely enough).
|
|
68
|
+
return 0xff000000 >>> 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function processColorNumber(value: unknown): number | null {
|
|
72
|
+
return processColor(value)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function processColorsInProps(props: StyleProps): void {
|
|
76
|
+
for (const key of Object.keys(props)) {
|
|
77
|
+
if (COLOR_KEYS.has(key)) {
|
|
78
|
+
const processed = processColor(props[key])
|
|
79
|
+
if (processed != null) props[key] = processed
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// PlatformColor / DynamicColorIOS — gpui has no native semantic colors; identity-ish.
|
|
85
|
+
export function PlatformColor(...names: string[]): unknown {
|
|
86
|
+
return names[0]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function DynamicColorIOS(tuple: { light: unknown; dark: unknown }): unknown {
|
|
90
|
+
return tuple?.light
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── worklet-builtin registration ─────────────────────────────────────────────
|
|
94
|
+
// Upstream's updateProps worklet CAPTURES processColorsInProps in its closure.
|
|
95
|
+
// These shim fns are plain (not babel-workletized), so without a builtin brand
|
|
96
|
+
// the cross-runtime serializer ships them as async jsCallback proxies — on the
|
|
97
|
+
// worklet/UI runtime they then do nothing and every color key crosses as
|
|
98
|
+
// undefined (the dropped-dialog-bg bug). Brand + register them so closures
|
|
99
|
+
// serialize as {kind:'builtin', name} and bind to THIS module's implementation
|
|
100
|
+
// on whichever runtime executes (this shim is baked into the prebuilt chunk,
|
|
101
|
+
// which evaluates on both). Constants mirror worklet-runtime.ts — this file
|
|
102
|
+
// cannot import it (the prebuilt chunk keeps the worklets package external).
|
|
103
|
+
const RNGPUI_BUILTIN_NAME = Symbol.for('rngpui.workletBuiltinName')
|
|
104
|
+
function registerColorBuiltin(name: string, fn: (...args: never[]) => unknown): void {
|
|
105
|
+
try {
|
|
106
|
+
Object.defineProperty(fn, RNGPUI_BUILTIN_NAME, {
|
|
107
|
+
value: name,
|
|
108
|
+
enumerable: false,
|
|
109
|
+
configurable: true,
|
|
110
|
+
writable: false,
|
|
111
|
+
})
|
|
112
|
+
} catch {}
|
|
113
|
+
const g = globalThis as unknown as { __rngpui_worklet_builtins?: Map<string, unknown> }
|
|
114
|
+
if (!g.__rngpui_worklet_builtins) g.__rngpui_worklet_builtins = new Map()
|
|
115
|
+
g.__rngpui_worklet_builtins.set(name, fn)
|
|
116
|
+
}
|
|
117
|
+
registerColorBuiltin('processColor', processColor)
|
|
118
|
+
registerColorBuiltin('processColorNumber', processColorNumber)
|
|
119
|
+
registerColorBuiltin('processColorsInProps', processColorsInProps)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// `react-native-reanimated` entry for rngpui bundles — the bundler plugins alias
|
|
2
|
+
// the package here, and `rngpui-reanimated-prebuilt` to the prebuilt upstream
|
|
3
|
+
// chunk. Re-exports upstream untouched EXCEPT, on the React runtime with the
|
|
4
|
+
// worklet bridge present, the animation factories — the producer half of
|
|
5
|
+
// sootsim's guestDecorator (plans/off-thread-reanimated.md):
|
|
6
|
+
//
|
|
7
|
+
// - calling `withTiming(...)` / `withSpring(...)` / … stamps the returned
|
|
8
|
+
// animation object with a non-enumerable descriptor {type, args} so a direct
|
|
9
|
+
// `sv.value = withX(...)` write on the React runtime can be re-created and
|
|
10
|
+
// DRIVEN on the worklet/UI runtime (worklets.ts setValue →
|
|
11
|
+
// dispatchHostValueSet → HOST_SET_SHARED_VALUE_WORKLET revive).
|
|
12
|
+
// - each wrapper registers itself as a worklet builtin, so a mapper closure
|
|
13
|
+
// capturing `withSpring` serializes as {kind:'builtin', name:'withSpring'}
|
|
14
|
+
// and binds to the UI runtime's OWN factory over there (registered raw by
|
|
15
|
+
// ui-entry.ts). Without this, closure serialization would ship the wrapper
|
|
16
|
+
// as code and lose the stamp-free UI-side identity.
|
|
17
|
+
//
|
|
18
|
+
// On the UI runtime (and in bun tests / single-runtime mode) factories pass
|
|
19
|
+
// through untouched — animations are created and driven locally there, no stamp
|
|
20
|
+
// needed. Explicit named exports win over the `export *` per the ES module
|
|
21
|
+
// spec's ambiguity rule, so the wrapped names below shadow the chunk's.
|
|
22
|
+
import * as upstream from 'rngpui-reanimated-prebuilt'
|
|
23
|
+
import { registerWorkletBuiltin } from './worklet-runtime'
|
|
24
|
+
|
|
25
|
+
export * from 'rngpui-reanimated-prebuilt'
|
|
26
|
+
export { default } from 'rngpui-reanimated-prebuilt'
|
|
27
|
+
|
|
28
|
+
const RNGPUI_REANIMATED_ANIMATION_DESCRIPTOR = Symbol.for('rngpui.reanimatedAnimationDescriptor')
|
|
29
|
+
|
|
30
|
+
declare const __rngpui_uiPost: ((arg: string) => void) | undefined
|
|
31
|
+
|
|
32
|
+
// only the React runtime with a live worklet bridge stamps factories.
|
|
33
|
+
const wrapFactories = typeof __rngpui_uiPost === 'function'
|
|
34
|
+
|
|
35
|
+
type AnimationFactory = (...args: unknown[]) => unknown
|
|
36
|
+
|
|
37
|
+
function stamped(name: string, factory: unknown): AnimationFactory {
|
|
38
|
+
const fn = factory as AnimationFactory
|
|
39
|
+
if (!wrapFactories || typeof fn !== 'function') return fn
|
|
40
|
+
const wrapper: AnimationFactory = (...args: unknown[]) => {
|
|
41
|
+
const out = fn(...args)
|
|
42
|
+
if (out && (typeof out === 'object' || typeof out === 'function')) {
|
|
43
|
+
Object.defineProperty(out as object, RNGPUI_REANIMATED_ANIMATION_DESCRIPTOR, {
|
|
44
|
+
value: { type: name, args },
|
|
45
|
+
enumerable: false,
|
|
46
|
+
configurable: true,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
return out
|
|
50
|
+
}
|
|
51
|
+
// keep the worklet metadata visible on the wrapper so upstream's
|
|
52
|
+
// areWorkletsEqual/isWorkletFunction checks treat it like the original.
|
|
53
|
+
for (const key of ['__workletHash', '__initData', '__closure', '__isAnimationDefinition']) {
|
|
54
|
+
const value = (fn as unknown as Record<string, unknown>)[key]
|
|
55
|
+
if (value !== undefined) {
|
|
56
|
+
Object.defineProperty(wrapper, key, { value, enumerable: false, configurable: true, writable: true })
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
registerWorkletBuiltin(name, wrapper)
|
|
60
|
+
return wrapper
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const withTiming = stamped('withTiming', upstream.withTiming)
|
|
64
|
+
export const withSpring = stamped('withSpring', upstream.withSpring)
|
|
65
|
+
export const withDecay = stamped('withDecay', upstream.withDecay)
|
|
66
|
+
export const withDelay = stamped('withDelay', upstream.withDelay)
|
|
67
|
+
export const withRepeat = stamped('withRepeat', upstream.withRepeat)
|
|
68
|
+
export const withSequence = stamped('withSequence', upstream.withSequence)
|
|
69
|
+
export const withClamp = stamped('withClamp', upstream.withClamp)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// virtual module id the bundler plugins map to the esbuild-prebuilt upstream
|
|
2
|
+
// reanimated chunk (.reanimated-prebuilt/react-native-reanimated.mjs). Only
|
|
3
|
+
// reanimated-host.ts imports it; type-wise it IS upstream reanimated.
|
|
4
|
+
declare module 'rngpui-reanimated-prebuilt' {
|
|
5
|
+
export * from 'react-native-reanimated'
|
|
6
|
+
export { default } from 'react-native-reanimated'
|
|
7
|
+
}
|