expo-modules-macros 0.22.0 → 0.23.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/.github/workflows/linux.yml +8 -5
- package/.github/workflows/publish.yml +26 -11
- package/.github/workflows/swift-format.yml +4 -1
- package/.github/workflows/swift.yml +7 -4
- package/.github/workflows/windows.yml +8 -5
- package/AGENTS.md +12 -10
- package/README.md +7 -5
- package/apple/ExpoModulesMacros +0 -0
- package/apple/Sources/ExpoModulesMacros/DecorateModuleBuilder.swift +32 -5
- package/apple/Sources/ExpoModulesMacros/ExpoModuleMacro.swift +14 -27
- package/apple/Sources/ExpoModulesMacros/JSMacro.swift +4 -4
- package/apple/Sources/ExpoModulesMacros/MacroHelpers.swift +9 -1
- package/apple/Sources/ExpoModulesMacros/RecordMacro.swift +1 -1
- package/apple/Sources/ExpoModulesMacros/SharedObjectMacro.swift +13 -33
- package/apple/Sources/ExpoModulesScanner/Exports/ExportedSurface.swift +17 -2
- package/apple/Sources/ExpoModulesScanner/Exports/ResolveRefs.swift +3 -0
- package/apple/Sources/ExpoModulesScanner/Exports/SurfaceVisitor.swift +52 -1
- package/apple/Sources/ExpoModulesScanner/Exports/TypeNode.swift +26 -7
- package/build/index.d.ts +1 -1
- package/build/index.js +3 -3
- package/build/types.d.ts +25 -1
- package/build/types.js +1 -1
- package/package.json +6 -5
|
@@ -50,6 +50,9 @@ jobs:
|
|
|
50
50
|
with:
|
|
51
51
|
node-version: 24
|
|
52
52
|
|
|
53
|
+
- name: Setup pnpm
|
|
54
|
+
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
55
|
+
|
|
53
56
|
- name: Cache SwiftPM build
|
|
54
57
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
55
58
|
with:
|
|
@@ -65,10 +68,10 @@ jobs:
|
|
|
65
68
|
run: swift test
|
|
66
69
|
|
|
67
70
|
- name: Build release tool
|
|
68
|
-
# Runs `
|
|
71
|
+
# Runs `pnpm run build` (scripts/build.js): builds the release plugin binary for this
|
|
69
72
|
# architecture as platforms/linux-<arch>/ExpoModulesMacros.
|
|
70
73
|
working-directory: ${{ github.workspace }}
|
|
71
|
-
run:
|
|
74
|
+
run: pnpm run build
|
|
72
75
|
|
|
73
76
|
- name: Verify the release tool
|
|
74
77
|
# With no arguments it is the plugin server, which exits when stdin closes; with arguments it
|
|
@@ -125,8 +128,8 @@ jobs:
|
|
|
125
128
|
# `@expo/modules-macros-linux-<arch>`, as it would in a project, not the local build.
|
|
126
129
|
working-directory: ${{ github.workspace }}
|
|
127
130
|
run: |
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
pnpm install
|
|
132
|
+
pnpm add --save-optional "$PLATFORM_PACKAGE_TARBALL"
|
|
133
|
+
pnpm run build:ts
|
|
131
134
|
node scripts/check-scan.js .
|
|
132
135
|
|
|
@@ -56,10 +56,13 @@ jobs:
|
|
|
56
56
|
with:
|
|
57
57
|
node-version: 24
|
|
58
58
|
|
|
59
|
+
- name: Setup pnpm
|
|
60
|
+
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
61
|
+
|
|
59
62
|
- name: Build release tool
|
|
60
63
|
# Runs scripts/build.js: builds the release plugin binary for this architecture as
|
|
61
64
|
# platforms/win32-<arch>/ExpoModulesMacros.exe.
|
|
62
|
-
run:
|
|
65
|
+
run: pnpm run build
|
|
63
66
|
|
|
64
67
|
- name: Upload tool
|
|
65
68
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
@@ -94,10 +97,13 @@ jobs:
|
|
|
94
97
|
with:
|
|
95
98
|
node-version: 24
|
|
96
99
|
|
|
100
|
+
- name: Setup pnpm
|
|
101
|
+
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
102
|
+
|
|
97
103
|
- name: Build release tool
|
|
98
104
|
# Runs scripts/build.js: builds the release plugin binary for this architecture as
|
|
99
105
|
# platforms/linux-<arch>/ExpoModulesMacros.
|
|
100
|
-
run:
|
|
106
|
+
run: pnpm run build
|
|
101
107
|
|
|
102
108
|
- name: Upload tool
|
|
103
109
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
@@ -130,6 +136,9 @@ jobs:
|
|
|
130
136
|
with:
|
|
131
137
|
node-version: 24
|
|
132
138
|
|
|
139
|
+
- name: Setup pnpm
|
|
140
|
+
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
141
|
+
|
|
133
142
|
- name: Download tool
|
|
134
143
|
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
135
144
|
with:
|
|
@@ -142,11 +151,11 @@ jobs:
|
|
|
142
151
|
# The `./` matters: npm reads `platforms/win32-<arch>` as a GitHub repository.
|
|
143
152
|
$pack = npm pack "./platforms/win32-${{ matrix.arch }}" --pack-destination $env:RUNNER_TEMP --json | ConvertFrom-Json
|
|
144
153
|
if ($LASTEXITCODE -ne 0) { throw "npm pack failed" }
|
|
145
|
-
|
|
154
|
+
pnpm install
|
|
146
155
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
147
|
-
|
|
156
|
+
pnpm add --save-optional (Join-Path $env:RUNNER_TEMP $pack[0].filename)
|
|
148
157
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
149
|
-
|
|
158
|
+
pnpm run build:ts
|
|
150
159
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
151
160
|
node scripts/check-scan.js .
|
|
152
161
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
@@ -178,6 +187,9 @@ jobs:
|
|
|
178
187
|
with:
|
|
179
188
|
node-version: 24
|
|
180
189
|
|
|
190
|
+
- name: Setup pnpm
|
|
191
|
+
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
192
|
+
|
|
181
193
|
- name: Download tool
|
|
182
194
|
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
183
195
|
with:
|
|
@@ -191,9 +203,9 @@ jobs:
|
|
|
191
203
|
# The `./` matters: npm reads `platforms/linux-<arch>` as a GitHub repository.
|
|
192
204
|
npm pack "./platforms/linux-${{ matrix.arch }}" --pack-destination "$RUNNER_TEMP" --json > "$RUNNER_TEMP/pack.json"
|
|
193
205
|
tarball="$(node -p 'JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8"))[0].filename' "$RUNNER_TEMP/pack.json")"
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
206
|
+
pnpm install
|
|
207
|
+
pnpm add --save-optional "$RUNNER_TEMP/$tarball"
|
|
208
|
+
pnpm run build:ts
|
|
197
209
|
|
|
198
210
|
- name: Scan on each distribution
|
|
199
211
|
run: |
|
|
@@ -233,6 +245,9 @@ jobs:
|
|
|
233
245
|
node-version: 24
|
|
234
246
|
registry-url: https://registry.npmjs.org
|
|
235
247
|
|
|
248
|
+
- name: Setup pnpm
|
|
249
|
+
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
250
|
+
|
|
236
251
|
- name: Update npm
|
|
237
252
|
# Trusted publishing requires npm >= 11.5.1.
|
|
238
253
|
run: npm install -g npm@latest
|
|
@@ -280,18 +295,18 @@ jobs:
|
|
|
280
295
|
|
|
281
296
|
- name: Install dependencies
|
|
282
297
|
# The wrapper's `prepublishOnly` build needs `tsc`; the tool binary needs none.
|
|
283
|
-
run:
|
|
298
|
+
run: pnpm install
|
|
284
299
|
|
|
285
300
|
- name: Build release tool
|
|
286
301
|
# Runs scripts/build.js: builds the release plugin binary,
|
|
287
302
|
# copies it to apple/ExpoModulesMacros, and strips it.
|
|
288
|
-
run:
|
|
303
|
+
run: pnpm run build
|
|
289
304
|
|
|
290
305
|
- name: Verify the macOS tool
|
|
291
306
|
# Scans through the TypeScript wrapper, which must find the binary in `apple/`, before
|
|
292
307
|
# anything is published. The verify-windows and verify-linux jobs check the other binaries.
|
|
293
308
|
run: |
|
|
294
|
-
|
|
309
|
+
pnpm run build:ts
|
|
295
310
|
node scripts/check-scan.js .
|
|
296
311
|
|
|
297
312
|
- name: Download Windows and Linux tools
|
|
@@ -37,6 +37,9 @@ jobs:
|
|
|
37
37
|
with:
|
|
38
38
|
node-version: 24
|
|
39
39
|
|
|
40
|
+
- name: Setup pnpm
|
|
41
|
+
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
42
|
+
|
|
40
43
|
- name: Restore swift-format
|
|
41
44
|
id: swift-format-cache
|
|
42
45
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
@@ -73,4 +76,4 @@ jobs:
|
|
|
73
76
|
# The script lists the files with `git ls-files`. The checkout belongs to another user than
|
|
74
77
|
# the container's root user, so Git needs it marked as safe.
|
|
75
78
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
76
|
-
|
|
79
|
+
pnpm run swift:lint
|
|
@@ -24,6 +24,9 @@ jobs:
|
|
|
24
24
|
- name: Checkout
|
|
25
25
|
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
26
26
|
|
|
27
|
+
- name: Setup pnpm
|
|
28
|
+
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
29
|
+
|
|
27
30
|
- name: Select Xcode
|
|
28
31
|
# Pin to an Xcode that ships Swift 6.2 (required by Package.swift).
|
|
29
32
|
run: sudo xcode-select -switch /Applications/Xcode_26.4.1.app
|
|
@@ -40,11 +43,11 @@ jobs:
|
|
|
40
43
|
run: swift package resolve
|
|
41
44
|
|
|
42
45
|
- name: Build release tool
|
|
43
|
-
# Runs `
|
|
46
|
+
# Runs `pnpm run build` (scripts/build.js): builds the universal
|
|
44
47
|
# (arm64 + x86_64) release plugin binary, copies it to
|
|
45
48
|
# apple/ExpoModulesMacros, and strips it.
|
|
46
49
|
working-directory: ${{ github.workspace }}
|
|
47
|
-
run:
|
|
50
|
+
run: pnpm run build
|
|
48
51
|
|
|
49
52
|
- name: Verify universal binary
|
|
50
53
|
run: |
|
|
@@ -64,5 +67,5 @@ jobs:
|
|
|
64
67
|
- name: Typecheck the TypeScript wrapper
|
|
65
68
|
working-directory: ${{ github.workspace }}
|
|
66
69
|
run: |
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
pnpm install
|
|
71
|
+
pnpm run typecheck
|
|
@@ -50,6 +50,9 @@ jobs:
|
|
|
50
50
|
with:
|
|
51
51
|
node-version: 24
|
|
52
52
|
|
|
53
|
+
- name: Setup pnpm
|
|
54
|
+
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
55
|
+
|
|
53
56
|
- name: Cache SwiftPM build
|
|
54
57
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
55
58
|
with:
|
|
@@ -65,10 +68,10 @@ jobs:
|
|
|
65
68
|
run: swift test
|
|
66
69
|
|
|
67
70
|
- name: Build release tool
|
|
68
|
-
# Runs `
|
|
71
|
+
# Runs `pnpm run build` (scripts/build.js): builds the release plugin binary for this
|
|
69
72
|
# architecture as platforms/win32-<arch>/ExpoModulesMacros.exe.
|
|
70
73
|
working-directory: ${{ github.workspace }}
|
|
71
|
-
run:
|
|
74
|
+
run: pnpm run build
|
|
72
75
|
|
|
73
76
|
- name: Verify the release tool
|
|
74
77
|
# Runs it with only the Windows directories on PATH, so it must not need the Swift runtime DLLs.
|
|
@@ -109,11 +112,11 @@ jobs:
|
|
|
109
112
|
working-directory: ${{ github.workspace }}
|
|
110
113
|
shell: pwsh
|
|
111
114
|
run: |
|
|
112
|
-
|
|
115
|
+
pnpm install
|
|
113
116
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
114
|
-
|
|
117
|
+
pnpm add --save-optional $env:PLATFORM_PACKAGE_TARBALL
|
|
115
118
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
116
|
-
|
|
119
|
+
pnpm run build:ts
|
|
117
120
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
118
121
|
node scripts/check-scan.js .
|
|
119
122
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
package/AGENTS.md
CHANGED
|
@@ -9,26 +9,27 @@
|
|
|
9
9
|
- `apple/Sources/ExpoModulesScanner`: the scanner library. `Modules/` implements `scan-modules` (autolinking), `Exports/` implements `scan-exports` (type generation), `Core/` holds the shared parsing and `#if` evaluation, and `CLI.swift` is the command-line front end.
|
|
10
10
|
- `apple/Sources/ExpoModulesOptimized`: declarations for the `@OptimizedFunction` macro.
|
|
11
11
|
- `apple/Tests`: `ExpoModulesMacrosTests` (expansion tests) and `ExpoModulesScannerTests`.
|
|
12
|
-
- `platforms/`: the Windows packages `@expo/modules-macros-win32-x64` and `-arm64`, and the Linux packages `@expo/modules-macros-linux-x64` and `-arm64` (a `package.json` with `os` and `cpu`, plus `libc` on Linux, a README and a copy of `LICENSE`). The Publish workflow runs `scripts/set-platform-versions.js` to give them the version of this package. Their `ExpoModulesMacros.exe` (Windows) or `ExpoModulesMacros` (Linux) is written there by `
|
|
12
|
+
- `platforms/`: the Windows packages `@expo/modules-macros-win32-x64` and `-arm64`, and the Linux packages `@expo/modules-macros-linux-x64` and `-arm64` (a `package.json` with `os` and `cpu`, plus `libc` on Linux, a README and a copy of `LICENSE`). The Publish workflow runs `scripts/set-platform-versions.js` to give them the version of this package. Their `ExpoModulesMacros.exe` (Windows) or `ExpoModulesMacros` (Linux) is written there by `pnpm run build` on that platform and isn't committed.
|
|
13
13
|
- `apple/PodTests`: a stub test so that `expo/expo` native tests can install this package as a pod. Do not add real tests here.
|
|
14
14
|
- `src/`: the TypeScript wrapper around the scanner CLI. `types.ts` mirrors the Swift `Codable` output types by hand.
|
|
15
|
-
- `scripts/build.js` (`
|
|
16
|
-
- `scripts/swift-format.js` (`
|
|
15
|
+
- `scripts/build.js` (`pnpm run build`): builds the release binaries: the universal macOS `apple/ExpoModulesMacros` (committed and published), and on Windows and Linux `platforms/win32-<arch>/ExpoModulesMacros.exe` or `platforms/linux-<arch>/ExpoModulesMacros` for the host architecture (not committed; published in that package).
|
|
16
|
+
- `scripts/swift-format.js` (`pnpm run swift:format`, `pnpm run swift:lint`): runs swift-format on the `.swift` files that Git tracks or doesn't ignore, with the configuration in `.swift-format`.
|
|
17
17
|
|
|
18
18
|
## Commands
|
|
19
19
|
|
|
20
|
-
Swift 6.2 is required (Xcode 26 or newer on macOS).
|
|
20
|
+
Swift 6.2 is required (Xcode 26 or newer on macOS). The package manager is pnpm, at the version in `packageManager` in `package.json`.
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
23
|
cd apple && swift build # debug build
|
|
24
24
|
cd apple && swift test # all Swift tests
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
pnpm install # TypeScript dependencies
|
|
26
|
+
pnpm run typecheck # TypeScript wrapper
|
|
27
|
+
pnpm run build # release binary (slow; on macOS uses Rosetta for x86_64)
|
|
28
|
+
pnpm run swift:format # format the Swift sources in place
|
|
29
|
+
pnpm run swift:lint # check the formatting, as CI does
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
CI runs on macOS (`.github/workflows/swift.yml`), on Windows x64 and arm64 (`.github/workflows/windows.yml`), and on Linux x64 and arm64 (`.github/workflows/linux.yml`). On macOS it runs the release build, checks both binary slices, then runs `swift test` and `
|
|
32
|
+
CI runs on macOS (`.github/workflows/swift.yml`), on Windows x64 and arm64 (`.github/workflows/windows.yml`), and on Linux x64 and arm64 (`.github/workflows/linux.yml`). On macOS it runs the release build, checks both binary slices, then runs `swift test` and `pnpm run typecheck`. On Windows it builds and tests the package, builds the release binary, checks that it starts without the Swift runtime, packs it into its `platforms/` package (checking the files and the `os`/`cpu` fields), installs that package, and scans through the TypeScript wrapper with `scripts/check-scan.js`, which must find the executable there. On Linux it does the same in the `swift:6.4.0-rhel-ubi9` image, and also checks that no Swift library is linked dynamically, the newest glibc symbol version that the binary needs, and that the package keeps the executable bit. The Swift Format workflow (`.github/workflows/swift-format.yml`) builds the pinned swift-format version and runs `pnpm run swift:lint`. Code in `Sources` and `Tests` must not assume Apple platforms or POSIX paths.
|
|
32
33
|
|
|
33
34
|
## Windows
|
|
34
35
|
|
|
@@ -42,7 +43,7 @@ CI runs on macOS (`.github/workflows/swift.yml`), on Windows x64 and arm64 (`.gi
|
|
|
42
43
|
|
|
43
44
|
## Conventions
|
|
44
45
|
|
|
45
|
-
- swift-format formats the Swift sources. After you change Swift code, run `
|
|
46
|
+
- swift-format formats the Swift sources. After you change Swift code, run `pnpm run swift:format` and then `pnpm run swift:lint`, which also reports the rules that the formatter doesn't fix. `.swift-format` is a copy of the configuration in `expo/expo`, so keep the two the same. Each repository pins its own swift-format version.
|
|
46
47
|
- The scripts run the swift-format on `PATH`, or on macOS the one in Xcode when there is none. CI builds the version in `SWIFT_FORMAT_VERSION` (`.github/workflows/swift-format.yml`, also named in the README), which can format differently from the one in Xcode. When CI reports a finding that the local lint doesn't, fix it as CI reports it.
|
|
47
48
|
- Write new doc comments as `///` line comments, not `/** */` blocks.
|
|
48
49
|
- Tests use Swift Testing (`import Testing`, `@Test`) with backtick-quoted descriptive names, for example ``func `Prunes .build, Pods, .git, and node_modules directories`()``.
|
|
@@ -68,6 +69,7 @@ CI runs on macOS (`.github/workflows/swift.yml`), on Windows x64 and arm64 (`.gi
|
|
|
68
69
|
|
|
69
70
|
- Do not rebuild or commit the binary under `apple/` in a feature change. Only the **Publish** workflow (`.github/workflows/publish.yml`, manual) rebuilds it, and it commits the result as `Release vX.Y.Z` after the npm publish succeeds. It also builds the Windows and Linux binaries and publishes them in the `platforms/` packages, with the same version. Before it publishes anything, it installs each binary from its packed package and scans through the TypeScript wrapper with `scripts/check-scan.js`.
|
|
70
71
|
- Do not edit the `version` in `package.json` by hand. The Publish workflow bumps it.
|
|
72
|
+
- The workflows install dependencies and run scripts with pnpm, but they pack, version and publish with npm (`npm pack`, `npm version`, `npm publish`), because the packages use npm trusted publishing (OIDC). Keep those commands on npm.
|
|
71
73
|
- Commit and PR titles are short and imperative, with code identifiers in backticks, for example ``Report `@Union` types in `scan-exports` ``.
|
|
72
74
|
|
|
73
75
|
## Installed copies
|
package/README.md
CHANGED
|
@@ -25,9 +25,9 @@ This package lists them as optional dependencies, and their `os`, `cpu` and (on
|
|
|
25
25
|
# Macros
|
|
26
26
|
|
|
27
27
|
- **`@ExpoModule(_ name: String? = nil, classes: [Any.Type] = [])`** on a class. Turns the class into a module: binds its `@JS` members into the module's JavaScript object and resolves the module name from the argument, falling back to the class name. It also synthesizes everything inheriting from `Module` used to provide, so a module class can carry any superclass, or none.
|
|
28
|
-
- **`@JS(_ jsName: String? = nil, _ options: JSOptions...)`** on a member of a module or shared object. Marks a function, property or initializer for export. `@ExpoModule` and `@SharedObject` bind each marked member straight into the JavaScript object, with the argument decoding, the call and the result encoding inlined, so there is no dynamic per-call path. The JS name defaults to the Swift name; pass a string to override it. Two `@JS` members with the same JS name on one JavaScript object are a compile-time error, since JavaScript could reach only one of them. A shared object's static and instance members bind onto different objects, so they can share a name. The macro also checks that every type crossing the boundary is convertible in the direction it travels, so a bad type is reported on the author's own declaration rather than on the enclosing type. The `.concurrent` option moves an `async` body off the JavaScript thread while still decoding and encoding on it. The `.withAsync("name")` option binds a synchronous function a second time under that name, as a function that returns a promise and runs the body on the concurrent thread pool. That function isn't isolated to the JavaScript thread, so its body must be safe to run on any thread. The macro reports a function isolated to a global actor through its own attribute or its type's. When the isolation comes from elsewhere, such as a protocol or the module's default isolation, the error points into the generated code, so mark the function `nonisolated`. A function or initializer can take a closure argument, such as `(Point) throws -> Size` or `(String) async throws -> Data`. JavaScript passes a function for it, and native code can store the closure and call it from any thread. A closure that throws or returns a value blocks the caller until JavaScript returns, and an `async` closure suspends instead and awaits a promise that the function returns. A closure that returns a value must be `throws`, so that an error from JavaScript can reach the caller. A non-throwing `Void` closure returns before JavaScript reads its arguments, so don't change a class instance after passing it to one.
|
|
28
|
+
- **`@JS(_ jsName: String? = nil, _ options: JSOptions...)`** on a member of a module or shared object. Marks a function, property or initializer for export. `@ExpoModule` and `@SharedObject` bind each marked member straight into the JavaScript object, with the argument decoding, the call and the result encoding inlined, so there is no dynamic per-call path. The JS name defaults to the Swift name; pass a string to override it. Two `@JS` members with the same JS name on one JavaScript object are a compile-time error, since JavaScript could reach only one of them. A shared object's static and instance members bind onto different objects, so they can share a name. The macro also checks that every type crossing the boundary is convertible in the direction it travels, so a bad type is reported on the author's own declaration rather than on the enclosing type. A `lazy var` is read-only from JavaScript. On the first read, its getter stores the value as a data property on the JavaScript object, so later reads don't call into native code. On a frozen object the value isn't stored, and each read calls the getter. The `.concurrent` option moves an `async` body off the JavaScript thread while still decoding and encoding on it. The `.withAsync("name")` option binds a synchronous function a second time under that name, as a function that returns a promise and runs the body on the concurrent thread pool. That function isn't isolated to the JavaScript thread, so its body must be safe to run on any thread. The macro reports a function isolated to a global actor through its own attribute or its type's. When the isolation comes from elsewhere, such as a protocol or the module's default isolation, the error points into the generated code, so mark the function `nonisolated`. A function or initializer can take a closure argument, such as `(Point) throws -> Size` or `(String) async throws -> Data`. JavaScript passes a function for it, and native code can store the closure and call it from any thread. A closure that throws or returns a value blocks the caller until JavaScript returns, and an `async` closure suspends instead and awaits a promise that the function returns. A closure that returns a value must be `throws`, so that an error from JavaScript can reach the caller. A non-throwing `Void` closure returns before JavaScript reads its arguments, so don't change a class instance after passing it to one.
|
|
29
29
|
- **`@Event(_ name: String? = nil, sync: Bool = false)`** on a function-typed `var`. Expands the property into a closure that emits the event, so calling the property sends it to JavaScript with the closure's parameter as the payload. The JS name defaults to the property name with a leading `on` stripped. `sync: true` dispatches inline instead of asynchronously.
|
|
30
|
-
- **`@SharedObject(_ name: String? = nil)`** on a `SharedObject` subclass.
|
|
30
|
+
- **`@SharedObject(_ name: String? = nil)`** on a subclass of `SharedObject`, direct or not, such as a `SharedRef<T>` subclass. Binds the class's `@JS` members through the `SharedObject` hooks that core calls when it builds the JavaScript class, and emits the JS class name, which defaults to the class name. A module exposes the class through `@ExpoModule(classes:)`, which only accepts `SharedObject` subclasses.
|
|
31
31
|
- **`@Record()`** on a struct, or on a final class with no inheritance clause (declare a class's protocol conformances in an extension). Treats every non-static, non-private, non-computed stored property as a field, with no per-field wrapper, and synthesizes the memberwise initializer, the conversions in both directions, and the `Record` conformance. Requiredness is inferred: a default value makes a field optional, an optional type makes it nullable and optional.
|
|
32
32
|
- **`@Union()`** on an enum whose cases each carry one associated value. Models a TypeScript union. Synthesizes the conversions in both directions, a typed accessor per payload type, and the `JavaScriptDecodable` and `JavaScriptEncodable` conformances. Decoding is ordered: the first case whose payload decodes wins, so the more specific case goes first.
|
|
33
33
|
|
|
@@ -102,7 +102,7 @@ The binary is a compiled executable, so each call still spawns a process. What t
|
|
|
102
102
|
|
|
103
103
|
to `OTHER_SWIFT_FLAGS` for `ExpoModulesCore`, every pod that depends on it, and their test specs. Expo's SPM prebuilds pass the same flag when they generate `Package.swift`, so both build systems load the same binary.
|
|
104
104
|
|
|
105
|
-
On Windows and Linux, the compiler loads `ExpoModulesMacros.exe` from `@expo/modules-macros-win32-<arch>` or `ExpoModulesMacros` from `@expo/modules-macros-linux-<arch>` with the same flag. `getScannerBinaryPath()` in the TypeScript wrapper returns the binary for the current platform and architecture. On Windows and Linux it falls back to the local `
|
|
105
|
+
On Windows and Linux, the compiler loads `ExpoModulesMacros.exe` from `@expo/modules-macros-win32-<arch>` or `ExpoModulesMacros` from `@expo/modules-macros-linux-<arch>` with the same flag. `getScannerBinaryPath()` in the TypeScript wrapper returns the binary for the current platform and architecture. On Windows and Linux it falls back to the local `pnpm run build` output in `platforms/<platform>-<arch>` when the platform package isn't installed, for example in this repository.
|
|
106
106
|
|
|
107
107
|
The module and type names in `#externalMacro` must stay in sync with `apple/Sources/ExpoModulesMacros/Plugin.swift`.
|
|
108
108
|
|
|
@@ -116,7 +116,9 @@ swift build
|
|
|
116
116
|
swift test
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
The
|
|
119
|
+
The scripts and the TypeScript wrapper use [pnpm](https://pnpm.io), at the version in `packageManager` in `package.json`. Run `pnpm install` to install the TypeScript dependencies, and `pnpm run typecheck` to check the wrapper.
|
|
120
|
+
|
|
121
|
+
The Swift sources are formatted with [swift-format](https://github.com/swiftlang/swift-format), using the same configuration (`.swift-format`) as the `expo/expo` repository. `pnpm run swift:format` formats the `.swift` files that Git tracks or doesn't ignore in place, and `pnpm run swift:lint` checks them without changes, as CI does.
|
|
120
122
|
|
|
121
123
|
The scripts run the `swift-format` on `PATH`, or on macOS the one in the selected Xcode when there is none on `PATH`. CI uses swift-format `604.0.0`, which can format differently from the one in Xcode. To get the version that CI uses, build it from source:
|
|
122
124
|
|
|
@@ -129,7 +131,7 @@ cp "$(cd /tmp/swift-format && swift build -c release --show-bin-path)/swift-form
|
|
|
129
131
|
|
|
130
132
|
Then add `~/.local/bin` to `PATH`.
|
|
131
133
|
|
|
132
|
-
`
|
|
134
|
+
`pnpm run build` runs `scripts/build.js`, which builds the release binary with SwiftPM's native build system (Swift Build, the default since Swift 6.4, doesn't build a macro tool that no target in the package uses).
|
|
133
135
|
|
|
134
136
|
- On macOS, it builds for arm64 and x86_64, merges the slices into `apple/ExpoModulesMacros` with `lipo`, strips it, and verifies both slices are present. SwiftPM only builds macro tools for the host architecture, so the x86_64 slice is produced by running the toolchain under Rosetta; the script installs Rosetta if it is missing.
|
|
135
137
|
- On Windows, it builds for the host architecture only, with the Swift runtime linked in statically and without debug info, and writes `platforms/win32-<arch>/ExpoModulesMacros.exe` (`x64` or `arm64`, as Node's `process.arch` names them), the file that the package for that architecture publishes. It strips the executable with `llvm-strip` from the Swift toolchain and checks the architecture in its header.
|
package/apple/ExpoModulesMacros
CHANGED
|
Binary file
|
|
@@ -363,6 +363,13 @@ internal struct JSFunction {
|
|
|
363
363
|
/// writes `<callee>.<name> = …`. Decode/encode of the value go through `JavaScriptDecodable.decode` /
|
|
364
364
|
/// `JavaScriptEncodable.encode` (or the free-form methods for a type that mentions `Any`), the same
|
|
365
365
|
/// path as functions.
|
|
366
|
+
///
|
|
367
|
+
/// A `lazy var` binds a getter only, on a configurable accessor. On the first read the getter defines
|
|
368
|
+
/// the encoded value as a data property on the JS `this` with `defineProperty`, so later reads don't
|
|
369
|
+
/// call into native code. The data property goes on `this`, not on the object that holds the accessor,
|
|
370
|
+
/// so each shared object instance stores its own value. `defineProperty` leaves a frozen object
|
|
371
|
+
/// unchanged, so there the property stays an accessor. A `this` that isn't an object doesn't fail the read
|
|
372
|
+
/// either: the getter still returns the value.
|
|
366
373
|
internal struct JSProperty {
|
|
367
374
|
let swiftName: String
|
|
368
375
|
let jsName: String
|
|
@@ -373,8 +380,10 @@ internal struct JSProperty {
|
|
|
373
380
|
/// How the value converts when its type mentions `Any`, or `nil` for any other type.
|
|
374
381
|
let freeForm: FreeFormConversion?
|
|
375
382
|
/// Whether the property is settable from JS: `true` for a stored `var` or a computed `var` with an
|
|
376
|
-
/// explicit `set` accessor; `false` for a getter-only computed `var` or a `let`.
|
|
383
|
+
/// explicit `set` accessor; `false` for a getter-only computed `var`, a `lazy var` or a `let`.
|
|
377
384
|
let isSettable: Bool
|
|
385
|
+
/// Whether the property is a `lazy var`, whose getter replaces the accessor with the first value.
|
|
386
|
+
let isLazy: Bool
|
|
378
387
|
/// The property's name in the source, where a JavaScript name collision is reported.
|
|
379
388
|
let nameToken: TokenSyntax
|
|
380
389
|
|
|
@@ -395,18 +404,36 @@ internal struct JSProperty {
|
|
|
395
404
|
|
|
396
405
|
lines.append("let \(descriptorName) = runtime.createObject()")
|
|
397
406
|
lines.append("\(descriptorName).setProperty(\"enumerable\", value: true)")
|
|
407
|
+
if isLazy {
|
|
408
|
+
// The getter redefines the property on `this`, which needs a configurable accessor when `this`
|
|
409
|
+
// is the object that holds it (a module).
|
|
410
|
+
lines.append("\(descriptorName).setProperty(\"configurable\", value: true)")
|
|
411
|
+
}
|
|
398
412
|
|
|
399
413
|
// Getter: read `<callee>.<name>` and encode the result back to JS through `encode`. When the value
|
|
400
414
|
// type couldn't be inferred (no annotation and no literal default, rare for a stored var) there's
|
|
401
415
|
// no static type to call `encode` on, so the value's own `toJavaScriptValue(in:)` is the fallback.
|
|
402
|
-
let
|
|
416
|
+
let encode: String
|
|
403
417
|
if let valueType {
|
|
404
|
-
|
|
418
|
+
encode = "try \(encodeCall(valueType, freeForm: freeForm, of: "\(callee).\(swiftName)"))"
|
|
419
|
+
} else {
|
|
420
|
+
encode = "\(callee).\(swiftName).toJavaScriptValue(in: runtime)"
|
|
421
|
+
}
|
|
422
|
+
let getBody: String
|
|
423
|
+
if isLazy {
|
|
424
|
+
let define = "this.getObject(in: runtime).defineProperty(\"\(jsName)\", value: value, options: [.enumerable])"
|
|
425
|
+
// A module getter ignores `this` otherwise, so it can be anything, and only an object can hold the
|
|
426
|
+
// value. A shared object's unwrap has already checked that `this` is an object.
|
|
427
|
+
let defineStatement =
|
|
428
|
+
receiver.unwrapStatement == nil
|
|
429
|
+
? "if this.isObject() {\n \(define)\n}"
|
|
430
|
+
: define
|
|
431
|
+
getBody = "let value = \(encode)\n\(defineStatement)\nreturn value"
|
|
405
432
|
} else {
|
|
406
|
-
|
|
433
|
+
getBody = "return \(encode)"
|
|
407
434
|
}
|
|
408
435
|
lines.append(
|
|
409
|
-
accessorClosure(descriptorName, "get", receiver: receiver, body: "\(unwrap)\(
|
|
436
|
+
accessorClosure(descriptorName, "get", receiver: receiver, body: "\(unwrap)\(getBody)")
|
|
410
437
|
)
|
|
411
438
|
|
|
412
439
|
// Setter: decode argument 0 by the static type through `decode` and write `<callee>.<name>`. A
|
|
@@ -7,10 +7,9 @@ import SwiftSyntaxMacros
|
|
|
7
7
|
///
|
|
8
8
|
/// - `MemberMacro`: binds `@JS`-marked members directly into the module's JS object via the
|
|
9
9
|
/// synthesized `_decorateModule`, and emits the resolved module name as a non-optional
|
|
10
|
-
/// `_jsName` static (no `Name(…)` DSL element).
|
|
11
|
-
///
|
|
12
|
-
///
|
|
13
|
-
/// definition. When the class doesn't already inherit `Module`/`BaseModule`, it also
|
|
10
|
+
/// `_jsName` static (no `Name(…)` DSL element). The `classes:` and `views:` arguments become
|
|
11
|
+
/// `_classTypes` and `_viewTypes` statics that `expo-modules-core` registers by type.
|
|
12
|
+
/// When the class doesn't already inherit `Module`/`BaseModule`, it also
|
|
14
13
|
/// synthesizes the `appContext` storage and `init(appContext:)` those base classes provide.
|
|
15
14
|
/// - `MemberAttributeMacro`: stamps `@JavaScriptActor` on `@JS` sync members and
|
|
16
15
|
/// `@ModuleDefinitionBuilder` on a `definition()` method.
|
|
@@ -44,18 +43,13 @@ public struct ExpoModuleMacro: MemberMacro {
|
|
|
44
43
|
// macro no longer emits a `Name(…)` DSL entry. Core reads the static for the module name,
|
|
45
44
|
// ahead of its type-name fallback (which then only applies to non-macro DSL modules).
|
|
46
45
|
let moduleName = jsNameArgument(of: node) ?? withoutBackticks(classDecl.name.text)
|
|
47
|
-
var entries: [String] = []
|
|
48
46
|
|
|
49
47
|
// `@JS func`s (sync and async) and `@JS var`s are bound directly into the JS object by the
|
|
50
48
|
// synthesized `_decorateModule` rather than described with a `Function(...)` / `Property(...)`
|
|
51
|
-
// DSL entry
|
|
49
|
+
// DSL entry.
|
|
52
50
|
var functions: [JSFunction] = []
|
|
53
51
|
var properties: [JSProperty] = []
|
|
54
52
|
|
|
55
|
-
for typeName in classListArgument(of: node, label: "classes") {
|
|
56
|
-
entries.append("\(typeName)._synthesizedClassDefinition()")
|
|
57
|
-
}
|
|
58
|
-
|
|
59
53
|
for member in classDecl.memberBlock.members {
|
|
60
54
|
let decl = member.decl
|
|
61
55
|
|
|
@@ -79,14 +73,6 @@ public struct ExpoModuleMacro: MemberMacro {
|
|
|
79
73
|
|
|
80
74
|
diagnoseJSNameCollisions(functions: functions, properties: properties, in: context)
|
|
81
75
|
|
|
82
|
-
let body: String
|
|
83
|
-
if entries.isEmpty {
|
|
84
|
-
body = " return []"
|
|
85
|
-
} else {
|
|
86
|
-
let lines = entries.map { " \($0)" }.joined(separator: ",\n")
|
|
87
|
-
body = " return [\n\(lines)\n ]"
|
|
88
|
-
}
|
|
89
|
-
|
|
90
76
|
var emitted: [DeclSyntax] = []
|
|
91
77
|
|
|
92
78
|
// The fully-resolved module name as a non-optional stored constant. Core reads this
|
|
@@ -94,6 +80,16 @@ public struct ExpoModuleMacro: MemberMacro {
|
|
|
94
80
|
// JS object name, so they can't diverge.
|
|
95
81
|
emitted.append("public static let _jsName = \"\(raw: moduleName)\"")
|
|
96
82
|
|
|
83
|
+
// Shared object classes are registered by type: core builds each JavaScript class from the
|
|
84
|
+
// `@SharedObject` hooks, with the name from the class's `_jsName`. The element type makes the
|
|
85
|
+
// compiler reject a class that isn't a `SharedObject` subclass. Emitted only when the argument is
|
|
86
|
+
// present, so a module without classes gains nothing.
|
|
87
|
+
let classTypes = classListArgument(of: node, label: "classes")
|
|
88
|
+
if !classTypes.isEmpty {
|
|
89
|
+
let entries = classTypes.map { "\($0).self" }.joined(separator: ", ")
|
|
90
|
+
emitted.append("public static let _classTypes: [SharedObject.Type] = [\(raw: entries)]")
|
|
91
|
+
}
|
|
92
|
+
|
|
97
93
|
// View classes are registered by type, not spliced as definitions: an `@ExpoView` emits no
|
|
98
94
|
// definition function, so there is nothing to call on each entry. Core reads a view's props
|
|
99
95
|
// type from its `Props` typealias and its event names from that type's `_eventNames`, both
|
|
@@ -123,15 +119,6 @@ public struct ExpoModuleMacro: MemberMacro {
|
|
|
123
119
|
}
|
|
124
120
|
}
|
|
125
121
|
|
|
126
|
-
// Always emitted: the definition is derived from the class name and `@JS` members,
|
|
127
|
-
// independent of any `definition()` the user wrote.
|
|
128
|
-
let method: DeclSyntax = """
|
|
129
|
-
public func _synthesizedDefinition() -> [AnyDefinition] {
|
|
130
|
-
\(raw: body)
|
|
131
|
-
}
|
|
132
|
-
"""
|
|
133
|
-
emitted.append(method)
|
|
134
|
-
|
|
135
122
|
// Direct JSI binding: one `_decorateModule` that binds each `@JS func` (inlined `setProperty`
|
|
136
123
|
// closure) and each `@JS var` (a `defineProperty` get/set accessor) into the module's JS object.
|
|
137
124
|
// Only emitted when there's at least one member to bind.
|
|
@@ -386,15 +386,15 @@ private func boundaryMember(of declaration: some DeclSyntaxProtocol) -> Boundary
|
|
|
386
386
|
let type = binding.typeAnnotation?.type,
|
|
387
387
|
!containsFunctionType(type)
|
|
388
388
|
{
|
|
389
|
-
// A `let`, or a `var` with no setter
|
|
390
|
-
// decoded on write, so its value type is asserted in both directions. A type that mentions
|
|
391
|
-
// converts through the free-form methods instead, so it isn't asserted at all.
|
|
389
|
+
// A `let`, a `lazy var`, or a `var` with no setter is read-only (encodable only). A settable `var`
|
|
390
|
+
// is also decoded on write, so its value type is asserted in both directions. A type that mentions
|
|
391
|
+
// `Any` converts through the free-form methods instead, so it isn't asserted at all.
|
|
392
392
|
guard !mentionsFreeFormAny(type) else {
|
|
393
393
|
return nil
|
|
394
394
|
}
|
|
395
395
|
let typeText = type.trimmedDescription
|
|
396
396
|
let isVar = varDecl.bindingSpecifier.tokenKind == .keyword(.var)
|
|
397
|
-
let isSettable = isVar && bindingIsSettable(binding)
|
|
397
|
+
let isSettable = isVar && !isLazy(varDecl.modifiers) && bindingIsSettable(binding)
|
|
398
398
|
return BoundaryMember(
|
|
399
399
|
name: identifier.identifier.text,
|
|
400
400
|
decodableTypes: isSettable ? [typeText] : [],
|
|
@@ -270,6 +270,12 @@ internal func isTypeLevel(_ modifiers: DeclModifierListSyntax) -> Bool {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
/// True for a `lazy` property. A lazy `@JS` property is read-only from JS: its getter stores the
|
|
274
|
+
/// first value as a data property on the JS object, which a setter couldn't write through.
|
|
275
|
+
internal func isLazy(_ modifiers: DeclModifierListSyntax) -> Bool {
|
|
276
|
+
return modifiers.contains { $0.name.tokenKind == .keyword(.lazy) }
|
|
277
|
+
}
|
|
278
|
+
|
|
273
279
|
/// True if a trailing occurrence of this parameter may be omitted by the JS caller: it either has a
|
|
274
280
|
/// default value (Swift applies it) or is an optional type (an absent slot becomes `nil`). The arity
|
|
275
281
|
/// range and the per-arity call branches are derived from this.
|
|
@@ -545,6 +551,7 @@ internal func collectProperties(
|
|
|
545
551
|
let jsNameOverride = jsNameArgument(of: attribute)
|
|
546
552
|
// A `let` is never settable; only `var` bindings can carry a setter.
|
|
547
553
|
let isVar = varDecl.bindingSpecifier.tokenKind == .keyword(.var)
|
|
554
|
+
let isLazy = isLazy(varDecl.modifiers)
|
|
548
555
|
|
|
549
556
|
return varDecl.bindings.compactMap { binding in
|
|
550
557
|
guard let ident = binding.pattern.as(IdentifierPatternSyntax.self) else {
|
|
@@ -562,7 +569,8 @@ internal func collectProperties(
|
|
|
562
569
|
valueType: valueType,
|
|
563
570
|
// A literal-inferred type never mentions `Any`, so only an explicit annotation can be free-form.
|
|
564
571
|
freeForm: binding.typeAnnotation.flatMap { freeFormConversion(of: $0.type) },
|
|
565
|
-
isSettable: isVar && bindingIsSettable(binding),
|
|
572
|
+
isSettable: isVar && !isLazy && bindingIsSettable(binding),
|
|
573
|
+
isLazy: isLazy,
|
|
566
574
|
nameToken: ident.identifier
|
|
567
575
|
)
|
|
568
576
|
}
|
|
@@ -499,7 +499,7 @@ private func javaScriptCodableWitnesses(
|
|
|
499
499
|
[" let object = runtime.createObject()"]
|
|
500
500
|
+ lookupLines
|
|
501
501
|
+ objectWriteLines(properties: properties, of: "value", cachesPropNames: cachesPropNames)
|
|
502
|
-
+ [" return object.
|
|
502
|
+
+ [" return object.takeValue()"]
|
|
503
503
|
return [
|
|
504
504
|
"""
|
|
505
505
|
@JavaScriptActor
|
|
@@ -2,9 +2,10 @@ import SwiftSyntax
|
|
|
2
2
|
import SwiftSyntaxBuilder
|
|
3
3
|
import SwiftSyntaxMacros
|
|
4
4
|
|
|
5
|
-
/// Member macro applied to a `SharedObject` subclass
|
|
6
|
-
/// declarations marked with `@JS` and
|
|
7
|
-
///
|
|
5
|
+
/// Member macro applied to a `SharedObject` subclass, direct or not (a `SharedRef<T>` subclass too).
|
|
6
|
+
/// Scans the class body for declarations marked with `@JS` and binds them directly through overrides of
|
|
7
|
+
/// the `SharedObject` hooks core calls when it builds the JavaScript class. The class's JavaScript name
|
|
8
|
+
/// is emitted as a `_jsName` override.
|
|
8
9
|
///
|
|
9
10
|
/// @SharedObject
|
|
10
11
|
/// final class Cache: SharedObject {
|
|
@@ -18,8 +19,8 @@ import SwiftSyntaxMacros
|
|
|
18
19
|
/// var size: Int { 42 }
|
|
19
20
|
/// }
|
|
20
21
|
///
|
|
21
|
-
/// The companion `@ExpoModule(classes: [Cache.self])`
|
|
22
|
-
///
|
|
22
|
+
/// The companion `@ExpoModule(classes: [Cache.self])` registers the class with the module, which
|
|
23
|
+
/// exposes it in its JavaScript object.
|
|
23
24
|
public struct SharedObjectMacro: MemberMacro {
|
|
24
25
|
public static func expansion(
|
|
25
26
|
of node: AttributeSyntax,
|
|
@@ -31,26 +32,19 @@ public struct SharedObjectMacro: MemberMacro {
|
|
|
31
32
|
throw MacroExpansionErrorMessage("@SharedObject can only be applied to a class")
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
|
|
35
|
+
// The superclass isn't checked here: the macro sees only this declaration, so it can't tell an
|
|
36
|
+
// indirect subclass (`class Thumbnail: ImageRef`) from an unrelated class. The compiler reports a
|
|
37
|
+
// class that isn't a `SharedObject` subclass on the `_jsName` override, which is always emitted.
|
|
40
38
|
let typeName = classDecl.name.text
|
|
41
39
|
let jsName = jsNameArgument(of: node) ?? withoutBackticks(typeName)
|
|
42
40
|
|
|
43
41
|
// `@JS func`s/`var`s and the `@JS init` are bound directly into the shared object's JS object by
|
|
44
|
-
// the synthesized `_decorateSharedObject(prototype:)` / `_decorateSharedObject(constructor:)` / `_constructSharedObject
|
|
45
|
-
// rather than described with a `Function(...)` / `Property(...)` / `Constructor { … }` DSL entry, so
|
|
46
|
-
// they're collected here instead of appended to the `Class` block. The block keeps only non-`@JS`
|
|
47
|
-
// definitions (none are collected today), so it's empty when every member is `@JS`.
|
|
42
|
+
// the synthesized `_decorateSharedObject(prototype:)` / `_decorateSharedObject(constructor:)` / `_constructSharedObject`.
|
|
48
43
|
//
|
|
49
44
|
// Members split by the `static`/`class` modifier onto two different JS objects: instance members
|
|
50
45
|
// decorate the prototype (receiver recovered from JS `this`), static members decorate the
|
|
51
46
|
// constructor (called on the metatype). A JS instance and static member may share a name without
|
|
52
47
|
// colliding: they live on different objects.
|
|
53
|
-
let entries: [String] = []
|
|
54
48
|
var instanceFunctions: [JSFunction] = []
|
|
55
49
|
var instanceProperties: [JSProperty] = []
|
|
56
50
|
var staticFunctions: [JSFunction] = []
|
|
@@ -100,18 +94,10 @@ public struct SharedObjectMacro: MemberMacro {
|
|
|
100
94
|
diagnoseJSNameCollisions(functions: instanceFunctions, properties: instanceProperties, in: context)
|
|
101
95
|
diagnoseJSNameCollisions(functions: staticFunctions, properties: staticProperties, in: context)
|
|
102
96
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
entries.isEmpty
|
|
106
|
-
? " return Class(\"\(jsName)\", \(typeName).self) {\n }"
|
|
107
|
-
: " return Class(\"\(jsName)\", \(typeName).self) {\n\(lines)\n }"
|
|
108
|
-
|
|
97
|
+
// The resolved JS class name, read by core when it builds the class. An `override` of the
|
|
98
|
+
// `SharedObject` class property, so core reads it through the concrete type's metatype.
|
|
109
99
|
var emitted: [DeclSyntax] = [
|
|
110
|
-
"""
|
|
111
|
-
public static func _synthesizedClassDefinition() -> ClassDefinition {
|
|
112
|
-
\(raw: body)
|
|
113
|
-
}
|
|
114
|
-
"""
|
|
100
|
+
"public override class var _jsName: String { \"\(raw: jsName)\" }"
|
|
115
101
|
]
|
|
116
102
|
|
|
117
103
|
// Direct JSI binding, split by which JS object each member decorates:
|
|
@@ -170,9 +156,3 @@ extension SharedObjectMacro: MemberAttributeMacro {
|
|
|
170
156
|
return ["@JavaScriptActor"]
|
|
171
157
|
}
|
|
172
158
|
}
|
|
173
|
-
|
|
174
|
-
// MARK: - Inheritance check
|
|
175
|
-
|
|
176
|
-
private func inheritsFromSharedObject(_ classDecl: ClassDeclSyntax) -> Bool {
|
|
177
|
-
return inheritsFromAny(classDecl, names: ["SharedObject"])
|
|
178
|
-
}
|
|
@@ -293,6 +293,9 @@ struct ExportedSharedObject: Encodable, Equatable {
|
|
|
293
293
|
/// if a malformed source has more. Not named `constructor`, which every JS object inherits.
|
|
294
294
|
var initializer: ExportedInitializer?
|
|
295
295
|
|
|
296
|
+
/// What the class inherits from `SharedRef<T>`, or `nil` when its superclass isn't `SharedRef`.
|
|
297
|
+
let sharedRef: ExportedSharedRef?
|
|
298
|
+
|
|
296
299
|
var functions: [ExportedFunction]
|
|
297
300
|
var properties: [ExportedProperty]
|
|
298
301
|
var events: [ExportedEvent]
|
|
@@ -305,6 +308,17 @@ struct ExportedSharedObject: Encodable, Equatable {
|
|
|
305
308
|
let file: String
|
|
306
309
|
}
|
|
307
310
|
|
|
311
|
+
/// The `SharedRef<T>` superclass of a shared object. Only the superclass written on the class is read,
|
|
312
|
+
/// so a class that inherits `SharedRef` through another subclass has none.
|
|
313
|
+
struct ExportedSharedRef: Encodable, Equatable {
|
|
314
|
+
/// The `T` in `SharedRef<T>`: the native type the object holds, which JS never sees.
|
|
315
|
+
let native: TypeNode
|
|
316
|
+
|
|
317
|
+
/// The `nativeRefType` JS reads to tell refs apart, for example `image`. `unknown` (core's default)
|
|
318
|
+
/// when the class doesn't override it, and `nil` when the override isn't a plain string literal.
|
|
319
|
+
let nativeRefType: String?
|
|
320
|
+
}
|
|
321
|
+
|
|
308
322
|
/// A `@Record` type and its properties (data only: no functions, accessors, or constructor).
|
|
309
323
|
struct ExportedRecord: Encodable, Equatable {
|
|
310
324
|
/// The Swift type name (struct or class), qualified with the types it is nested in
|
|
@@ -448,8 +462,9 @@ struct ExportedSurface: Encodable, Equatable {
|
|
|
448
462
|
/// an enum ref's `typeof`; version 6 added `classes` to modules; version 7 reports nested types under
|
|
449
463
|
/// qualified names and gives a resolved ref the name of its declaration; version 8 reports
|
|
450
464
|
/// declarations in `#if` blocks, adds `platforms` (with deprecations), `accessLevel`, and
|
|
451
|
-
/// `defaultValue`, and replaces `constructorParameters` with `initializer
|
|
452
|
-
|
|
465
|
+
/// `defaultValue`, and replaces `constructorParameters` with `initializer`; version 9 adds the
|
|
466
|
+
/// `sharedRef` type kind and the `sharedRef` of a shared object.
|
|
467
|
+
let scanExportsSchemaVersion = 9
|
|
453
468
|
|
|
454
469
|
/// The `scan-exports` result: the surface plus the run's stats. A distinct envelope from
|
|
455
470
|
/// `ScanModulesResult` (different consumer: TS generation vs. autolinking).
|
|
@@ -112,6 +112,9 @@ extension TypeNode {
|
|
|
112
112
|
switch self {
|
|
113
113
|
case .primitive, .unknown:
|
|
114
114
|
return self
|
|
115
|
+
case .sharedRef:
|
|
116
|
+
// The native type never reaches JS, so it isn't looked up among the scanned declarations.
|
|
117
|
+
return self
|
|
115
118
|
case .ref(let name, _, _):
|
|
116
119
|
guard let entry = index.lookup(name, in: scope) else {
|
|
117
120
|
return self
|
|
@@ -170,6 +170,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
170
170
|
name: qualified,
|
|
171
171
|
jsName: stringArgument(of: attribute) ?? name,
|
|
172
172
|
initializer: initializer,
|
|
173
|
+
sharedRef: sharedRefSuperclass(of: node),
|
|
173
174
|
functions: functions,
|
|
174
175
|
properties: properties,
|
|
175
176
|
events: events,
|
|
@@ -348,6 +349,8 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
348
349
|
platforms: [ExportedPlatform]
|
|
349
350
|
) -> [ExportedProperty] {
|
|
350
351
|
let isLet = varDecl.bindingSpecifier.tokenKind == .keyword(.let)
|
|
352
|
+
// A `lazy var` is read-only from JS, like a `let`: the macro binds it getter-only.
|
|
353
|
+
let isLazy = varDecl.modifiers.contains { $0.name.tokenKind == .keyword(.lazy) }
|
|
351
354
|
let isStatic = isTypeLevel(varDecl.modifiers)
|
|
352
355
|
let override = stringArgument(of: attribute)
|
|
353
356
|
var result: [ExportedProperty] = []
|
|
@@ -363,7 +366,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
363
366
|
name: name,
|
|
364
367
|
jsName: override ?? name,
|
|
365
368
|
type: type,
|
|
366
|
-
isSettable: isSettable(binding: binding, isLet: isLet),
|
|
369
|
+
isSettable: !isLazy && isSettable(binding: binding, isLet: isLet),
|
|
367
370
|
isStatic: isStatic,
|
|
368
371
|
// Only a stored property has an initializer; a computed one has none to report.
|
|
369
372
|
defaultValue: binding.initializer?.value.trimmedDescription,
|
|
@@ -741,6 +744,54 @@ private func asyncVariantName(of attribute: AttributeSyntax) -> String? {
|
|
|
741
744
|
return nil
|
|
742
745
|
}
|
|
743
746
|
|
|
747
|
+
/// The `SharedRef<T>` a class names as its superclass, or `nil` when it names another one or none.
|
|
748
|
+
/// Swift requires the superclass to come first in the inheritance clause, so only that entry is read.
|
|
749
|
+
private func sharedRefSuperclass(of node: some DeclGroupSyntax) -> ExportedSharedRef? {
|
|
750
|
+
guard let superclass = node.inheritanceClause?.inheritedTypes.first?.type,
|
|
751
|
+
case .sharedRef(let native) = typeNode(from: superclass)
|
|
752
|
+
else {
|
|
753
|
+
return nil
|
|
754
|
+
}
|
|
755
|
+
return ExportedSharedRef(native: native, nativeRefType: nativeRefType(in: node.memberBlock.members))
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/// The value of a `nativeRefType` override that returns a plain string literal: `"image"` in
|
|
759
|
+
/// `override var nativeRefType: String { "image" }`, also with `return` or inside a `get` block.
|
|
760
|
+
/// `unknown`, core's default, when the class declares no `nativeRefType`, and `nil` when its getter is
|
|
761
|
+
/// anything else.
|
|
762
|
+
private func nativeRefType(in members: MemberBlockItemListSyntax) -> String? {
|
|
763
|
+
let binding = members.lazy
|
|
764
|
+
.compactMap { $0.decl.as(VariableDeclSyntax.self) }
|
|
765
|
+
.filter { !isTypeLevel($0.modifiers) }
|
|
766
|
+
.flatMap { $0.bindings }
|
|
767
|
+
.first { $0.pattern.as(IdentifierPatternSyntax.self)?.identifier.text == "nativeRefType" }
|
|
768
|
+
guard let binding else {
|
|
769
|
+
return "unknown"
|
|
770
|
+
}
|
|
771
|
+
let statements: CodeBlockItemListSyntax?
|
|
772
|
+
switch binding.accessorBlock?.accessors {
|
|
773
|
+
case .getter(let body):
|
|
774
|
+
statements = body
|
|
775
|
+
case .accessors(let accessors):
|
|
776
|
+
statements = accessors.first { $0.accessorSpecifier.tokenKind == .keyword(.get) }?.body?.statements
|
|
777
|
+
case nil:
|
|
778
|
+
statements = nil
|
|
779
|
+
}
|
|
780
|
+
guard let statements, statements.count == 1, let item = statements.first?.item else {
|
|
781
|
+
return nil
|
|
782
|
+
}
|
|
783
|
+
let expression = item.as(ReturnStmtSyntax.self)?.expression ?? item.as(ExprSyntax.self)
|
|
784
|
+
guard let literal = expression?.as(StringLiteralExprSyntax.self),
|
|
785
|
+
literal.segments.count == 1,
|
|
786
|
+
let segment = literal.segments.first?.as(StringSegmentSyntax.self)
|
|
787
|
+
else {
|
|
788
|
+
return nil
|
|
789
|
+
}
|
|
790
|
+
// An escape would need real unescaping, so it's treated as unreadable, as in `writtenRawValue(of:)`.
|
|
791
|
+
let content = segment.content.text
|
|
792
|
+
return content.contains("\\") ? nil : content
|
|
793
|
+
}
|
|
794
|
+
|
|
744
795
|
private func isTypeLevel(_ modifiers: DeclModifierListSyntax) -> Bool {
|
|
745
796
|
modifiers.contains {
|
|
746
797
|
$0.name.tokenKind == .keyword(.static) || $0.name.tokenKind == .keyword(.class)
|
|
@@ -8,8 +8,8 @@ import SwiftSyntax
|
|
|
8
8
|
/// never silently dropped.
|
|
9
9
|
|
|
10
10
|
/// The runtime category mirroring JavaScript's `typeof`. A coarse companion to `TypeNode.kind`: every
|
|
11
|
-
/// structured object kind (array, dictionary, promise, ref) reports `object`. `symbol` is
|
|
12
|
-
/// completeness; the scanner doesn't produce it.
|
|
11
|
+
/// structured object kind (array, dictionary, promise, ref, sharedRef) reports `object`. `symbol` is
|
|
12
|
+
/// included for completeness; the scanner doesn't produce it.
|
|
13
13
|
enum JSType: String, Encodable {
|
|
14
14
|
case undefined
|
|
15
15
|
case object
|
|
@@ -72,6 +72,11 @@ indirect enum TypeNode: Equatable {
|
|
|
72
72
|
/// node parsed at a use site has never seen.
|
|
73
73
|
case ref(name: String, refKind: RefKind? = nil, jsTypeOverride: JSType? = nil)
|
|
74
74
|
|
|
75
|
+
/// `SharedRef<T>` (or `ExpoModulesCore.SharedRef<T>`): a shared object holding a native `T`, which
|
|
76
|
+
/// JS sees as an instance of `expo.SharedRef`. `native` is `T` parsed like any other type, but it
|
|
77
|
+
/// never crosses the boundary itself, so refs in it are not resolved.
|
|
78
|
+
case sharedRef(native: TypeNode)
|
|
79
|
+
|
|
75
80
|
/// A spelling the parser doesn't model (generic parameter, tuple, metatype, …), kept verbatim so
|
|
76
81
|
/// nothing is lost. Has no `typeof`.
|
|
77
82
|
case unknown(text: String)
|
|
@@ -82,7 +87,7 @@ indirect enum TypeNode: Equatable {
|
|
|
82
87
|
switch self {
|
|
83
88
|
case .primitive(_, let jsType):
|
|
84
89
|
return jsType
|
|
85
|
-
case .array, .dictionary, .promise:
|
|
90
|
+
case .array, .dictionary, .promise, .sharedRef:
|
|
86
91
|
return .object
|
|
87
92
|
case .ref(_, _, let jsTypeOverride):
|
|
88
93
|
// An unresolved ref, and every resolved one but an enum, is an object.
|
|
@@ -105,7 +110,7 @@ extension TypeNode: Encodable {
|
|
|
105
110
|
// The `typeof` category. Spelled `typeof` in JSON (what the consumer reads); the Swift property is
|
|
106
111
|
// `jsType` since it's the cleaner Swift name.
|
|
107
112
|
case jsType = "typeof"
|
|
108
|
-
case wrapped, element, key, value, parameters, returns, text
|
|
113
|
+
case wrapped, element, key, value, parameters, returns, text, native
|
|
109
114
|
// A closure type's effects, matching `ExportedFunction`'s TS-keyword spellings.
|
|
110
115
|
case isAsync = "async"
|
|
111
116
|
case isThrowing = "throws"
|
|
@@ -143,6 +148,9 @@ extension TypeNode: Encodable {
|
|
|
143
148
|
try container.encode("ref", forKey: .kind)
|
|
144
149
|
try container.encode(name, forKey: .name)
|
|
145
150
|
try container.encodeIfPresent(refKind, forKey: .refKind)
|
|
151
|
+
case .sharedRef(let native):
|
|
152
|
+
try container.encode("sharedRef", forKey: .kind)
|
|
153
|
+
try container.encode(native, forKey: .native)
|
|
146
154
|
case .unknown(let text):
|
|
147
155
|
try container.encode("unknown", forKey: .kind)
|
|
148
156
|
try container.encode(text, forKey: .text)
|
|
@@ -209,8 +217,15 @@ func typeNode(from type: TypeSyntax) -> TypeNode {
|
|
|
209
217
|
if let identifier = type.as(IdentifierTypeSyntax.self) {
|
|
210
218
|
return nominalNode(name: identifier.name.identifierName, generics: identifier.genericArgumentClause)
|
|
211
219
|
}
|
|
212
|
-
// A qualified nominal type: `Foo.Bar`. Kept as a ref under its full spelling
|
|
220
|
+
// A qualified nominal type: `Foo.Bar`. Kept as a ref under its full spelling, except core's own
|
|
221
|
+
// `ExpoModulesCore.SharedRef<T>`, which is the same type as a bare `SharedRef<T>`.
|
|
213
222
|
if let member = type.as(MemberTypeSyntax.self) {
|
|
223
|
+
if member.baseType.as(IdentifierTypeSyntax.self)?.name.text == "ExpoModulesCore",
|
|
224
|
+
member.name.text == "SharedRef",
|
|
225
|
+
let generics = member.genericArgumentClause
|
|
226
|
+
{
|
|
227
|
+
return nominalNode(name: "SharedRef", generics: generics)
|
|
228
|
+
}
|
|
214
229
|
return .ref(name: member.trimmedDescription)
|
|
215
230
|
}
|
|
216
231
|
// Tuples, metatypes, some/any types, etc.: not modeled, preserve the verbatim spelling.
|
|
@@ -218,8 +233,8 @@ func typeNode(from type: TypeSyntax) -> TypeNode {
|
|
|
218
233
|
}
|
|
219
234
|
|
|
220
235
|
/// A nominal type's node. The standard generic wrappers normalize to their sugared node
|
|
221
|
-
/// (`Optional<T>`/`Array<T>`/`Dictionary<K,V>`/`Promise<T>`)
|
|
222
|
-
/// else a ref; any other generic is a ref under its full spelling.
|
|
236
|
+
/// (`Optional<T>`/`Array<T>`/`Dictionary<K,V>`/`Promise<T>`) and `SharedRef<T>` to its own node; a
|
|
237
|
+
/// bare name is a primitive when known, else a ref; any other generic is a ref under its full spelling.
|
|
223
238
|
private func nominalNode(name: String, generics: GenericArgumentClauseSyntax?) -> TypeNode {
|
|
224
239
|
guard let generics else {
|
|
225
240
|
if let jsType = primitiveJSTypes[name] {
|
|
@@ -238,6 +253,8 @@ private func nominalNode(name: String, generics: GenericArgumentClauseSyntax?) -
|
|
|
238
253
|
return .dictionary(key: arguments[0], value: arguments[1])
|
|
239
254
|
case ("Promise", 1):
|
|
240
255
|
return .promise(value: arguments[0])
|
|
256
|
+
case ("SharedRef", 1):
|
|
257
|
+
return .sharedRef(native: arguments[0])
|
|
241
258
|
default:
|
|
242
259
|
// An unmodeled generic (`Set<Int>`, `Either<A, B>`, …). Keep the whole spelling as a ref so the
|
|
243
260
|
// name and its arguments survive for the generator to interpret or flag.
|
|
@@ -297,6 +314,8 @@ extension TypeNode {
|
|
|
297
314
|
return "[\(key.spelling): \(value.spelling)]"
|
|
298
315
|
case .promise(let value):
|
|
299
316
|
return "Promise<\(value.spelling)>"
|
|
317
|
+
case .sharedRef(let native):
|
|
318
|
+
return "SharedRef<\(native.spelling)>"
|
|
300
319
|
case .function(let parameters, let returns, _, _):
|
|
301
320
|
return "(\(parameters.map { $0.spelling }.joined(separator: ", "))) -> \(returns?.spelling ?? "Void")"
|
|
302
321
|
case .unknown(let text):
|
package/build/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export declare class ScannerSchemaVersionError extends Error {
|
|
|
43
43
|
* `bin` directory.
|
|
44
44
|
* - Windows and Linux: `ExpoModulesMacros.exe` or `ExpoModulesMacros` from the package for the
|
|
45
45
|
* current platform and architecture (see `platformBinaryPackageName`). When that package isn't
|
|
46
|
-
* installed, for example in this repository, it falls back to the executable that `
|
|
46
|
+
* installed, for example in this repository, it falls back to the executable that `pnpm run build`
|
|
47
47
|
* writes to `platforms/<platform>-<arch>`.
|
|
48
48
|
*
|
|
49
49
|
* The path isn't checked here; running a missing binary fails with a `ScannerError` that says how to
|
package/build/index.js
CHANGED
|
@@ -75,7 +75,7 @@ function platformBinaryPackageName() {
|
|
|
75
75
|
* `bin` directory.
|
|
76
76
|
* - Windows and Linux: `ExpoModulesMacros.exe` or `ExpoModulesMacros` from the package for the
|
|
77
77
|
* current platform and architecture (see `platformBinaryPackageName`). When that package isn't
|
|
78
|
-
* installed, for example in this repository, it falls back to the executable that `
|
|
78
|
+
* installed, for example in this repository, it falls back to the executable that `pnpm run build`
|
|
79
79
|
* writes to `platforms/<platform>-<arch>`.
|
|
80
80
|
*
|
|
81
81
|
* The path isn't checked here; running a missing binary fails with a `ScannerError` that says how to
|
|
@@ -97,7 +97,7 @@ function getScannerBinaryPath() {
|
|
|
97
97
|
/** The message for a scanner binary that doesn't exist at `binaryPath`. */
|
|
98
98
|
function missingBinaryMessage(binaryPath) {
|
|
99
99
|
if (process.platform === 'darwin') {
|
|
100
|
-
return `The scanner binary is missing at ${binaryPath}. Run \`
|
|
100
|
+
return `The scanner binary is missing at ${binaryPath}. Run \`pnpm run build\` in this package to build it.`;
|
|
101
101
|
}
|
|
102
102
|
const machines = platformPackageMachines[process.platform];
|
|
103
103
|
if (machines === undefined) {
|
|
@@ -106,7 +106,7 @@ function missingBinaryMessage(binaryPath) {
|
|
|
106
106
|
return (`The scanner binary is missing at ${binaryPath}. It comes from the optional package ` +
|
|
107
107
|
`${platformBinaryPackageName()}, which your package manager installs only on ${machines}. ` +
|
|
108
108
|
'Reinstall dependencies without skipping optional ones (for example without `--omit=optional`), ' +
|
|
109
|
-
'or run `
|
|
109
|
+
'or run `pnpm run build` in this package to build it locally.');
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
112
|
* Runs a scanner subcommand and parses its JSON report.
|
package/build/types.d.ts
CHANGED
|
@@ -61,6 +61,15 @@ export type TypeNode = {
|
|
|
61
61
|
name: string;
|
|
62
62
|
refKind?: 'record' | 'sharedObject' | 'enum' | 'union';
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* `SharedRef<T>`: a shared object that JS sees as an instance of `expo.SharedRef`. `native` is the
|
|
66
|
+
* `T` it holds on the native side. It never reaches JS, so refs in it are not resolved.
|
|
67
|
+
*/
|
|
68
|
+
| {
|
|
69
|
+
kind: 'sharedRef';
|
|
70
|
+
typeof: JSType;
|
|
71
|
+
native: TypeNode;
|
|
72
|
+
}
|
|
64
73
|
/** A type the scanner couldn't interpret; `text` is its source spelling. */
|
|
65
74
|
| {
|
|
66
75
|
kind: 'unknown';
|
|
@@ -241,11 +250,26 @@ export interface ExportedSharedObject extends ExportedAccess, ExportedAvailabili
|
|
|
241
250
|
* which every JS object inherits.
|
|
242
251
|
*/
|
|
243
252
|
initializer?: ExportedInitializer;
|
|
253
|
+
/** What the class inherits from `SharedRef<T>`, or absent when its superclass isn't `SharedRef`. */
|
|
254
|
+
sharedRef?: ExportedSharedRef;
|
|
244
255
|
functions: ExportedFunction[];
|
|
245
256
|
properties: ExportedProperty[];
|
|
246
257
|
events: ExportedEvent[];
|
|
247
258
|
file: string;
|
|
248
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* The `SharedRef<T>` superclass of a shared object. Only the superclass written on the class is read,
|
|
262
|
+
* so a class that inherits `SharedRef` through another subclass has none.
|
|
263
|
+
*/
|
|
264
|
+
export interface ExportedSharedRef {
|
|
265
|
+
/** The `T` in `SharedRef<T>`: the native type the object holds, which JS never sees. */
|
|
266
|
+
native: TypeNode;
|
|
267
|
+
/**
|
|
268
|
+
* The `nativeRefType` JS reads to tell refs apart, for example `image`. `unknown` (core's default)
|
|
269
|
+
* when the class doesn't override it, and absent when the override isn't a plain string literal.
|
|
270
|
+
*/
|
|
271
|
+
nativeRefType?: string;
|
|
272
|
+
}
|
|
249
273
|
/** A `@Record` type and its properties. */
|
|
250
274
|
export interface ExportedRecord extends ExportedAccess, ExportedAvailability {
|
|
251
275
|
/** The Swift type name, qualified with the types it is nested in (`Outer.Options`). */
|
|
@@ -365,4 +389,4 @@ export interface ScanExportsResult {
|
|
|
365
389
|
* as a clear error instead of silently misread fields.
|
|
366
390
|
*/
|
|
367
391
|
export declare const SUPPORTED_SCAN_MODULES_SCHEMA_VERSION = 2;
|
|
368
|
-
export declare const SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION =
|
|
392
|
+
export declare const SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION = 9;
|
package/build/types.js
CHANGED
|
@@ -12,4 +12,4 @@ exports.SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION = exports.SUPPORTED_SCAN_MODULES_S
|
|
|
12
12
|
* as a clear error instead of silently misread fields.
|
|
13
13
|
*/
|
|
14
14
|
exports.SUPPORTED_SCAN_MODULES_SCHEMA_VERSION = 2;
|
|
15
|
-
exports.SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION =
|
|
15
|
+
exports.SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION = 9;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-macros",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Swift macro plugin for Expo modules",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "git+https://github.com/expo/expo-modules-macros.git"
|
|
13
13
|
},
|
|
14
|
+
"packageManager": "pnpm@12.14.0",
|
|
14
15
|
"scripts": {
|
|
15
16
|
"build": "node scripts/build.js",
|
|
16
17
|
"build:ts": "tsc -p tsconfig.json",
|
|
@@ -25,9 +26,9 @@
|
|
|
25
26
|
"typescript": "^5.4.0"
|
|
26
27
|
},
|
|
27
28
|
"optionalDependencies": {
|
|
28
|
-
"@expo/modules-macros-linux-arm64": "0.
|
|
29
|
-
"@expo/modules-macros-linux-x64": "0.
|
|
30
|
-
"@expo/modules-macros-win32-arm64": "0.
|
|
31
|
-
"@expo/modules-macros-win32-x64": "0.
|
|
29
|
+
"@expo/modules-macros-linux-arm64": "0.23.0",
|
|
30
|
+
"@expo/modules-macros-linux-x64": "0.23.0",
|
|
31
|
+
"@expo/modules-macros-win32-arm64": "0.23.0",
|
|
32
|
+
"@expo/modules-macros-win32-x64": "0.23.0"
|
|
32
33
|
}
|
|
33
34
|
}
|