zyzz 0.0.21 → 0.0.22

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.
Files changed (220) hide show
  1. package/Zyzz.podspec +16 -0
  2. package/dist/Style.d.ts +3 -1
  3. package/dist/Style.d.ts.map +1 -1
  4. package/dist/Style.js.map +1 -1
  5. package/dist/babel/NativeJsx.d.ts.map +1 -1
  6. package/dist/babel/NativeJsx.js +204 -12
  7. package/dist/babel/NativeJsx.js.map +1 -1
  8. package/dist/babel/index.d.ts +6 -2
  9. package/dist/babel/index.d.ts.map +1 -1
  10. package/dist/babel/index.js +10 -1
  11. package/dist/babel/index.js.map +1 -1
  12. package/dist/compiler/Graph.d.ts.map +1 -1
  13. package/dist/compiler/Graph.js +64 -0
  14. package/dist/compiler/Graph.js.map +1 -1
  15. package/dist/compiler/Native.d.ts +6 -3
  16. package/dist/compiler/Native.d.ts.map +1 -1
  17. package/dist/compiler/Native.js +142 -24
  18. package/dist/compiler/Native.js.map +1 -1
  19. package/dist/compiler/Source.d.ts +2 -0
  20. package/dist/compiler/Source.d.ts.map +1 -1
  21. package/dist/compiler/Source.js +17 -5
  22. package/dist/compiler/Source.js.map +1 -1
  23. package/dist/compiler/internal/Configurations.d.ts +2 -0
  24. package/dist/compiler/internal/Configurations.d.ts.map +1 -1
  25. package/dist/compiler/internal/Configurations.js +21 -3
  26. package/dist/compiler/internal/Configurations.js.map +1 -1
  27. package/dist/compiler/internal/Contract.d.ts.map +1 -1
  28. package/dist/compiler/internal/Contract.js +75 -38
  29. package/dist/compiler/internal/Contract.js.map +1 -1
  30. package/dist/compiler/internal/Contributions.d.ts.map +1 -1
  31. package/dist/compiler/internal/Contributions.js +7 -1
  32. package/dist/compiler/internal/Contributions.js.map +1 -1
  33. package/dist/compiler/internal/Descriptors.d.ts.map +1 -1
  34. package/dist/compiler/internal/Descriptors.js +11 -2
  35. package/dist/compiler/internal/Descriptors.js.map +1 -1
  36. package/dist/compiler/internal/NativeBindings.d.ts +24 -26
  37. package/dist/compiler/internal/NativeBindings.d.ts.map +1 -1
  38. package/dist/compiler/internal/NativeBindings.js +61 -8
  39. package/dist/compiler/internal/NativeBindings.js.map +1 -1
  40. package/dist/compiler/internal/NativeMedia.d.ts +14 -0
  41. package/dist/compiler/internal/NativeMedia.d.ts.map +1 -0
  42. package/dist/compiler/internal/NativeMedia.js +287 -0
  43. package/dist/compiler/internal/NativeMedia.js.map +1 -0
  44. package/dist/compiler/internal/NativeVars.d.ts +28 -0
  45. package/dist/compiler/internal/NativeVars.d.ts.map +1 -0
  46. package/dist/compiler/internal/NativeVars.js +157 -0
  47. package/dist/compiler/internal/NativeVars.js.map +1 -0
  48. package/dist/compiler/internal/PackedStyles.d.ts +2 -0
  49. package/dist/compiler/internal/PackedStyles.d.ts.map +1 -1
  50. package/dist/compiler/internal/PackedStyles.js +2 -0
  51. package/dist/compiler/internal/PackedStyles.js.map +1 -1
  52. package/dist/compiler/internal/Themes.d.ts +22 -1
  53. package/dist/compiler/internal/Themes.d.ts.map +1 -1
  54. package/dist/compiler/internal/Themes.js +118 -3
  55. package/dist/compiler/internal/Themes.js.map +1 -1
  56. package/dist/compiler/internal/Vars.d.ts +1 -1
  57. package/dist/compiler/internal/Vars.d.ts.map +1 -1
  58. package/dist/compiler/internal/Vars.js +7 -2
  59. package/dist/compiler/internal/Vars.js.map +1 -1
  60. package/dist/default.js.zyzz.json +1 -1
  61. package/dist/internal/Completion.d.ts +1 -1
  62. package/dist/internal/Completion.d.ts.map +1 -1
  63. package/dist/internal/Targets.d.ts.map +1 -1
  64. package/dist/internal/Targets.js +2 -0
  65. package/dist/internal/Targets.js.map +1 -1
  66. package/dist/internal/Token.d.ts +0 -2
  67. package/dist/internal/Token.d.ts.map +1 -1
  68. package/dist/internal/Token.js.map +1 -1
  69. package/dist/internal/Value.d.ts +7 -0
  70. package/dist/internal/Value.d.ts.map +1 -1
  71. package/dist/internal/Value.js.map +1 -1
  72. package/dist/metro/Graph.d.ts +22 -7
  73. package/dist/metro/Graph.d.ts.map +1 -1
  74. package/dist/metro/Graph.js +179 -42
  75. package/dist/metro/Graph.js.map +1 -1
  76. package/dist/metro/Watch.d.ts +28 -4
  77. package/dist/metro/Watch.d.ts.map +1 -1
  78. package/dist/metro/Watch.js +39 -7
  79. package/dist/metro/Watch.js.map +1 -1
  80. package/dist/metro/index.d.ts +15 -4
  81. package/dist/metro/index.d.ts.map +1 -1
  82. package/dist/metro/index.js +27 -0
  83. package/dist/metro/index.js.map +1 -1
  84. package/dist/metro/transformer.d.ts +5 -1
  85. package/dist/metro/transformer.d.ts.map +1 -1
  86. package/dist/metro/transformer.js +5 -3
  87. package/dist/metro/transformer.js.map +1 -1
  88. package/dist/react-native/StyleSheet.d.ts +3 -2
  89. package/dist/react-native/StyleSheet.d.ts.map +1 -1
  90. package/dist/react-native/StyleSheet.js +17 -34
  91. package/dist/react-native/StyleSheet.js.map +1 -1
  92. package/dist/react-native/index.d.ts +1 -1
  93. package/dist/react-native/index.d.ts.map +1 -1
  94. package/dist/react-native/index.js +1 -1
  95. package/dist/react-native/index.js.map +1 -1
  96. package/dist/react-native/index.native.d.ts +5 -0
  97. package/dist/react-native/index.native.js +20 -0
  98. package/dist/react-native/index.native.js.map +7 -0
  99. package/dist/react-native/internal/Calculation.d.ts +7 -0
  100. package/dist/react-native/internal/Calculation.d.ts.map +1 -0
  101. package/dist/react-native/internal/Calculation.js +134 -0
  102. package/dist/react-native/internal/Calculation.js.map +1 -0
  103. package/dist/react-native/internal/Device.js +191 -0
  104. package/dist/react-native/internal/Device.js.map +7 -0
  105. package/dist/react-native/internal/NativeZyzz.js +7 -0
  106. package/dist/react-native/internal/NativeZyzz.js.map +7 -0
  107. package/dist/react-native/internal/Scalar.d.ts.map +1 -1
  108. package/dist/react-native/internal/Scalar.js +9 -2
  109. package/dist/react-native/internal/Scalar.js.map +1 -1
  110. package/dist/react-native/internal/Store.d.ts +32 -0
  111. package/dist/react-native/internal/Store.d.ts.map +1 -0
  112. package/dist/react-native/internal/Store.js +57 -0
  113. package/dist/react-native/internal/Store.js.map +1 -0
  114. package/dist/react-native/internal/Tokens.d.ts +21 -0
  115. package/dist/react-native/internal/Tokens.d.ts.map +1 -0
  116. package/dist/react-native/internal/Tokens.js +58 -0
  117. package/dist/react-native/internal/Tokens.js.map +1 -0
  118. package/dist/react-native/internal/Viewport.d.ts +6 -0
  119. package/dist/react-native/internal/Viewport.d.ts.map +1 -0
  120. package/dist/react-native/internal/Viewport.js +4 -0
  121. package/dist/react-native/internal/Viewport.js.map +1 -0
  122. package/dist/react-native/react.d.ts +74 -11
  123. package/dist/react-native/react.d.ts.map +1 -1
  124. package/dist/react-native/react.js +111 -12
  125. package/dist/react-native/react.js.map +1 -1
  126. package/dist/react-native/reanimated.d.ts +39 -0
  127. package/dist/react-native/reanimated.d.ts.map +1 -0
  128. package/dist/react-native/reanimated.js +76 -0
  129. package/dist/react-native/reanimated.js.map +1 -0
  130. package/dist/runtime/NativeContext.d.ts +10 -1
  131. package/dist/runtime/NativeContext.d.ts.map +1 -1
  132. package/dist/runtime/NativeContext.js +67 -14
  133. package/dist/runtime/NativeContext.js.map +1 -1
  134. package/dist/runtime/NativeDynamic.d.ts +3 -2
  135. package/dist/runtime/NativeDynamic.d.ts.map +1 -1
  136. package/dist/runtime/NativeDynamic.js +32 -7
  137. package/dist/runtime/NativeDynamic.js.map +1 -1
  138. package/dist/runtime/NativeStatic.d.ts +23 -0
  139. package/dist/runtime/NativeStatic.d.ts.map +1 -0
  140. package/dist/runtime/NativeStatic.js +106 -0
  141. package/dist/runtime/NativeStatic.js.map +1 -0
  142. package/dist/runtime/NativeVars.d.ts +53 -0
  143. package/dist/runtime/NativeVars.d.ts.map +1 -0
  144. package/dist/runtime/NativeVars.js +77 -0
  145. package/dist/runtime/NativeVars.js.map +1 -0
  146. package/dist/runtime/index.d.ts +4 -0
  147. package/dist/runtime/index.d.ts.map +1 -1
  148. package/dist/runtime/index.js +4 -0
  149. package/dist/runtime/index.js.map +1 -1
  150. package/dist/runtime/internal/NativeMedia.d.ts +25 -0
  151. package/dist/runtime/internal/NativeMedia.d.ts.map +1 -0
  152. package/dist/runtime/internal/NativeMedia.js +36 -0
  153. package/dist/runtime/internal/NativeMedia.js.map +1 -0
  154. package/dist/vite/index.d.ts.map +1 -1
  155. package/dist/vite/index.js +7 -35
  156. package/dist/vite/index.js.map +1 -1
  157. package/dist/web/Css.d.ts.map +1 -1
  158. package/dist/web/Css.js +31 -19
  159. package/dist/web/Css.js.map +1 -1
  160. package/dist/web/internal/Themes.d.ts +0 -2
  161. package/dist/web/internal/Themes.d.ts.map +1 -1
  162. package/dist/web/internal/Themes.js +30 -28
  163. package/dist/web/internal/Themes.js.map +1 -1
  164. package/dist/web/page.d.ts +2 -0
  165. package/dist/web/page.d.ts.map +1 -1
  166. package/native/NativeZyzz.cpp +215 -0
  167. package/native/NativeZyzz.h +52 -0
  168. package/native/ZyzzSpecJSI.h +65 -0
  169. package/native/android/CMakeLists.txt +8 -0
  170. package/native/android/ZyzzSpec-generated.cpp +50 -0
  171. package/native/android/ZyzzSpec.h +31 -0
  172. package/native/ios/ZyzzModuleProvider.mm +13 -0
  173. package/package.json +39 -1
  174. package/react-native.config.ts +14 -0
  175. package/src/Style.ts +11 -8
  176. package/src/babel/NativeJsx.ts +305 -28
  177. package/src/babel/index.ts +21 -3
  178. package/src/compiler/Graph.ts +75 -0
  179. package/src/compiler/Native.ts +158 -26
  180. package/src/compiler/Source.ts +32 -5
  181. package/src/compiler/internal/Configurations.ts +25 -3
  182. package/src/compiler/internal/Contract.ts +98 -50
  183. package/src/compiler/internal/Contributions.ts +7 -1
  184. package/src/compiler/internal/Descriptors.ts +12 -8
  185. package/src/compiler/internal/NativeBindings.ts +98 -15
  186. package/src/compiler/internal/NativeMedia.ts +342 -0
  187. package/src/compiler/internal/NativeVars.ts +228 -0
  188. package/src/compiler/internal/PackedStyles.ts +4 -0
  189. package/src/compiler/internal/Themes.ts +187 -2
  190. package/src/compiler/internal/Vars.ts +12 -1
  191. package/src/internal/Completion.ts +6 -3
  192. package/src/internal/Targets.ts +2 -0
  193. package/src/internal/Token.ts +0 -2
  194. package/src/internal/Value.ts +20 -0
  195. package/src/metro/Graph.ts +235 -50
  196. package/src/metro/Watch.ts +95 -10
  197. package/src/metro/index.ts +62 -4
  198. package/src/metro/transformer.ts +21 -11
  199. package/src/react-native/StyleSheet.ts +28 -44
  200. package/src/react-native/index.native.ts +10 -0
  201. package/src/react-native/index.ts +1 -1
  202. package/src/react-native/internal/Calculation.ts +159 -0
  203. package/src/react-native/internal/Device.ts +231 -0
  204. package/src/react-native/internal/NativeZyzz.ts +20 -0
  205. package/src/react-native/internal/Scalar.ts +13 -2
  206. package/src/react-native/internal/Store.ts +78 -0
  207. package/src/react-native/internal/Tokens.ts +85 -0
  208. package/src/react-native/internal/Viewport.ts +8 -0
  209. package/src/react-native/react.ts +211 -19
  210. package/src/react-native/reanimated.ts +162 -0
  211. package/src/runtime/NativeContext.ts +88 -17
  212. package/src/runtime/NativeDynamic.ts +35 -10
  213. package/src/runtime/NativeStatic.ts +145 -0
  214. package/src/runtime/NativeVars.ts +189 -0
  215. package/src/runtime/index.ts +6 -0
  216. package/src/runtime/internal/NativeMedia.ts +63 -0
  217. package/src/vite/index.ts +9 -42
  218. package/src/web/Css.ts +42 -27
  219. package/src/web/internal/Themes.ts +34 -57
  220. package/src/web/page.ts +2 -0
