feat(compiler-sfc,compiler-spatial): add spatial computing support for visionOS#14612
feat(compiler-sfc,compiler-spatial): add spatial computing support for visionOS#14612Amiya167 wants to merge 3 commits intovuejs:mainfrom
Conversation
Add `<script setup spatial>` attribute parsing to enable Vue SFC compilation targeting SwiftUI on visionOS. This is Phase 1 of the spatial computing initiative (RFC 0000). Changes: - SFCBlock: add `spatial` field - SFCDescriptor: add `spatial` flag and `stylesSpatial` array - parse(): route `<script setup spatial>` and `<style spatial>` blocks - createBlock(): recognize `spatial` attribute - compileScript(): early return for spatial components (defers to future @vue/compiler-spatial package) - Add `isSpatialComponent()` utility - 10 new tests covering parsing and validation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds spatial SFC support and a new spatial compiler: parses Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
❌ Deploy Preview for vue-next-template-explorer failed. Why did it fail? →
|
❌ Deploy Preview for vue-sfc-playground failed. Why did it fail? →
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/compiler-sfc/src/parse.ts (2)
354-357:slottedcheck does not includestylesSpatial.Similar to the CSS vars issue, the
:slotted()detection only checksdescriptor.styles. If scoped spatial styles can use:slotted(), this check should includestylesSpatial. However, if spatial styles compile entirely differently (to SwiftUI), this may be intentional.Consider documenting the design decision or extending the check if applicable.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/compiler-sfc/src/parse.ts` around lines 354 - 357, The slotted detection only inspects descriptor.styles and thus misses scoped spatial styles; update the check that sets descriptor.slotted to include descriptor.stylesSpatial as well (e.g., iterate both descriptor.styles and descriptor.stylesSpatial or concat them) by testing s.scoped && slottedRE.test(s.content) for entries in both collections (referencing slottedRE, descriptor.slotted, descriptor.styles, descriptor.stylesSpatial, and the s.scoped/s.content predicate), or if spatial styles intentionally differ, add a comment documenting that design decision instead of changing the logic.
350-351: Consider adding a comment clarifying whyparseCssVarsintentionally omitsstylesSpatialblocks.The concern is accurate:
parseCssVarsonly iterates oversfc.stylesand excludesstylesSpatial. However, this is intentional design. Spatial styles (marked with<style spatial>) compile to SwiftUI view modifiers rather than CSS, as documented in theSFCDescriptorinterface. Since SwiftUI modifiers don't use CSS v-bind() expressions, processing them inparseCssVarswould be unnecessary and semantically incorrect. The actual compilation of spatial styles is delegated to@vue/compiler-spatial.A brief inline comment at line 350 could improve clarity for future maintainers:
// parse CSS vars (spatial styles use SwiftUI modifiers instead of CSS) descriptor.cssVars = parseCssVars(descriptor)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/compiler-sfc/src/parse.ts` around lines 350 - 351, Add a short inline comment above the existing assignment to explain why parseCssVars only processes descriptor.styles and intentionally omits stylesSpatial: note that styles with the "spatial" flag compile to SwiftUI view modifiers (handled by `@vue/compiler-spatial`) rather than CSS, so they don't contain CSS v-bind() expressions and should not be processed by parseCssVars; update the line with descriptor.cssVars = parseCssVars(descriptor) to include that brief clarification.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/compiler-sfc/src/parse.ts`:
- Around line 214-234: The variables declared in the case 'script' branch
(scriptBlock, isSetup, isSpatial) leak into other switch cases; wrap the entire
case 'script' body in a block (add { ... } after case 'script':) to create a
local scope for those declarations so they cannot be accessed by other cases;
adjust control flow (break statements) to remain inside that new block and keep
existing uses of scriptBlock/isSetup/isSpatial (and assignments to
descriptor.scriptSetup / descriptor.spatial and pushes to errors) unchanged.
---
Nitpick comments:
In `@packages/compiler-sfc/src/parse.ts`:
- Around line 354-357: The slotted detection only inspects descriptor.styles and
thus misses scoped spatial styles; update the check that sets descriptor.slotted
to include descriptor.stylesSpatial as well (e.g., iterate both
descriptor.styles and descriptor.stylesSpatial or concat them) by testing
s.scoped && slottedRE.test(s.content) for entries in both collections
(referencing slottedRE, descriptor.slotted, descriptor.styles,
descriptor.stylesSpatial, and the s.scoped/s.content predicate), or if spatial
styles intentionally differ, add a comment documenting that design decision
instead of changing the logic.
- Around line 350-351: Add a short inline comment above the existing assignment
to explain why parseCssVars only processes descriptor.styles and intentionally
omits stylesSpatial: note that styles with the "spatial" flag compile to SwiftUI
view modifiers (handled by `@vue/compiler-spatial`) rather than CSS, so they don't
contain CSS v-bind() expressions and should not be processed by parseCssVars;
update the line with descriptor.cssVars = parseCssVars(descriptor) to include
that brief clarification.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1f88850c-ae52-42d5-b94f-45258dfc820f
📒 Files selected for processing (6)
docs/spatial-computing-dev.mdpackages/compiler-sfc/__tests__/parse.spec.tspackages/compiler-sfc/src/compileScript.tspackages/compiler-sfc/src/index.tspackages/compiler-sfc/src/parse.tspackages/compiler-sfc/src/spatial.ts
- Wrap `case 'script'` and `case 'style'` in block scope to prevent variable leakage across switch clauses (CodeRabbit feedback) - Remove docs/spatial-computing-dev.md from commit — its `export enum` code examples trigger scanEnums() which tries to Babel-parse .md files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/compiler-sfc/src/parse.ts`:
- Around line 307-317: The current check only triggers when descriptor.template
exists, so a standalone `<script setup spatial>` slips through; update the
condition to reject whenever descriptor.scriptSetup?.spatial is true and either
descriptor.template is missing or descriptor.template.spatial is false. In other
words, replace the existing conditional with one that tests
descriptor.scriptSetup?.spatial && (!descriptor.template ||
!descriptor.template.spatial) and keep the same errors.push(new
SyntaxError(...)) logic to report the missing/incorrect <template spatial>
invariant.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 07b69d25-3d2d-4e66-b7c6-db2b442b02fb
📒 Files selected for processing (1)
packages/compiler-sfc/src/parse.ts
| if ( | ||
| descriptor.template && | ||
| !descriptor.template.spatial && | ||
| descriptor.scriptSetup?.spatial | ||
| ) { | ||
| errors.push( | ||
| new SyntaxError( | ||
| `<script setup spatial> requires <template spatial>. ` + | ||
| `Add the "spatial" attribute to the <template> block.`, | ||
| ), | ||
| ) |
There was a problem hiding this comment.
Reject <script setup spatial> when the template is missing.
Line 308 only fires this check when descriptor.template exists, so <script setup spatial> without any <template> currently parses without the new error. That leaves the main spatial invariant unenforced for one of the invalid shapes this PR is supposed to catch.
🛠️ Suggested change
- if (
- descriptor.template &&
- !descriptor.template.spatial &&
- descriptor.scriptSetup?.spatial
- ) {
+ if (descriptor.scriptSetup?.spatial && !descriptor.template?.spatial) {
errors.push(
new SyntaxError(
`<script setup spatial> requires <template spatial>. ` +
`Add the "spatial" attribute to the <template> block.`,
),
)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ( | |
| descriptor.template && | |
| !descriptor.template.spatial && | |
| descriptor.scriptSetup?.spatial | |
| ) { | |
| errors.push( | |
| new SyntaxError( | |
| `<script setup spatial> requires <template spatial>. ` + | |
| `Add the "spatial" attribute to the <template> block.`, | |
| ), | |
| ) | |
| if (descriptor.scriptSetup?.spatial && !descriptor.template?.spatial) { | |
| errors.push( | |
| new SyntaxError( | |
| `<script setup spatial> requires <template spatial>. ` + | |
| `Add the "spatial" attribute to the <template> block.`, | |
| ), | |
| ) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/compiler-sfc/src/parse.ts` around lines 307 - 317, The current check
only triggers when descriptor.template exists, so a standalone `<script setup
spatial>` slips through; update the condition to reject whenever
descriptor.scriptSetup?.spatial is true and either descriptor.template is
missing or descriptor.template.spatial is false. In other words, replace the
existing conditional with one that tests descriptor.scriptSetup?.spatial &&
(!descriptor.template || !descriptor.template.spatial) and keep the same
errors.push(new SyntaxError(...)) logic to report the missing/incorrect
<template spatial> invariant.
… codegen Add the compiler-spatial package that compiles Vue spatial templates into SwiftUI source code, following the same two-pass architecture as compiler-ssr. - Element mapping: 30 Vue spatial tags → SwiftUI views (VStack, Text, Button, etc.) - Directive compilation: v-if → Swift if/else, v-for → ForEach, v-model → vm.binding(), v-show → .opacity(), spatial gesture directives → SwiftUI gestures - Style mapping: CSS-like spatial properties → SwiftUI view modifiers - Swift codegen: generates complete SwiftUI View structs with VueViewModel bridge - Bridge manifest: JSON contract for JS↔Swift IPC communication - Wire compiler-sfc to re-export compileSpatial from compiler-spatial - 28 tests covering elements, directives, codegen, and RFC examples Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
packages/compiler-spatial/__tests__/spatialElement.spec.ts (1)
67-74: Align the Button test with its name by asserting child rendering.This case says “with
@tapand children” but currently validates only the action closure. Add an assertion forText("Click")to lock in child emission behavior.Suggested addition
test('Button with `@tap` and children', () => { const lines = getSwiftLines( `<button `@tap`="increment"><text>Click</text></button>`, ) expect( lines.some(l => l.includes('Button(action: { vm.emit("increment") })')), ).toBe(true) + expect(lines.some(l => l.includes('Text("Click")'))).toBe(true) })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/compiler-spatial/__tests__/spatialElement.spec.ts` around lines 67 - 74, The test "Button with `@tap` and children" currently only asserts the action closure; update the test to also assert that child rendering is emitted by checking for Text("Click") in the output from getSwiftLines — use the existing lines variable (from getSwiftLines(...)) and add an expect that lines.some(l => l.includes('Text("Click")')) or equivalent to ensure child emission is validated alongside Button(action: { vm.emit("increment") }).packages/compiler-spatial/__tests__/spatialVIf.spec.ts (1)
22-29: Strengthen thev-else-if/v-elsetest with branch content assertions.Current checks verify control flow tokens, but not rendered branch bodies. Adding
Text("B")/Text("C")assertions will reduce false positives in regressions.Suggested test hardening
test('v-if with v-else-if and v-else', () => { const lines = getSwiftLines( `<text v-if="a">A</text><text v-else-if="b">B</text><text v-else>C</text>`, ) expect(lines.some(l => l.includes('if vm.get("a")'))).toBe(true) expect(lines.some(l => l.includes('} else if vm.get("b")'))).toBe(true) expect(lines.some(l => l.includes('} else {'))).toBe(true) + expect(lines.some(l => l.includes('Text("B")'))).toBe(true) + expect(lines.some(l => l.includes('Text("C")'))).toBe(true) })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/compiler-spatial/__tests__/spatialVIf.spec.ts` around lines 22 - 29, Update the test 'v-if with v-else-if and v-else' so it not only checks control-flow tokens but also asserts that the branch bodies are rendered: after calling getSwiftLines(...) verify that generated lines include the rendered branch contents like Text("B") for the v-else-if branch and Text("C") for the v-else branch (in addition to the existing checks for if vm.get("a"), } else if vm.get("b"), and } else {). This change targets the test case in packages/compiler-spatial/__tests__/spatialVIf.spec.ts and ensures the output contains the expected Text("B") and Text("C") strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/compiler-spatial/src/spatialCodegenTransform.ts`:
- Around line 18-25: The transform currently discards the collected metadata
(events, bindings, spatialGestures) in SpatialTransformContext when returning
from spatialCodegenTransform; update spatialCodegenTransform to include and
return those context fields (events, bindings, spatialGestures) alongside lines
so the caller can build a BridgeManifest; locate the SpatialTransformContext
interface and the spatialCodegenTransform function (and the return at the second
instance around the lines noted) and modify the return value to propagate the
Map<string,string> events, string[] bindings and string[] spatialGestures
instead of only returning lines.
- Around line 112-121: The default clauses in the switch inside
spatialCodegenTransform.ts declare a variable (_exhaustiveCheck) without a
block, which risks hoisting and binding conflicts; wrap each default: case body
in a block ({ ... }) so the const _exhaustiveCheck: never = child and the
context.onError(...) call are scoped to the case. Update both occurrences
referenced by the code that calls
context.onError(createSpatialCompilerError(SpatialErrorCodes.X_SPATIAL_INVALID_AST_NODE,
(child as any).loc)) and the subsequent const _exhaustiveCheck declarations to
be inside their own {} blocks.
In `@packages/compiler-spatial/src/transforms/spatialTransformElement.ts`:
- Around line 165-183: processDirectiveProp currently stores v-bind results only
in CONSTRUCTOR_PROPS/MODIFIER_PROPS (via constructorArgs and modifiers) so
downstream emitters that call findAttr lose bound values; update the handling so
bound attrs are discoverable by findAttr or provide a shared resolver.
Specifically, in processDirectiveProp (the code branch checking prop.name ===
'bind', isStaticExp, getExpContent) make bound values available as attribute
equivalents (e.g., add a synthesized prop entry onto the node.props/attrs or a
sidecar map) instead of only pushing into constructorArgs/modifiers, and
implement a single helper resolveStaticOrBoundAttr(name) that findAttr and all
specialized emitters call (replace direct findAttr uses) to return either the
static value or the bound expression from
CONSTRUCTOR_PROPS/MODIFIER_PROPS/constructorArgs/modifiers; ensure symbols
referenced include processDirectiveProp, CONSTRUCTOR_PROPS, MODIFIER_PROPS,
findAttr, constructorArgs, modifiers, getExpContent so emitters like
WindowGroup/ImmersiveSpace consume the bound inputs.
In `@packages/compiler-spatial/src/transforms/spatialVFor.ts`:
- Around line 39-43: The ForEach code creates a Swift local alias (the
`${valueName}` closure param) but the expression emitter still emits global
lookups via vm.get/ vm.getArray so loop body expressions never reference the
local; to fix, add a loop-local binding when emitting the ForEach (in
spatialVFor.ts around ForEach(vm.getArray(...), id: \\.${keyExpr}) {
${valueName} in and processChildren(node, context)) and update the expression
emitter logic (where vm.get is produced in spatialCodegenTransform.ts and used
by spatialVIf.ts and spatialTransformElement.ts) to consult the current codegen
scope/locals before emitting a vm.get — if a local binding for the root
identifier exists emit a direct local access (the `${valueName}`/property
lookup) instead of vm.get("..."); ensure the binding is pushed before calling
processChildren and popped after.
In `@packages/compiler-spatial/src/transforms/spatialVShow.ts`:
- Around line 19-22: The error report for a missing v-show expression omits the
directive location; update the call inside the if (!dir.exp) block so
context.onError invokes createDOMCompilerError with the location: pass dir.loc
as the second argument along with DOMErrorCodes.X_V_SHOW_NO_EXPRESSION (the same
pattern used in packages/compiler-dom/src/transforms/vShow.ts), ensuring the
call from context.onError(createDOMCompilerError(...)) includes the location to
improve diagnostic precision.
---
Nitpick comments:
In `@packages/compiler-spatial/__tests__/spatialElement.spec.ts`:
- Around line 67-74: The test "Button with `@tap` and children" currently only
asserts the action closure; update the test to also assert that child rendering
is emitted by checking for Text("Click") in the output from getSwiftLines — use
the existing lines variable (from getSwiftLines(...)) and add an expect that
lines.some(l => l.includes('Text("Click")')) or equivalent to ensure child
emission is validated alongside Button(action: { vm.emit("increment") }).
In `@packages/compiler-spatial/__tests__/spatialVIf.spec.ts`:
- Around line 22-29: Update the test 'v-if with v-else-if and v-else' so it not
only checks control-flow tokens but also asserts that the branch bodies are
rendered: after calling getSwiftLines(...) verify that generated lines include
the rendered branch contents like Text("B") for the v-else-if branch and
Text("C") for the v-else branch (in addition to the existing checks for if
vm.get("a"), } else if vm.get("b"), and } else {). This change targets the test
case in packages/compiler-spatial/__tests__/spatialVIf.spec.ts and ensures the
output contains the expected Text("B") and Text("C") strings.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c6271979-2daa-4068-8f7b-f5a73b09c2bd
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (21)
packages/compiler-sfc/package.jsonpackages/compiler-sfc/src/index.tspackages/compiler-spatial/LICENSEpackages/compiler-spatial/__tests__/spatialCodegen.spec.tspackages/compiler-spatial/__tests__/spatialElement.spec.tspackages/compiler-spatial/__tests__/spatialVFor.spec.tspackages/compiler-spatial/__tests__/spatialVIf.spec.tspackages/compiler-spatial/__tests__/spatialVModel.spec.tspackages/compiler-spatial/__tests__/spatialVShow.spec.tspackages/compiler-spatial/__tests__/utils.tspackages/compiler-spatial/package.jsonpackages/compiler-spatial/src/errors.tspackages/compiler-spatial/src/index.tspackages/compiler-spatial/src/runtimeHelpers.tspackages/compiler-spatial/src/spatialCodegenTransform.tspackages/compiler-spatial/src/swiftCodegen.tspackages/compiler-spatial/src/transforms/spatialTransformElement.tspackages/compiler-spatial/src/transforms/spatialVFor.tspackages/compiler-spatial/src/transforms/spatialVIf.tspackages/compiler-spatial/src/transforms/spatialVModel.tspackages/compiler-spatial/src/transforms/spatialVShow.ts
✅ Files skipped from review due to trivial changes (5)
- packages/compiler-spatial/LICENSE
- packages/compiler-sfc/package.json
- packages/compiler-spatial/package.json
- packages/compiler-spatial/tests/spatialCodegen.spec.ts
- packages/compiler-spatial/tests/utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/compiler-sfc/src/index.ts
| export interface SpatialTransformContext { | ||
| root: RootNode | ||
| options: CompilerOptions | ||
| lines: string[] | ||
| indentLevel: number | ||
| events: Map<string, string> | ||
| bindings: string[] | ||
| spatialGestures: string[] |
There was a problem hiding this comment.
Return the collected bridge metadata from codegen.
This context already tracks events, bindings, and spatialGestures, but spatialCodegenTransform() drops everything except lines. That leaves the caller no way to build a non-empty BridgeManifest, so templates using @tap, v-model, or spatial gesture directives will still surface an empty manifest.
Also applies to: 66-72
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/compiler-spatial/src/spatialCodegenTransform.ts` around lines 18 -
25, The transform currently discards the collected metadata (events, bindings,
spatialGestures) in SpatialTransformContext when returning from
spatialCodegenTransform; update spatialCodegenTransform to include and return
those context fields (events, bindings, spatialGestures) alongside lines so the
caller can build a BridgeManifest; locate the SpatialTransformContext interface
and the spatialCodegenTransform function (and the return at the second instance
around the lines noted) and modify the return value to propagate the
Map<string,string> events, string[] bindings and string[] spatialGestures
instead of only returning lines.
| default: | ||
| context.onError( | ||
| createSpatialCompilerError( | ||
| SpatialErrorCodes.X_SPATIAL_INVALID_AST_NODE, | ||
| (child as any).loc, | ||
| ), | ||
| ) | ||
| // make sure we exhaust all possible types | ||
| const _exhaustiveCheck: never = child | ||
| return _exhaustiveCheck |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
wc -l packages/compiler-spatial/src/spatialCodegenTransform.tsRepository: vuejs/core
Length of output: 114
🏁 Script executed:
sed -n '100,170p' packages/compiler-spatial/src/spatialCodegenTransform.ts | cat -nRepository: vuejs/core
Length of output: 2702
Wrap default: clauses in blocks.
Biome's noSwitchDeclarations rule is applicable here: variable declarations in switch cases without block wrappers are hoisted to the switch scope, creating potential binding conflicts. Both occurrences (lines 112–121 and 150–159) need to be wrapped.
Suggested fix
- default:
+ default: {
context.onError(
createSpatialCompilerError(
SpatialErrorCodes.X_SPATIAL_INVALID_AST_NODE,
(child as any).loc,
),
)
// make sure we exhaust all possible types
const _exhaustiveCheck: never = child
return _exhaustiveCheck
+ }
@@
- default:
+ default: {
context.onError(
createSpatialCompilerError(
SpatialErrorCodes.X_SPATIAL_INVALID_AST_NODE,
(child as any).loc,
),
)
// make sure we exhaust all possible types
const _exhaustiveCheck2: never = child
return _exhaustiveCheck2
+ }🧰 Tools
🪛 Biome (2.4.7)
[error] 120-120: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
(lint/correctness/noSwitchDeclarations)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/compiler-spatial/src/spatialCodegenTransform.ts` around lines 112 -
121, The default clauses in the switch inside spatialCodegenTransform.ts declare
a variable (_exhaustiveCheck) without a block, which risks hoisting and binding
conflicts; wrap each default: case body in a block ({ ... }) so the const
_exhaustiveCheck: never = child and the context.onError(...) call are scoped to
the case. Update both occurrences referenced by the code that calls
context.onError(createSpatialCompilerError(SpatialErrorCodes.X_SPATIAL_INVALID_AST_NODE,
(child as any).loc)) and the subsequent const _exhaustiveCheck declarations to
be inside their own {} blocks.
| if (prop.name === 'bind' && prop.arg && isStaticExp(prop.arg) && prop.exp) { | ||
| const attrName = prop.arg.content | ||
| const expr = getExpContent(prop.exp) | ||
|
|
||
| const modifierFn = MODIFIER_PROPS[attrName] | ||
| if (modifierFn) { | ||
| modifiers.push(modifierFn(`vm.get("${expr}")`)) | ||
| return | ||
| } | ||
|
|
||
| const constructorPropsMap = CONSTRUCTOR_PROPS[swiftView] || {} | ||
| if (attrName in constructorPropsMap) { | ||
| const swiftParam = constructorPropsMap[attrName] | ||
| if (swiftParam) { | ||
| constructorArgs.push(`${swiftParam}: vm.get("${expr}")`) | ||
| } else { | ||
| constructorArgs.push(`vm.get("${expr}")`) | ||
| } | ||
| } |
There was a problem hiding this comment.
Dynamic element attrs are dropped for the specialized emitters.
processDirectiveProp() only carries v-bind values through CONSTRUCTOR_PROPS/MODIFIER_PROPS, but these emitters later read only static attrs via findAttr(). That means :src, :name, :placeholder, :title, and :id collapse to empty strings, and even the constructorArgs collected for WindowGroup/ImmersiveSpace are ignored because those emitters never consume them. A shared "static-or-bound attr" resolver would avoid losing these inputs.
Also applies to: 279-315, 388-429
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/compiler-spatial/src/transforms/spatialTransformElement.ts` around
lines 165 - 183, processDirectiveProp currently stores v-bind results only in
CONSTRUCTOR_PROPS/MODIFIER_PROPS (via constructorArgs and modifiers) so
downstream emitters that call findAttr lose bound values; update the handling so
bound attrs are discoverable by findAttr or provide a shared resolver.
Specifically, in processDirectiveProp (the code branch checking prop.name ===
'bind', isStaticExp, getExpContent) make bound values available as attribute
equivalents (e.g., add a synthesized prop entry onto the node.props/attrs or a
sidecar map) instead of only pushing into constructorArgs/modifiers, and
implement a single helper resolveStaticOrBoundAttr(name) that findAttr and all
specialized emitters call (replace direct findAttr uses) to return either the
static value or the bound expression from
CONSTRUCTOR_PROPS/MODIFIER_PROPS/constructorArgs/modifiers; ensure symbols
referenced include processDirectiveProp, CONSTRUCTOR_PROPS, MODIFIER_PROPS,
findAttr, constructorArgs, modifiers, getExpContent so emitters like
WindowGroup/ImmersiveSpace consume the bound inputs.
| // Check for :key on the child | ||
| const keyAlias = node.keyAlias | ||
| const keyExpr = keyAlias | ||
| ? typeof keyAlias !== 'string' && (keyAlias as any).content | ||
| ? (keyAlias as any).content | ||
| : 'id' | ||
| : 'id' | ||
|
|
||
| context.pushLine( | ||
| `ForEach(vm.getArray("${sourceExpr}"), id: \\.${keyExpr}) { ${valueName} in`, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
fd 'ast\.ts$|vFor\.ts$' packages | xargs rg -n -C4 'interface ForNode|keyAlias|objectIndexAlias|findProp\(|keyProp'Repository: vuejs/core
Length of output: 9503
🏁 Script executed:
cat -n packages/compiler-spatial/src/transforms/spatialVFor.ts | head -100Repository: vuejs/core
Length of output: 1651
🏁 Script executed:
rg -n "findProp|:key|keyProp" packages/compiler-spatial/src/transforms/Repository: vuejs/core
Length of output: 145
🏁 Script executed:
rg -n "X_SPATIAL_MISSING_KEY_IN_FOR" packages/compiler-spatial/Repository: vuejs/core
Length of output: 305
🏁 Script executed:
rg -n "X_SPATIAL_MISSING_KEY_IN_FOR" packages/compiler-spatial/ -A 3 -B 3Repository: vuejs/core
Length of output: 1189
🏁 Script executed:
# Search for findProp usage in compiler-spatial to see if :key binding is extracted elsewhere
rg -n "findProp" packages/compiler-spatial/Repository: vuejs/core
Length of output: 36
🏁 Script executed:
# Check how compiler-dom/core extracts :key from child elements in v-for
rg -n "findProp.*key" packages/compiler-core/src/transforms/vFor.ts | head -20Repository: vuejs/core
Length of output: 162
🏁 Script executed:
# Search for any key validation or error emission in compiler-spatial
rg -n "createCompilerError|onError" packages/compiler-spatial/src/Repository: vuejs/core
Length of output: 1006
🏁 Script executed:
# Check if there's a ForNode children inspection that should look for :key
rg -n "node\.children" packages/compiler-spatial/src/transforms/Repository: vuejs/core
Length of output: 1170
🏁 Script executed:
# Check if X_SPATIAL_MISSING_KEY_IN_FOR is ever emitted
rg -rn "X_SPATIAL_MISSING_KEY_IN_FOR" packages/compiler-spatial/ --type tsRepository: vuejs/core
Length of output: 245
🏁 Script executed:
# Check the full spatialVFor.ts file to see if there's any other key handling
cat -n packages/compiler-spatial/src/transforms/spatialVFor.tsRepository: vuejs/core
Length of output: 1651
🏁 Script executed:
# Check spatialCodegenTransform.ts to see processChildren and overall context
rg -n "processChildren\|processFor" packages/compiler-spatial/src/spatialCodegenTransform.ts -A 5 -B 5Repository: vuejs/core
Length of output: 36
🏁 Script executed:
# Check if there are any tests that might show expected behavior
find packages/compiler-spatial -name "*.spec.ts" -o -name "*.test.ts" | xargs rg -l "spatialProcessFor\|spatialVFor" 2>/dev/null || echo "No test files found"Repository: vuejs/core
Length of output: 73
🏁 Script executed:
# Search for any validation logic that might check for :key in ForNode children
rg -n "node\.children" packages/compiler-spatial/src/transforms/spatialVFor.ts -A 10 -B 2Repository: vuejs/core
Length of output: 36
🏁 Script executed:
# Verify: does compiler-spatial have any logic to validate/extract :key from child elements?
cat packages/compiler-spatial/src/transforms/spatialVFor.ts | grep -A 50 "spatialProcessFor"Repository: vuejs/core
Length of output: 887
🏁 Script executed:
# Let's look at how compiler-dom's findProp is defined/exported
rg -n "export.*findProp\|function findProp" packages/compiler-core/src/ -A 3Repository: vuejs/core
Length of output: 36
🏁 Script executed:
# Check if spatialVFor imports findProp and if it should be using it
head -15 packages/compiler-spatial/src/transforms/spatialVFor.tsRepository: vuejs/core
Length of output: 455
🏁 Script executed:
# Verify the comment is misleading: check what node.children would contain
rg -n "node.children" packages/compiler-core/src/transforms/vFor.ts -B 2 -A 2 | head -30Repository: vuejs/core
Length of output: 939
🏁 Script executed:
# Let's see how compiler-dom exports findProp and related functions
rg -n "^export.*findProp" packages/compiler-core/src/Repository: vuejs/core
Length of output: 119
🏁 Script executed:
# Check what imports are available from compiler-dom in spatialVFor.ts
node -e "
const path = require('path');
const fs = require('fs');
const file = fs.readFileSync('packages/compiler-spatial/src/transforms/spatialVFor.ts', 'utf8');
console.log(file.split('\n').slice(0, 15).join('\n'));
"Repository: vuejs/core
Length of output: 455
🏁 Script executed:
# Verify: Is findProp exported from compiler-dom?
rg -n "export.*findProp\|export {.*findProp" packages/compiler-dom/index.ts packages/compiler-core/src/Repository: vuejs/core
Length of output: 198
🏁 Script executed:
# Check if findProp is exported from compiler-dom
rg -n "findProp" packages/compiler-dom/index.tsRepository: vuejs/core
Length of output: 124
🏁 Script executed:
# Check the full export list from compiler-dom
cat packages/compiler-dom/index.ts | grep -A 100 "export"Repository: vuejs/core
Length of output: 116
🏁 Script executed:
# Let's understand the correct behavior: check what node.children contains in a ForNode
rg -n "children:" packages/compiler-core/src/ast.ts | grep -i for -A 5 -B 5Repository: vuejs/core
Length of output: 324
🏁 Script executed:
# Check the ForNode interface definition
rg -n "interface ForNode" packages/compiler-core/src/ast.ts -A 10Repository: vuejs/core
Length of output: 407
🏁 Script executed:
# Let's look at how compiler-core validates or handles :key in v-for
rg -n "keyProp\|X_V_FOR.*KEY" packages/compiler-core/src/transforms/vFor.ts | head -20Repository: vuejs/core
Length of output: 36
🏁 Script executed:
# Now let's check if there's any :key extraction logic we should be doing in spatial
cat packages/compiler-core/src/transforms/vFor.ts | sed -n '60,85p'Repository: vuejs/core
Length of output: 907
🏁 Script executed:
# Check what is exported from compiler-dom
fd "index\\.ts$" packages/compiler-dom/dist/ | head -5Repository: vuejs/core
Length of output: 168
🏁 Script executed:
# Find the main entry point for compiler-dom
ls -la packages/compiler-dom/Repository: vuejs/core
Length of output: 521
🏁 Script executed:
# Check if findProp is exported from compiler-dom package
rg -n "findProp" packages/compiler-dom/Repository: vuejs/core
Length of output: 206
🏁 Script executed:
# Verify the imports available from `@vue/compiler-dom`
cat packages/compiler-dom/package.json | grep -A 5 "\"exports\""Repository: vuejs/core
Length of output: 248
🏁 Script executed:
# Check the compiler-dom index.js to see what's exported
cat packages/compiler-dom/index.jsRepository: vuejs/core
Length of output: 242
🏁 Script executed:
# Check if findProp is re-exported from compiler-core in compiler-dom
rg -n "findProp" packages/compiler-dom/src/ -lRepository: vuejs/core
Length of output: 100
🏁 Script executed:
# Look at the compiler-dom src structure
ls -la packages/compiler-dom/src/Repository: vuejs/core
Length of output: 609
🏁 Script executed:
# Check what compiler-dom exports
cat packages/compiler-dom/src/index.tsRepository: vuejs/core
Length of output: 2456
🏁 Script executed:
# Check the usage pattern: if spatialVFor imports from `@vue/compiler-dom`, what can it access?
rg -n "export.*from.*compiler-core" packages/compiler-dom/src/index.tsRepository: vuejs/core
Length of output: 91
Extract the actual :key binding from child elements instead of using node.keyAlias.
node.keyAlias is the second loop alias from v-for="(value, key) in source", not the rendered child's :key binding. The code silently defaults to \.id without ever inspecting actual key bindings on the elements. Use findProp(node, 'key') to extract the rendered child's :key binding, similar to how compiler-core/vFor.ts does it. Currently, SpatialErrorCodes.X_SPATIAL_MISSING_KEY_IN_FOR in packages/compiler-spatial/src/errors.ts is unreachable.
| context.pushLine( | ||
| `ForEach(vm.getArray("${sourceExpr}"), id: \\.${keyExpr}) { ${valueName} in`, | ||
| ) | ||
| context.indent() | ||
| processChildren(node, context) |
There was a problem hiding this comment.
The ForEach alias never becomes usable inside the generated body.
This introduces a local Swift alias ({ ${valueName} in), but the downstream emitters still turn expressions into global lookups like vm.get("item.name") in packages/compiler-spatial/src/spatialCodegenTransform.ts:129-132, packages/compiler-spatial/src/transforms/spatialVIf.ts:28-35, and packages/compiler-spatial/src/transforms/spatialTransformElement.ts:165-183. As written, {{ item.name }}, v-if="item.visible", and :title="item.name" inside the loop will never read the local item.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/compiler-spatial/src/transforms/spatialVFor.ts` around lines 39 -
43, The ForEach code creates a Swift local alias (the `${valueName}` closure
param) but the expression emitter still emits global lookups via vm.get/
vm.getArray so loop body expressions never reference the local; to fix, add a
loop-local binding when emitting the ForEach (in spatialVFor.ts around
ForEach(vm.getArray(...), id: \\.${keyExpr}) { ${valueName} in and
processChildren(node, context)) and update the expression emitter logic (where
vm.get is produced in spatialCodegenTransform.ts and used by spatialVIf.ts and
spatialTransformElement.ts) to consult the current codegen scope/locals before
emitting a vm.get — if a local binding for the root identifier exists emit a
direct local access (the `${valueName}`/property lookup) instead of
vm.get("..."); ensure the binding is pushed before calling processChildren and
popped after.
| if (!dir.exp) { | ||
| context.onError( | ||
| createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION), | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify current repository patterns for DOM compiler error location propagation.
rg -nP --type=ts -C2 'createDOMCompilerError\('
rg -nP --type=ts -C2 'X_V_SHOW_NO_EXPRESSION'Repository: vuejs/core
Length of output: 9688
Pass directive location when reporting missing v-show expressions.
The call to createDOMCompilerError() on line 21 omits the location parameter. Unlike the similar pattern in packages/compiler-dom/src/transforms/vShow.ts which passes loc, this reduces error diagnostic precision. Add dir.loc as the second argument to match established patterns.
Proposed fix
if (!dir.exp) {
context.onError(
- createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION),
+ createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION, dir.loc),
)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!dir.exp) { | |
| context.onError( | |
| createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION), | |
| ) | |
| if (!dir.exp) { | |
| context.onError( | |
| createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION, dir.loc), | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/compiler-spatial/src/transforms/spatialVShow.ts` around lines 19 -
22, The error report for a missing v-show expression omits the directive
location; update the call inside the if (!dir.exp) block so context.onError
invokes createDOMCompilerError with the location: pass dir.loc as the second
argument along with DOMErrorCodes.X_V_SHOW_NO_EXPRESSION (the same pattern used
in packages/compiler-dom/src/transforms/vShow.ts), ensuring the call from
context.onError(createDOMCompilerError(...)) includes the location to improve
diagnostic precision.
|
Thank you for your enthusiasm for Vue visionOS support! This is an interesting idea, but we don't think platform-specific compilers like this belong in the If you find that |
|
Hello @edison1105 , thank you for your interest in the Zixiao Labs Industries team. Indeed, we feel that including platform-specific compilers in the core repository might increase the burden on Evan You and the team, and could potentially lead to vendor lock-in (we don't want to become the next Vercel or Next.js). Therefore, the main repository will only contain a minimal number of hooks; compilation, packaging, and Apple's JavaScriptCore-specific handling will be handled by our Nasti (https://github.com/zixiao-labs/Nasti, which is also a general-purpose builder like Vite) (just add configuration). If you have any questions, please open a new issue or contact amya167@zixiaolabs.com |
|
Thanks for the clarification! The Nasti-based approach sounds like a much better fit — keeping Core lean while letting the platform-specific tooling live in its own ecosystem. Since the main compilation logic will live in Nasti rather than Core, I'd suggest closing this PR for now. If you identify any specific extension points or hooks that Core needs to expose to better support this use case, feel free to open a dedicated issue and we'll be happy to discuss. Looking forward to seeing Nasti take shape! |
Summary
Introduce spatial computing compilation support for Vue SFCs, enabling compilation of
<template spatial>to native SwiftUI views for visionOS. This PR implements two phases:Phase 1: SFC Parser (
compiler-sfc)<script setup spatial>,<template spatial>,<style spatial>attributesspatial,stylesSpatialfields toSFCDescriptorsetup, no coexistence with normal<script>isSpatialComponent()utilityPhase 2: SwiftUI Compiler (
compiler-spatial)@vue/compiler-spatialpackage followingcompiler-ssrtwo-pass architecture<v-stack>→VStack,<text>→Text,<button>→Button,<model3d>→Model3D, etc.)v-if→ Swiftif/else,v-for→ForEach,v-model→vm.binding(),v-show→.opacity(), spatial gesture directives<spatial-window>→WindowGroup,<spatial-volume>→.windowStyle(.volumetric),<spatial-immersive>→ImmersiveSpaceViewstructs withVueViewModelbridgecompileSpatialfrom@vue/compiler-sfcExample
Compiles to:
Test plan
compiler-sfc(spatial attribute parsing, validation errors)compiler-spatial(elements, v-if, v-for, v-model, v-show, full codegen)compiler-sfctests pass unchanged🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Behavior Changes
Bug Fixes / Validation
Tests