@@ -1,80 +1,265 @@
1
- /** Reads a closed local source graph for native authoring without evaluating modules. @module */
1
+ /** Reads resolved source and packed native authoring without evaluating modules. @module */
2
+ import type * as Ast from '@oxc-project/types'
3
+ import type * as Compiler from '../compiler/Graph.js'
2
4
  import * as Fs from 'node:fs'
3
5
  import * as Path from 'node:path'
4
6
  import * as Snapshot from '../node/internal/Snapshot.js'
5
- import type * as Graph from '../compiler/Graph.js'
6
7
 
7
- /** Collects relative source imports; package and asset imports remain owned by Metro. */
8
+ /** Serializable source snapshot delivered to a Metro transformer worker. */
9
+ export type Input = Pick<
10
+ Compiler.compile.Options,
11
+ 'contracts' | 'imports' | 'modules'
12
+ > & {
13
+ /** Files and sidecars whose edits invalidate this consumer. */
14
+ readonly files: readonly string[]
15
+ /** Portable compiler identity for the requesting module. */
16
+ readonly moduleId: string
17
+ }
18
+
19
+ /** Collects authoring through Metro resolutions, retaining packed contracts and watched inputs. */
8
20
  export function read(
9
21
  filename: string,
10
22
  source: string,
11
23
  platform: string,
12
24
  root: string,
13
25
  snapshot = Snapshot.create(),
14
- ) {
26
+ resolve?: Resolver,
27
+ ): read.ReturnType {
28
+ const contracts: Record<string, string> = Object.create(null)
15
29
  const files = new Set<string>()
16
- const modules: Record<string, string> = Object.create(null)
17
30
  const imports: Record<string, Record<string, string | null>> = Object.create(
18
31
  null,
19
32
  )
33
+ const modules: Record<string, string> = Object.create(null)
34
+
20
35
  function identity(filename: string) {
21
- const relative = Path.relative(root, filename).split(Path.sep).join('/')
22
- if (relative.startsWith('../'))
23
- throw new Error(
24
- `Native authoring must be inside Metro's project root: ${filename}`,
36
+ const parts = Path.relative(root, filename).split(Path.sep)
37
+ let parents = 0
38
+ while (parts[0] === '..') {
39
+ parents++
40
+ parts.shift()
41
+ }
42
+ return `${parents ? `external/${parents}` : 'project'}/${parts.map(encodeURIComponent).join('/')}`
43
+ }
44
+
45
+ function local(filename: string): boolean {
46
+ const relative = Path.relative(root, filename)
47
+ return (
48
+ relative !== '..' &&
49
+ !relative.startsWith(`..${Path.sep}`) &&
50
+ !Path.isAbsolute(relative) &&
51
+ !relative.split(Path.sep).includes('node_modules')
52
+ )
53
+ }
54
+
55
+ function manifest(filename: string): string | undefined {
56
+ let directory = Path.dirname(filename)
57
+ while (true) {
58
+ const file = Path.join(directory, 'package.json')
59
+ files.add(file)
60
+ if (Fs.existsSync(file)) return file
61
+
62
+ const parent = Path.dirname(directory)
63
+ if (parent === directory) return undefined
64
+ directory = parent
65
+ }
66
+ }
67
+
68
+ function target(
69
+ filename: string,
70
+ specifier: string,
71
+ isESMImport = true,
72
+ ): string | undefined {
73
+ if (resolve) {
74
+ const next = resolve(filename, specifier, isESMImport)
75
+ if (next && !specifier.startsWith('.')) manifest(next)
76
+ return next
77
+ }
78
+ if (!specifier.startsWith('.')) return undefined
79
+ const base = Path.resolve(Path.dirname(filename), specifier)
80
+ const stem = base.replace(/\.[cm]?jsx?$/, '')
81
+ const candidates = [
82
+ ...['.ts', '.tsx', '.js', '.jsx'].flatMap((extension) => [
83
+ `${stem}.${platform}${extension}`,
84
+ `${stem}.native${extension}`,
85
+ `${stem}${extension}`,
86
+ ]),
87
+ base,
88
+ ...['.ts', '.tsx', '.js', '.jsx'].map((extension) =>
89
+ Path.join(base, `index${extension}`),
90
+ ),
91
+ ]
92
+ for (const candidate of candidates) files.add(candidate)
93
+ return candidates.find(
94
+ (path) => Fs.existsSync(path) && Fs.statSync(path).isFile(),
95
+ )
96
+ }
97
+
98
+ function specifier(node: Ast.Program['body'][number]): string | undefined {
99
+ if (
100
+ (node.type !== 'ImportDeclaration' &&
101
+ node.type !== 'ExportAllDeclaration' &&
102
+ node.type !== 'ExportNamedDeclaration') ||
103
+ !node.source ||
104
+ ('importKind' in node && node.importKind === 'type') ||
105
+ ('exportKind' in node && node.exportKind === 'type') ||
106
+ ('specifiers' in node &&
107
+ node.specifiers.length > 0 &&
108
+ node.specifiers.every(
109
+ (specifier) =>
110
+ ('importKind' in specifier && specifier.importKind === 'type') ||
111
+ ('exportKind' in specifier && specifier.exportKind === 'type'),
112
+ ))
113
+ )
114
+ return undefined
115
+
116
+ return node.source.value
117
+ }
118
+
119
+ function requires(node: unknown, names: Map<string, boolean>): void {
120
+ if (!node || typeof node !== 'object') return
121
+ if (Array.isArray(node)) {
122
+ for (const child of node) requires(child, names)
123
+ return
124
+ }
125
+
126
+ const entry = node as Ast.Node
127
+ if (
128
+ entry.type === 'CallExpression' &&
129
+ entry.callee.type === 'Identifier' &&
130
+ entry.callee.name === 'require' &&
131
+ entry.arguments.length === 1
132
+ ) {
133
+ const argument = entry.arguments[0]!
134
+ if (argument.type === 'Literal' && typeof argument.value === 'string')
135
+ names.set(argument.value, false)
136
+ }
137
+
138
+ for (const child of Object.values(node)) requires(child, names)
139
+ }
140
+
141
+ const authored = new Set<string>()
142
+ function authoring(filename: string, seen = new Set<string>()): boolean {
143
+ files.add(filename)
144
+ files.add(`${filename}.zyzz.json`)
145
+ if (Fs.existsSync(`${filename}.zyzz.json`)) return true
146
+ if (authored.has(filename)) return true
147
+ if (seen.has(filename)) return false
148
+ seen.add(filename)
149
+ const source = snapshot.readSync(filename)
150
+ const file = manifest(filename)
151
+ if (file && !/['"]zyzz(?:\/default)?['"]/.test(source)) {
152
+ const data = JSON.parse(snapshot.readSync(file)) as Record<
153
+ string,
154
+ unknown
155
+ >
156
+ if (
157
+ ![
158
+ 'dependencies',
159
+ 'devDependencies',
160
+ 'optionalDependencies',
161
+ 'peerDependencies',
162
+ ].some((key) => {
163
+ const dependencies = data[key]
164
+ return (
165
+ dependencies &&
166
+ typeof dependencies === 'object' &&
167
+ Object.hasOwn(dependencies, 'zyzz')
168
+ )
169
+ })
25
170
  )
26
- return relative
171
+ return false
172
+ }
173
+
174
+ for (const node of snapshot.parse({ moduleId: identity(filename), source })
175
+ .program.body) {
176
+ const name = specifier(node)
177
+ if (name === 'zyzz' || name === 'zyzz/default') {
178
+ authored.add(filename)
179
+ return true
180
+ }
181
+ if (!name || name.startsWith('zyzz/')) continue
182
+ const next = target(filename, name)
183
+ if (next && /\.[cm]?[jt]sx?$/.test(next) && authoring(next, seen)) {
184
+ authored.add(filename)
185
+ return true
186
+ }
187
+ }
188
+ return false
27
189
  }
28
- function visit(filename: string, source: string) {
190
+ const visited = new Set<string>()
191
+ function visit(filename: string, source: string, packed = false) {
29
192
  files.add(filename)
30
193
  const id = identity(filename)
31
- if (Object.hasOwn(modules, id)) return
32
- modules[id] = source
194
+ if (visited.has(id)) return
195
+ visited.add(id)
196
+ if (!packed) modules[id] = source
33
197
  const resolved: Record<string, string | null> = Object.create(null)
34
198
  imports[id] = resolved
35
- for (const statement of snapshot.parse({ moduleId: id, source }).program
36
- .body) {
199
+ const program = snapshot.parse({ moduleId: id, source }).program
200
+ const names = new Map<string, boolean>()
201
+ for (const statement of program.body) {
202
+ const name = specifier(statement)
203
+ if (name) names.set(name, true)
204
+ }
205
+ if (packed) requires(program, names)
206
+
207
+ for (const [name, isESMImport] of names) {
208
+ resolved[name] = null
37
209
  if (
38
- !('source' in statement) ||
39
- !statement.source ||
40
- typeof statement.source !== 'object' ||
41
- !('value' in statement.source) ||
42
- typeof statement.source.value !== 'string'
210
+ name === 'zyzz' ||
211
+ name.startsWith('zyzz/') ||
212
+ ['react', 'react-native'].includes(name)
43
213
  )
44
214
  continue
45
- const specifier = statement.source.value
46
- resolved[specifier] = null
47
- if (!specifier.startsWith('.')) continue
48
- const base = Path.resolve(Path.dirname(filename), specifier)
49
- const stem = base.replace(/\.[cm]?jsx?$/, '')
50
- const candidates = [
51
- ...['.ts', '.tsx', '.js', '.jsx'].flatMap((extension) => [
52
- `${stem}.${platform}${extension}`,
53
- `${stem}.native${extension}`,
54
- `${stem}${extension}`,
55
- ]),
56
- base,
57
- ...['.ts', '.tsx', '.js', '.jsx'].map((extension) =>
58
- Path.join(base, `index${extension}`),
59
- ),
60
- ]
61
- for (const candidate of candidates) files.add(candidate)
62
- const target = candidates.find(
63
- (path) => Fs.existsSync(path) && Fs.statSync(path).isFile(),
64
- )
65
- if (!target || !/\.[cm]?[jt]sx?$/.test(target)) continue
66
- resolved[specifier] = identity(target)
67
- visit(target, snapshot.readSync(target))
215
+ const next = target(filename, name, isESMImport)
216
+ if (!next || !/\.[cm]?[jt]sx?$/.test(next)) continue
217
+ const packed = `${next}.zyzz.json`
218
+ files.add(packed)
219
+ if (Fs.existsSync(packed)) {
220
+ files.add(next)
221
+ contracts[identity(next)] = snapshot.readSync(packed)
222
+ resolved[name] = identity(next)
223
+ visit(next, snapshot.readSync(next), true)
224
+ continue
225
+ }
226
+ if (!name.startsWith('.') && !local(next) && !authoring(next)) continue
227
+ resolved[name] = identity(next)
228
+ visit(next, snapshot.readSync(next))
68
229
  }
69
230
  }
231
+ if (!local(filename) && !authoring(filename))
232
+ return { files: [...files], input: undefined }
233
+
70
234
  visit(filename, source)
235
+
236
+ const watched = [...files]
71
237
  return {
72
- moduleId: identity(filename),
73
- modules,
74
- imports,
75
- files: [...files],
76
- } satisfies Pick<Graph.compile.Options, 'modules' | 'imports'> & {
77
- moduleId: string
78
- files: readonly string[]
238
+ files: watched,
239
+ input: {
240
+ contracts,
241
+ files: watched,
242
+ imports,
243
+ moduleId: identity(filename),
244
+ modules,
245
+ },
79
246
  }
80
247
  }
248
+
249
+ /** Source classification and all paths that can change its result. */
250
+ export declare namespace read {
251
+ /** Candidate files remain watchable even when the module currently needs no compilation. */
252
+ type ReturnType = {
253
+ /** Sources, manifests, and sidecar candidates that affect classification. */
254
+ readonly files: readonly string[]
255
+ /** Compiler input when the module belongs to the authoring graph. */
256
+ readonly input: Input | undefined
257
+ }
258
+ }
259
+
260
+ /** Source resolution supplied by Metro's live dependency graph. */
261
+ export type Resolver = (
262
+ filename: string,
263
+ specifier: string,
264
+ isESMImport: boolean,
265
+ ) => string | undefined
@@ -1,24 +1,57 @@
1
1
  /** Keeps Metro's transform cache and delta graph aligned with imported authoring. @module */
2
+ import * as AsyncHooks from 'node:async_hooks'
2
3
  import * as Crypto from 'node:crypto'
3
4
  import * as Fs from 'node:fs'
4
5
  import * as Path from 'node:path'
6
+ import * as Snapshot from '../node/internal/Snapshot.js'
5
7
  import type { EventEmitter } from 'node:events'
6
8
  import * as Graph from './Graph.js'
7
9
 
8
10
  type Options = {
9
- readonly platform?: string
10
11
  readonly customTransformOptions?: Readonly<Record<string, unknown>>
12
+ readonly platform?: string
11
13
  }
12
14
  type Bundler = {
13
- getDependencyGraph(): Promise<{ getWatcher(): EventEmitter }>
15
+ getDependencyGraph(): Promise<{
16
+ getWatcher(): EventEmitter
17
+ resolveDependency(
18
+ filename: string,
19
+ dependency: {
20
+ data: {
21
+ asyncType: null
22
+ isESMImport: boolean
23
+ key: string
24
+ locs: readonly never[]
25
+ }
26
+ name: string
27
+ },
28
+ platform: string,
29
+ options: { customResolverOptions: Readonly<Record<string, unknown>> },
30
+ ): { type: string; filePath?: string }
31
+ }>
14
32
  transformFile(
15
33
  filename: string,
16
34
  options: Options,
17
35
  buffer?: Buffer,
18
36
  ): Promise<unknown>
19
37
  }
38
+ type ResolverOptions = {
39
+ readonly customResolverOptions: Readonly<Record<string, unknown>>
40
+ }
41
+ type IncrementalBundler = {
42
+ buildGraphForEntries: Build
43
+ getBundler(): Bundler
44
+ getDependencies: Build
45
+ updateGraph(revision: { graph: object }, reset: boolean): Promise<unknown>
46
+ }
47
+ type Build = (
48
+ entries: readonly string[],
49
+ transform: unknown,
50
+ resolver: ResolverOptions,
51
+ options?: unknown,
52
+ ) => Promise<object>
20
53
  /** Metro server methods used by its own serializers and development middleware. */
21
- export type Server = { getBundler(): { getBundler(): Bundler } }
54
+ export type Server = { getBundler(): IncrementalBundler }
22
55
  type Changes = {
23
56
  rootDir: string
24
57
  changes: {
@@ -30,24 +63,74 @@ type Changes = {
30
63
 
31
64
  /** Includes imported source contents in transform keys and invalidates their consumers on edits. */
32
65
  export async function attach(server: Server, root: string) {
33
- const bundler = server.getBundler().getBundler()
34
- const graph = await bundler.getDependencyGraph()
66
+ const incremental = server.getBundler()
67
+ const bundler = incremental.getBundler()
68
+ const requests = new AsyncHooks.AsyncLocalStorage<ResolverOptions>()
69
+ const selections = new WeakMap<object, ResolverOptions>()
70
+ for (const name of ['buildGraphForEntries', 'getDependencies'] as const) {
71
+ const build = incremental[name].bind(incremental)
72
+ incremental[name] = (entries, transform, resolver, options) =>
73
+ requests.run(resolver, async () => {
74
+ const graph = await build(entries, transform, resolver, options)
75
+ selections.set(graph, resolver)
76
+ return graph
77
+ })
78
+ }
79
+ const update = incremental.updateGraph.bind(incremental)
80
+ incremental.updateGraph = (revision, reset) =>
81
+ requests.run(
82
+ selections.get(revision.graph) ?? { customResolverOptions: {} },
83
+ () => update(revision, reset),
84
+ )
85
+
86
+ const ready = bundler.getDependencyGraph()
35
87
  const dependencies = new Map<string, Map<string, Set<string>>>()
88
+ const snapshot = Snapshot.create()
36
89
  const transform = bundler.transformFile.bind(bundler)
37
- bundler.transformFile = (filename, options, buffer) => {
90
+ bundler.transformFile = async (filename, options, buffer) => {
38
91
  if (
39
92
  (options.platform !== 'ios' && options.platform !== 'android') ||
40
93
  !/\.[cm]?[jt]sx?$/.test(filename) ||
41
- Path.relative(root, filename).startsWith(`..${Path.sep}`) ||
42
- filename.split(Path.sep).includes('node_modules')
94
+ filename.startsWith(
95
+ `${Path.resolve(import.meta.dirname, '..')}${Path.sep}`,
96
+ )
43
97
  )
44
98
  return transform(filename, options, buffer)
45
99
  const source = buffer?.toString('utf8') ?? Fs.readFileSync(filename, 'utf8')
46
- const input = Graph.read(filename, source, options.platform, root)
100
+ const graph = await ready
101
+ const read = Graph.read(
102
+ filename,
103
+ source,
104
+ options.platform,
105
+ root,
106
+ snapshot,
107
+ (filename, specifier, isESMImport) => {
108
+ const resolved = graph.resolveDependency(
109
+ filename,
110
+ {
111
+ data: {
112
+ asyncType: null,
113
+ isESMImport,
114
+ key: specifier,
115
+ locs: [],
116
+ },
117
+ name: specifier,
118
+ },
119
+ options.platform!,
120
+ requests.getStore() ?? { customResolverOptions: {} },
121
+ )
122
+ return resolved.type === 'sourceFile' ? resolved.filePath : undefined
123
+ },
124
+ )
47
125
  const platforms =
48
126
  dependencies.get(filename) ?? new Map<string, Set<string>>()
49
- platforms.set(options.platform, new Set(input.files))
127
+ platforms.set(
128
+ JSON.stringify([options.platform, requests.getStore()]),
129
+ new Set(read.files),
130
+ )
50
131
  dependencies.set(filename, platforms)
132
+ const input = read.input
133
+ if (!input) return transform(filename, options, buffer)
51
134
  const hash = Crypto.hash('sha256', JSON.stringify(input))
52
135
  return transform(
53
136
  filename,
@@ -56,11 +139,13 @@ export async function attach(server: Server, root: string) {
56
139
  customTransformOptions: {
57
140
  ...options.customTransformOptions,
58
141
  zyzzGraph: hash,
142
+ zyzzSources: input,
59
143
  },
60
144
  },
61
145
  buffer,
62
146
  )
63
147
  }
148
+ const graph = await ready
64
149
  graph.getWatcher().prependListener('change', (event: Changes) => {
65
150
  const entries = [
66
151
  ...event.changes.addedFiles,
@@ -6,12 +6,23 @@ import * as Module from 'node:module'
6
6
  import * as Path from 'node:path'
7
7
  import * as Watch from './Watch.js'
8
8
 
9
+ type Context = {
10
+ readonly resolveRequest: Resolve
11
+ }
12
+ type Resolve = (
13
+ context: Context,
14
+ name: string,
15
+ platform: string | null,
16
+ ) => unknown
17
+
9
18
  /** Minimum Metro configuration consumed by the adapter. */
10
19
  export type Config = {
11
- /** Existing development middleware settings. */
12
- readonly server?: object | undefined
13
20
  /** Application root used for the adapter cache. */
14
21
  readonly projectRoot?: string | undefined
22
+ /** Existing package, alias, and platform resolution settings. */
23
+ readonly resolver?: object | undefined
24
+ /** Existing development middleware settings. */
25
+ readonly server?: object | undefined
15
26
  /** Existing transformer configuration, including Expo's Babel transformer. */
16
27
  readonly transformer?:
17
28
  | {
@@ -29,7 +40,13 @@ export function zyzz<const config extends Config>(
29
40
  config: config,
30
41
  options: Omit<
31
42
  NativeOptions,
32
- 'platform' | 'target' | 'moduleId' | 'modules' | 'imports' | 'colorScheme'
43
+ | 'platform'
44
+ | 'target'
45
+ | 'moduleId'
46
+ | 'modules'
47
+ | 'imports'
48
+ | 'contracts'
49
+ | 'colorScheme'
33
50
  > = {},
34
51
  ): zyzz.ReturnType<config> {
35
52
  if (Object.hasOwn(options, 'colorScheme'))
@@ -62,8 +79,42 @@ export function zyzz<const config extends Config>(
62
79
  | ((middleware: Middleware, server: Watch.Server) => Middleware)
63
80
  | undefined)
64
81
  type Middleware = (...args: unknown[]) => unknown
82
+ const resolve =
83
+ config.resolver &&
84
+ (Reflect.get(config.resolver, 'resolveRequest') as Resolve | undefined)
85
+
65
86
  return {
66
87
  ...config,
88
+ resolver: {
89
+ ...config.resolver,
90
+ resolveRequest(context: Context, name: string, platform: string | null) {
91
+ const next = resolve ?? context.resolveRequest
92
+ try {
93
+ return next(context, name, platform)
94
+ } catch (error) {
95
+ if (
96
+ (platform !== 'ios' && platform !== 'android') ||
97
+ !name.startsWith('.') ||
98
+ !name.endsWith('.js') ||
99
+ !(error instanceof Error && 'candidates' in error)
100
+ )
101
+ throw error
102
+
103
+ // NodeNext imports name emitted JavaScript; source packages retain TypeScript files.
104
+ const source = next(context, name.slice(0, -3), platform)
105
+ if (
106
+ source &&
107
+ typeof source === 'object' &&
108
+ 'filePath' in source &&
109
+ typeof source.filePath === 'string' &&
110
+ /\.tsx?$/.test(source.filePath)
111
+ )
112
+ return source
113
+
114
+ throw error
115
+ }
116
+ },
117
+ },
67
118
  server: {
68
119
  ...config.server,
69
120
  enhanceMiddleware(middleware: Middleware, server: Watch.Server) {
@@ -89,7 +140,14 @@ export function zyzz<const config extends Config>(
89
140
  /** Configuration returned by the Metro adapter. */
90
141
  export declare namespace zyzz {
91
142
  /** Preserves caller configuration fields and the existing transformer options. */
92
- type ReturnType<config extends Config> = Omit<config, 'transformer'> & {
143
+ type ReturnType<config extends Config> = Omit<
144
+ config,
145
+ 'resolver' | 'transformer'
146
+ > & {
147
+ /** Existing resolver options with the chained native source resolver. */
148
+ resolver: Omit<NonNullable<config['resolver']>, 'resolveRequest'> & {
149
+ resolveRequest: Resolve
150
+ }
93
151
  /** Existing transformer configuration with the native compilation entrypoint. */
94
152
  transformer: Omit<
95
153
  NonNullable<config['transformer']>,
@@ -13,7 +13,12 @@ import { type NativeOptions, zyzz } from '../babel/index.js'
13
13
  /** Babel transformer inputs passed through without removing upstream fields. */
14
14
  type Input = {
15
15
  readonly filename: string
16
- readonly options: { readonly platform?: string | undefined }
16
+ readonly options: {
17
+ readonly platform?: string | undefined
18
+ readonly customTransformOptions?:
19
+ | { readonly zyzzSources?: Graph.Input | undefined }
20
+ | undefined
21
+ }
17
22
  readonly plugins?: readonly Babel.PluginItem[] | undefined
18
23
  readonly src: string
19
24
  }
@@ -28,7 +33,13 @@ export function create(
28
33
  upstreamPath: string,
29
34
  options: Omit<
30
35
  NativeOptions,
31
- 'platform' | 'target' | 'moduleId' | 'modules' | 'imports' | 'colorScheme'
36
+ | 'platform'
37
+ | 'target'
38
+ | 'moduleId'
39
+ | 'modules'
40
+ | 'imports'
41
+ | 'contracts'
42
+ | 'colorScheme'
32
43
  > & { readonly root: string },
33
44
  ) {
34
45
  const compilers = new Map<string, ReturnType<typeof Compiler.create>>()
@@ -78,18 +89,17 @@ export function create(
78
89
  if (
79
90
  (platform !== 'ios' && platform !== 'android') ||
80
91
  !/\.[cm]?[jt]sx?$/.test(input.filename) ||
81
- input.filename.split(/[\\/]/).includes('node_modules') ||
82
- Path.relative(options.root, filename).startsWith(`..${Path.sep}`)
92
+ filename.startsWith(
93
+ `${Path.resolve(import.meta.dirname, '..')}${Path.sep}`,
94
+ )
83
95
  )
84
96
  return upstream.transform(input)
85
97
 
86
- const graph = Graph.read(
87
- filename,
88
- input.src,
89
- platform,
90
- options.root,
91
- snapshot,
92
- )
98
+ const graph =
99
+ input.options.customTransformOptions?.zyzzSources ??
100
+ Graph.read(filename, input.src, platform, options.root, snapshot).input
101
+ if (!graph) return upstream.transform(input)
102
+
93
103
  const key = `${platform}:${filename}`
94
104
  let compiler = compilers.get(key)
95
105
  if (!compiler) {