chore: Merge branch 'edison/feat/vaporTeleport' into edison/testVapor

This commit is contained in:
daiwei 2025-06-23 15:22:11 +08:00
commit fd2e46bf64
41 changed files with 1849 additions and 229 deletions

View File

@ -0,0 +1,62 @@
import path from 'node:path'
import {
E2E_TIMEOUT,
setupPuppeteer,
} from '../../../packages/vue/__tests__/e2e/e2eUtils'
import connect from 'connect'
import sirv from 'sirv'
import { nextTick } from 'vue'
import { ports } from '../utils'
const { page, click, html } = setupPuppeteer()
describe('vapor teleport', () => {
let server: any
const port = ports.teleport
beforeAll(() => {
server = connect()
.use(sirv(path.resolve(import.meta.dirname, '../dist')))
.listen(port)
process.on('SIGTERM', () => server && server.close())
})
afterAll(() => {
server.close()
})
beforeEach(async () => {
const baseUrl = `http://localhost:${port}/teleport/`
await page().goto(baseUrl)
await page().waitForSelector('#app')
})
test(
'render vdom component',
async () => {
const targetSelector = '.target'
const testSelector = '.interop-render-vdom-comp'
const containerSelector = `${testSelector} > div`
const btnSelector = `${testSelector} > button`
const tt = await html('#app')
console.log(tt)
// teleport is disabled
expect(await html(containerSelector)).toBe('<h1>vdom comp</h1>')
expect(await html(targetSelector)).toBe('')
// enable teleport
await click(btnSelector)
await nextTick()
expect(await html(containerSelector)).toBe('')
expect(await html(targetSelector)).toBe('<h1>vdom comp</h1>')
// disable teleport
await click(btnSelector)
await nextTick()
expect(await html(containerSelector)).toBe('<h1>vdom comp</h1>')
expect(await html(targetSelector)).toBe('')
},
E2E_TIMEOUT,
)
})

View File

@ -5,6 +5,7 @@ import {
} from '../../../packages/vue/__tests__/e2e/e2eUtils' } from '../../../packages/vue/__tests__/e2e/e2eUtils'
import connect from 'connect' import connect from 'connect'
import sirv from 'sirv' import sirv from 'sirv'
import { ports } from '../utils'
describe('e2e: todomvc', () => { describe('e2e: todomvc', () => {
const { const {
@ -23,7 +24,7 @@ describe('e2e: todomvc', () => {
} = setupPuppeteer() } = setupPuppeteer()
let server: any let server: any
const port = '8194' const port = ports.todomvc
beforeAll(() => { beforeAll(() => {
server = connect() server = connect()
.use(sirv(path.resolve(import.meta.dirname, '../dist'))) .use(sirv(path.resolve(import.meta.dirname, '../dist')))

View File

@ -7,6 +7,7 @@ import connect from 'connect'
import sirv from 'sirv' import sirv from 'sirv'
import { expect } from 'vitest' import { expect } from 'vitest'
const { page, nextFrame, timeout, html, transitionStart } = setupPuppeteer() const { page, nextFrame, timeout, html, transitionStart } = setupPuppeteer()
import { ports } from '../utils'
const duration = process.env.CI ? 200 : 50 const duration = process.env.CI ? 200 : 50
const buffer = process.env.CI ? 50 : 20 const buffer = process.env.CI ? 50 : 20
@ -14,7 +15,7 @@ const transitionFinish = (time = duration) => timeout(time + buffer)
describe('vapor transition-group', () => { describe('vapor transition-group', () => {
let server: any let server: any
const port = '8196' const port = ports.transitionGroup
beforeAll(() => { beforeAll(() => {
server = connect() server = connect()
.use(sirv(path.resolve(import.meta.dirname, '../dist'))) .use(sirv(path.resolve(import.meta.dirname, '../dist')))

View File

@ -19,6 +19,7 @@ const {
waitForElement, waitForElement,
click, click,
} = setupPuppeteer() } = setupPuppeteer()
import { ports } from '../utils'
const duration = process.env.CI ? 200 : 50 const duration = process.env.CI ? 200 : 50
const buffer = process.env.CI ? 50 : 20 const buffer = process.env.CI ? 50 : 20
@ -26,7 +27,7 @@ const transitionFinish = (time = duration) => timeout(time + buffer)
describe('vapor transition', () => { describe('vapor transition', () => {
let server: any let server: any
const port = '8195' const port = ports.transition
beforeAll(() => { beforeAll(() => {
server = connect() server = connect()
.use(sirv(path.resolve(import.meta.dirname, '../dist'))) .use(sirv(path.resolve(import.meta.dirname, '../dist')))

View File

@ -21,9 +21,12 @@ const duration = process.env.CI ? 200 : 50
const buffer = process.env.CI ? 50 : 20 const buffer = process.env.CI ? 50 : 20
const transitionFinish = (time = duration) => timeout(time + buffer) const transitionFinish = (time = duration) => timeout(time + buffer)
import { ports } from '../utils'
import { nextTick } from 'vue'
describe('vdom / vapor interop', () => { describe('vdom / vapor interop', () => {
let server: any let server: any
const port = '8193' const port = ports.vdomInterop
beforeAll(() => { beforeAll(() => {
server = connect() server = connect()
.use(sirv(path.resolve(import.meta.dirname, '../dist'))) .use(sirv(path.resolve(import.meta.dirname, '../dist')))
@ -31,6 +34,12 @@ describe('vdom / vapor interop', () => {
process.on('SIGTERM', () => server && server.close()) process.on('SIGTERM', () => server && server.close())
}) })
beforeEach(async () => {
const baseUrl = `http://localhost:${port}/interop/`
await page().goto(baseUrl)
await page().waitForSelector('#app')
})
afterAll(() => { afterAll(() => {
server.close() server.close()
}) })
@ -255,5 +264,33 @@ describe('vdom / vapor interop', () => {
}, },
E2E_TIMEOUT, E2E_TIMEOUT,
) )
describe('teleport', () => {
const testSelector = '.teleport'
test('render vapor component', async () => {
const targetSelector = `${testSelector} .teleport-target`
const containerSelector = `${testSelector} .render-vapor-comp`
const buttonSelector = `${containerSelector} button`
// teleport is disabled by default
expect(await html(containerSelector)).toBe(
`<button>toggle</button><div>vapor comp</div>`,
)
expect(await html(targetSelector)).toBe('')
// disabled -> enabled
await click(buttonSelector)
await nextTick()
expect(await html(containerSelector)).toBe(`<button>toggle</button>`)
expect(await html(targetSelector)).toBe('<div>vapor comp</div>')
// enabled -> disabled
await click(buttonSelector)
await nextTick()
expect(await html(containerSelector)).toBe(
`<button>toggle</button><div>vapor comp</div>`,
)
expect(await html(targetSelector)).toBe('')
})
})
}) })
}) })

View File

@ -2,6 +2,7 @@
<a href="/todomvc/">Vapor TodoMVC</a> <a href="/todomvc/">Vapor TodoMVC</a>
<a href="/transition/">Vapor Transition</a> <a href="/transition/">Vapor Transition</a>
<a href="/transition-group/">Vapor TransitionGroup</a> <a href="/transition-group/">Vapor TransitionGroup</a>
<a href="/teleport/">Vapor Teleport</a>
<style> <style>
a { a {

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, shallowRef } from 'vue' import { ref, shallowRef } from 'vue'
import VaporComp from './VaporComp.vue' import VaporComp from './components/VaporComp.vue'
import VaporCompA from '../transition/components/VaporCompA.vue' import VaporCompA from '../transition/components/VaporCompA.vue'
import VdomComp from '../transition/components/VdomComp.vue' import VdomComp from '../transition/components/VdomComp.vue'
import VaporSlot from '../transition/components/VaporSlot.vue' import VaporSlot from '../transition/components/VaporSlot.vue'
@ -17,6 +17,9 @@ function toggleInteropComponent() {
const items = ref(['a', 'b', 'c']) const items = ref(['a', 'b', 'c'])
const enterClick = () => items.value.push('d', 'e') const enterClick = () => items.value.push('d', 'e')
import SimpleVaporComp from './components/SimpleVaporComp.vue'
const disabled = ref(true)
</script> </script>
<template> <template>
@ -69,4 +72,15 @@ const enterClick = () => items.value.push('d', 'e')
</div> </div>
</div> </div>
</div> </div>
<!-- teleport -->
<div class="teleport">
<div class="teleport-target"></div>
<div class="render-vapor-comp">
<button @click="disabled = !disabled">toggle</button>
<Teleport to=".teleport-target" defer :disabled="disabled">
<SimpleVaporComp />
</Teleport>
</div>
</div>
<!-- teleport end-->
</template> </template>

View File

@ -0,0 +1,6 @@
<script setup vapor lang="ts">
const msg = 'vapor comp'
</script>
<template>
<div>{{ msg }}</div>
</template>

View File

@ -0,0 +1,17 @@
<script setup vapor>
import { ref, Teleport } from 'vue'
import VdomComp from './components/VdomComp.vue'
const disabled = ref(true)
</script>
<template>
<div class="target"></div>
<div class="interop-render-vdom-comp">
<button @click="disabled = !disabled">toggle</button>
<div>
<Teleport to=".target" defer :disabled>
<VdomComp />
</Teleport>
</div>
</div>
</template>

View File

@ -0,0 +1,7 @@
<script setup lang="ts">
const msg = 'vdom comp'
</script>
<template>
<h1>{{ msg }}</h1>
</template>

View File

@ -0,0 +1,2 @@
<script type="module" src="./main.ts"></script>
<div id="app"></div>

View File

@ -0,0 +1,5 @@
import { createVaporApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'
import 'todomvc-app-css/index.css'
createVaporApp(App).use(vaporInteropPlugin).mount('#app')

View File

@ -0,0 +1,8 @@
// make sure these ports are unique
export const ports = {
vdomInterop: 8193,
todomvc: 8194,
transition: 8195,
transitionGroup: 8196,
teleport: 8197,
}

View File

@ -14,6 +14,7 @@ export default defineConfig({
input: { input: {
interop: resolve(import.meta.dirname, 'interop/index.html'), interop: resolve(import.meta.dirname, 'interop/index.html'),
todomvc: resolve(import.meta.dirname, 'todomvc/index.html'), todomvc: resolve(import.meta.dirname, 'todomvc/index.html'),
teleport: resolve(import.meta.dirname, 'teleport/index.html'),
transition: resolve(import.meta.dirname, 'transition/index.html'), transition: resolve(import.meta.dirname, 'transition/index.html'),
transitionGroup: resolve( transitionGroup: resolve(
import.meta.dirname, import.meta.dirname,

View File

@ -39,7 +39,6 @@ import { genEventHandler } from './event'
import { genDirectiveModifiers, genDirectivesForElement } from './directive' import { genDirectiveModifiers, genDirectivesForElement } from './directive'
import { genBlock } from './block' import { genBlock } from './block'
import { genModelHandler } from './vModel' import { genModelHandler } from './vModel'
import { isBuiltInComponent } from '../utils' import { isBuiltInComponent } from '../utils'
export function genCreateComponent( export function genCreateComponent(

View File

@ -115,10 +115,17 @@ export function isTransitionGroupTag(tag: string): boolean {
return tag === 'transitiongroup' || tag === 'vaportransitiongroup' return tag === 'transitiongroup' || tag === 'vaportransitiongroup'
} }
export function isTeleportTag(tag: string): boolean {
tag = tag.toLowerCase()
return tag === 'teleport' || tag === 'vaporteleport'
}
export function isBuiltInComponent(tag: string): string | undefined { export function isBuiltInComponent(tag: string): string | undefined {
if (isTransitionTag(tag)) { if (isTransitionTag(tag)) {
return 'VaporTransition' return 'VaporTransition'
} else if (isTransitionGroupTag(tag)) { } else if (isTransitionGroupTag(tag)) {
return 'VaporTransitionGroup' return 'VaporTransitionGroup'
} else if (isTeleportTag(tag)) {
return 'VaporTeleport'
} }
} }

View File

@ -27,10 +27,10 @@ export const TeleportEndKey: unique symbol = Symbol('_vte')
export const isTeleport = (type: any): boolean => type.__isTeleport export const isTeleport = (type: any): boolean => type.__isTeleport
const isTeleportDisabled = (props: VNode['props']): boolean => export const isTeleportDisabled = (props: VNode['props']): boolean =>
props && (props.disabled || props.disabled === '') props && (props.disabled || props.disabled === '')
const isTeleportDeferred = (props: VNode['props']): boolean => export const isTeleportDeferred = (props: VNode['props']): boolean =>
props && (props.defer || props.defer === '') props && (props.defer || props.defer === '')
const isTargetSVG = (target: RendererElement): boolean => const isTargetSVG = (target: RendererElement): boolean =>
@ -39,7 +39,7 @@ const isTargetSVG = (target: RendererElement): boolean =>
const isTargetMathML = (target: RendererElement): boolean => const isTargetMathML = (target: RendererElement): boolean =>
typeof MathMLElement === 'function' && target instanceof MathMLElement typeof MathMLElement === 'function' && target instanceof MathMLElement
const resolveTarget = <T = RendererElement>( export const resolveTarget = <T = RendererElement>(
props: TeleportProps | null, props: TeleportProps | null,
select: RendererOptions['querySelector'], select: RendererOptions['querySelector'],
): T | null => { ): T | null => {

View File

@ -119,7 +119,7 @@ function reload(id: string, newComp: HMRComponent): void {
// create a snapshot which avoids the set being mutated during updates // create a snapshot which avoids the set being mutated during updates
const instances = [...record.instances] const instances = [...record.instances]
if (newComp.vapor) { if (newComp.__vapor) {
for (const instance of instances) { for (const instance of instances) {
instance.hmrReload!(newComp) instance.hmrReload!(newComp)
} }

View File

@ -574,6 +574,14 @@ export { performTransitionEnter, performTransitionLeave } from './renderer'
* @internal * @internal
*/ */
export { ensureVaporSlotFallback } from './helpers/renderSlot' export { ensureVaporSlotFallback } from './helpers/renderSlot'
/**
* @internal
*/
export {
resolveTarget as resolveTeleportTarget,
isTeleportDisabled,
isTeleportDeferred,
} from './components/Teleport'
/** /**
* @internal * @internal
*/ */

View File

@ -1,10 +1,5 @@
import { import { insert, normalizeBlock, prepend, remove } from '../src/block'
VaporFragment, import { VaporFragment } from '../src/fragment'
insert,
normalizeBlock,
prepend,
remove,
} from '../src/block'
const node1 = document.createTextNode('node1') const node1 = document.createTextNode('node1')
const node2 = document.createTextNode('node2') const node2 = document.createTextNode('node2')

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { currentInstance, resolveDynamicComponent } from '@vue/runtime-dom' import { currentInstance, resolveDynamicComponent } from '@vue/runtime-dom'
import { DynamicFragment, type VaporFragment, insert } from './block' import { insert } from './block'
import { createComponentWithFallback, emptyContext } from './component' import { createComponentWithFallback, emptyContext } from './component'
import { renderEffect } from './renderEffect' import { renderEffect } from './renderEffect'
import type { RawProps } from './componentProps' import type { RawProps } from './componentProps'
@ -11,6 +11,7 @@ import {
} from './insertionState' } from './insertionState'
import { DYNAMIC_COMPONENT_ANCHOR_LABEL } from '@vue/shared' import { DYNAMIC_COMPONENT_ANCHOR_LABEL } from '@vue/shared'
import { isHydrating, locateHydrationNode } from './dom/hydration' import { isHydrating, locateHydrationNode } from './dom/hydration'
import { DynamicFragment, type VaporFragment } from './fragment'
export function createDynamicComponent( export function createDynamicComponent(
getter: () => any, getter: () => any,

View File

@ -19,12 +19,7 @@ import {
isString, isString,
} from '@vue/shared' } from '@vue/shared'
import { createComment, createTextNode } from './dom/node' import { createComment, createTextNode } from './dom/node'
import { import { type Block, insert, remove as removeBlock } from './block'
type Block,
VaporFragment,
insert,
remove as removeBlock,
} from './block'
import { warn } from '@vue/runtime-dom' import { warn } from '@vue/runtime-dom'
import { currentInstance, isVaporComponent } from './component' import { currentInstance, isVaporComponent } from './component'
import type { DynamicSlot } from './componentSlots' import type { DynamicSlot } from './componentSlots'
@ -37,6 +32,7 @@ import {
locateHydrationNode, locateHydrationNode,
locateVaporFragmentAnchor, locateVaporFragmentAnchor,
} from './dom/hydration' } from './dom/hydration'
import { VaporFragment } from './fragment'
import { import {
insertionAnchor, insertionAnchor,
insertionParent, insertionParent,

View File

@ -1,4 +1,5 @@
import { type Block, type BlockFn, DynamicFragment } from './block' import type { Block, BlockFn } from './block'
import { DynamicFragment } from './fragment'
import { renderEffect } from './renderEffect' import { renderEffect } from './renderEffect'
export function createKeyedFragment(key: () => any, render: BlockFn): Block { export function createKeyedFragment(key: () => any, render: BlockFn): Block {

View File

@ -1,5 +1,5 @@
import { IF_ANCHOR_LABEL } from '@vue/shared' import { IF_ANCHOR_LABEL } from '@vue/shared'
import { type Block, type BlockFn, DynamicFragment, insert } from './block' import { type Block, type BlockFn, insert } from './block'
import { isHydrating, locateHydrationNode } from './dom/hydration' import { isHydrating, locateHydrationNode } from './dom/hydration'
import { import {
insertionAnchor, insertionAnchor,
@ -7,6 +7,7 @@ import {
resetInsertionState, resetInsertionState,
} from './insertionState' } from './insertionState'
import { renderEffect } from './renderEffect' import { renderEffect } from './renderEffect'
import { DynamicFragment } from './fragment'
export function createIf( export function createIf(
condition: () => any, condition: () => any,

View File

@ -20,7 +20,7 @@ import {
isString, isString,
remove, remove,
} from '@vue/shared' } from '@vue/shared'
import { DynamicFragment } from './block' import { DynamicFragment } from './fragment'
export type NodeRef = string | Ref | ((ref: Element) => void) export type NodeRef = string | Ref | ((ref: Element) => void)
export type RefEl = Element | VaporComponentInstance export type RefEl = Element | VaporComponentInstance

View File

@ -5,14 +5,6 @@ import {
mountComponent, mountComponent,
unmountComponent, unmountComponent,
} from './component' } from './component'
import { createComment, createTextNode } from './dom/node'
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
import {
currentHydrationNode,
isComment,
locateHydrationNode,
locateVaporFragmentAnchor,
} from './dom/hydration'
import { import {
type TransitionHooks, type TransitionHooks,
type TransitionProps, type TransitionProps,
@ -20,17 +12,18 @@ import {
performTransitionEnter, performTransitionEnter,
performTransitionLeave, performTransitionLeave,
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import { isHydrating } from './dom/hydration'
import { getInheritedScopeIds } from '@vue/runtime-dom'
import { import {
applyTransitionHooks, type DynamicFragment,
applyTransitionLeaveHooks, type VaporFragment,
} from './components/Transition' isFragment,
} from './fragment'
export interface TransitionOptions { export interface TransitionOptions {
$key?: any $key?: any
$transition?: VaporTransitionHooks $transition?: VaporTransitionHooks
} }
import { isHydrating } from './dom/hydration'
import { getInheritedScopeIds } from '@vue/runtime-dom'
export interface VaporTransitionHooks extends TransitionHooks { export interface VaporTransitionHooks extends TransitionHooks {
state: TransitionState state: TransitionState
@ -50,119 +43,6 @@ export type Block = TransitionBlock | VaporComponentInstance | Block[]
export type BlockFn = (...args: any[]) => Block export type BlockFn = (...args: any[]) => Block
export class VaporFragment implements TransitionOptions {
$key?: any
$transition?: VaporTransitionHooks | undefined
nodes: Block
anchor?: Node
insert?: (
parent: ParentNode,
anchor: Node | null,
transitionHooks?: TransitionHooks,
) => void
remove?: (parent?: ParentNode, transitionHooks?: TransitionHooks) => void
fallback?: BlockFn
constructor(nodes: Block) {
this.nodes = nodes
}
}
export class DynamicFragment extends VaporFragment {
anchor!: Node
scope: EffectScope | undefined
current?: BlockFn
fallback?: BlockFn
/**
* slot only
* indicates forwarded slot
*/
forwarded?: boolean
constructor(anchorLabel?: string) {
super([])
if (isHydrating) {
locateHydrationNode(true)
this.hydrate(anchorLabel!)
} else {
this.anchor =
__DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
}
}
update(render?: BlockFn, key: any = render): void {
if (key === this.current) {
return
}
this.current = key
pauseTracking()
const parent = this.anchor.parentNode
const transition = this.$transition
const renderBranch = () => {
if (render) {
this.scope = new EffectScope()
this.nodes = this.scope.run(render) || []
if (transition) {
this.$transition = applyTransitionHooks(this.nodes, transition)
}
if (parent) insert(this.nodes, parent, this.anchor)
} else {
this.scope = undefined
this.nodes = []
}
}
// teardown previous branch
if (this.scope) {
this.scope.stop()
const mode = transition && transition.mode
if (mode) {
applyTransitionLeaveHooks(this.nodes, transition, renderBranch)
parent && remove(this.nodes, parent)
if (mode === 'out-in') {
resetTracking()
return
}
} else {
parent && remove(this.nodes, parent)
}
}
renderBranch()
if (this.fallback && !isValidBlock(this.nodes)) {
parent && remove(this.nodes, parent)
this.nodes =
(this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
[]
parent && insert(this.nodes, parent, this.anchor)
}
resetTracking()
}
hydrate(label: string): void {
// for `v-if="false"` the node will be an empty comment, use it as the anchor.
// otherwise, find next sibling vapor fragment anchor
if (isComment(currentHydrationNode!, '')) {
this.anchor = currentHydrationNode
} else {
const anchor = locateVaporFragmentAnchor(currentHydrationNode!, label)!
if (anchor) {
this.anchor = anchor
} else if (__DEV__) {
// this should not happen
throw new Error(`${label} fragment anchor node was not found.`)
}
}
}
}
export function isFragment(val: NonNullable<unknown>): val is VaporFragment {
return val instanceof VaporFragment
}
export function isBlock(val: NonNullable<unknown>): val is Block { export function isBlock(val: NonNullable<unknown>): val is Block {
return ( return (
val instanceof Node || val instanceof Node ||
@ -229,7 +109,12 @@ export function insert(
if (block.insert) { if (block.insert) {
block.insert(parent, anchor, (block as TransitionBlock).$transition) block.insert(parent, anchor, (block as TransitionBlock).$transition)
} else { } else {
insert(block.nodes, parent, anchor, parentSuspense) insert(
block.nodes,
block.target || parent,
block.targetAnchor || anchor,
parentSuspense,
)
} }
} }
} }
@ -289,7 +174,11 @@ export function normalizeBlock(block: Block): Node[] {
} else if (isVaporComponent(block)) { } else if (isVaporComponent(block)) {
nodes.push(...normalizeBlock(block.block!)) nodes.push(...normalizeBlock(block.block!))
} else { } else {
nodes.push(...normalizeBlock(block.nodes)) if (block.getNodes) {
nodes.push(...normalizeBlock(block.getNodes()))
} else {
nodes.push(...normalizeBlock(block.nodes))
}
block.anchor && nodes.push(block.anchor) block.anchor && nodes.push(block.anchor)
} }
return nodes return nodes

View File

@ -27,7 +27,6 @@ import {
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import { import {
type Block, type Block,
DynamicFragment,
insert, insert,
isBlock, isBlock,
remove, remove,
@ -68,11 +67,13 @@ import {
import { hmrReload, hmrRerender } from './hmr' import { hmrReload, hmrRerender } from './hmr'
import { createElement } from './dom/node' import { createElement } from './dom/node'
import { isHydrating, locateHydrationNode } from './dom/hydration' import { isHydrating, locateHydrationNode } from './dom/hydration'
import { isVaporTeleport } from './components/Teleport'
import { import {
insertionAnchor, insertionAnchor,
insertionParent, insertionParent,
resetInsertionState, resetInsertionState,
} from './insertionState' } from './insertionState'
import { DynamicFragment } from './fragment'
export { currentInstance } from '@vue/runtime-dom' export { currentInstance } from '@vue/runtime-dom'
@ -177,6 +178,18 @@ export function createComponent(
return frag return frag
} }
// teleport
if (isVaporTeleport(component)) {
const frag = component.process(rawProps!, rawSlots!)
if (!isHydrating && _insertionParent) {
insert(frag, _insertionParent, _insertionAnchor)
} else {
frag.hydrate()
}
return frag as any
}
if ( if (
isSingleRoot && isSingleRoot &&
component.inheritAttrs !== false && component.inheritAttrs !== false &&
@ -398,6 +411,7 @@ export class VaporComponentInstance implements GenericComponentInstance {
setupState?: Record<string, any> setupState?: Record<string, any>
devtoolsRawSetupState?: any devtoolsRawSetupState?: any
hmrRerender?: () => void hmrRerender?: () => void
hmrRerenderEffects?: (() => void)[]
hmrReload?: (newComp: VaporComponent) => void hmrReload?: (newComp: VaporComponent) => void
propsOptions?: NormalizedPropsOptions propsOptions?: NormalizedPropsOptions
emitsOptions?: ObjectEmitsOptions | null emitsOptions?: ObjectEmitsOptions | null

View File

@ -6,15 +6,7 @@ import {
isArray, isArray,
isFunction, isFunction,
} from '@vue/shared' } from '@vue/shared'
import { import { type Block, type BlockFn, insert, setScopeId } from './block'
type Block,
type BlockFn,
DynamicFragment,
type VaporFragment,
insert,
isFragment,
setScopeId,
} from './block'
import { rawPropsProxyHandlers } from './componentProps' import { rawPropsProxyHandlers } from './componentProps'
import { currentInstance, isRef } from '@vue/runtime-dom' import { currentInstance, isRef } from '@vue/runtime-dom'
import type { LooseRawProps, VaporComponentInstance } from './component' import type { LooseRawProps, VaporComponentInstance } from './component'
@ -25,6 +17,7 @@ import {
resetInsertionState, resetInsertionState,
} from './insertionState' } from './insertionState'
import { isHydrating, locateHydrationNode } from './dom/hydration' import { isHydrating, locateHydrationNode } from './dom/hydration'
import { DynamicFragment, type VaporFragment, isFragment } from './fragment'
export type RawSlots = Record<string, VaporSlot> & { export type RawSlots = Record<string, VaporSlot> & {
$?: DynamicSlotSource[] $?: DynamicSlotSource[]

View File

@ -0,0 +1,243 @@
import {
type TeleportProps,
currentInstance,
isTeleportDeferred,
isTeleportDisabled,
queuePostFlushCb,
resolveTeleportTarget,
warn,
} from '@vue/runtime-dom'
import { type Block, type BlockFn, insert, remove } from '../block'
import { createComment, createTextNode, querySelector } from '../dom/node'
import {
type LooseRawProps,
type LooseRawSlots,
type VaporComponentInstance,
isVaporComponent,
} from '../component'
import { rawPropsProxyHandlers } from '../componentProps'
import { renderEffect } from '../renderEffect'
import { extend, isArray } from '@vue/shared'
import { VaporFragment } from '../fragment'
const instanceToTeleportMap: WeakMap<VaporComponentInstance, TeleportFragment> =
__DEV__ ? new WeakMap() : (undefined as any)
export const VaporTeleportImpl = {
name: 'VaporTeleport',
__isTeleport: true,
__vapor: true,
process(props: LooseRawProps, slots: LooseRawSlots): TeleportFragment {
const frag = new TeleportFragment()
const updateChildrenEffect = renderEffect(() =>
frag.updateChildren(slots.default && (slots.default as BlockFn)()),
)
const updateEffect = renderEffect(() => {
frag.update(
// access the props to trigger tracking
extend(
{},
new Proxy(props, rawPropsProxyHandlers) as any as TeleportProps,
),
)
})
if (__DEV__) {
// used in `normalizeBlock` to get nodes of TeleportFragment during
// HMR updates. returns empty array if content is mounted in target
// container to prevent incorrect parent node lookup.
frag.getNodes = () => {
return frag.parent !== frag.currentParent ? [] : frag.nodes
}
// for HMR rerender
const instance = currentInstance as VaporComponentInstance
;(
instance!.hmrRerenderEffects || (instance!.hmrRerenderEffects = [])
).push(() => {
// remove the teleport content
frag.remove()
// stop effects
updateChildrenEffect.stop()
updateEffect.stop()
})
// for HMR reload
const nodes = frag.nodes
if (isVaporComponent(nodes)) {
instanceToTeleportMap.set(nodes, frag)
} else if (isArray(nodes)) {
nodes.forEach(
node =>
isVaporComponent(node) && instanceToTeleportMap.set(node, frag),
)
}
}
return frag
},
}
export class TeleportFragment extends VaporFragment {
anchor: Node
private targetStart?: Node
private mainAnchor?: Node
private placeholder?: Node
private mountContainer?: ParentNode | null
private mountAnchor?: Node | null
constructor() {
super([])
this.anchor = __DEV__ ? createComment('teleport') : createTextNode()
}
get currentParent(): ParentNode {
return (this.mountContainer || this.parent)!
}
get currentAnchor(): Node | null {
return this.mountAnchor || this.anchor
}
get parent(): ParentNode | null {
return this.anchor.parentNode
}
updateChildren(children: Block): void {
// not mounted yet
if (!this.parent) {
this.nodes = children
return
}
// teardown previous nodes
remove(this.nodes, this.currentParent)
// mount new nodes
insert((this.nodes = children), this.currentParent, this.currentAnchor)
}
update(props: TeleportProps): void {
const mount = (parent: ParentNode, anchor: Node | null) => {
insert(
this.nodes,
(this.mountContainer = parent),
(this.mountAnchor = anchor),
)
}
const mountToTarget = () => {
const target = (this.target = resolveTeleportTarget(props, querySelector))
if (target) {
if (
// initial mount into target
!this.targetAnchor ||
// target changed
this.targetAnchor.parentNode !== target
) {
insert((this.targetStart = createTextNode('')), target)
insert((this.targetAnchor = createTextNode('')), target)
}
mount(target, this.targetAnchor!)
} else if (__DEV__) {
warn(
`Invalid Teleport target on ${this.targetAnchor ? 'update' : 'mount'}:`,
target,
`(${typeof target})`,
)
}
}
// mount into main container
if (isTeleportDisabled(props)) {
if (this.parent) {
if (!this.mainAnchor) {
this.mainAnchor = __DEV__
? createComment('teleport end')
: createTextNode()
}
if (!this.placeholder) {
this.placeholder = __DEV__
? createComment('teleport start')
: createTextNode()
}
if (!this.mainAnchor.isConnected) {
insert(this.placeholder, this.parent, this.anchor)
insert(this.mainAnchor, this.parent, this.anchor)
}
mount(this.parent, this.mainAnchor)
}
}
// mount into target container
else {
if (isTeleportDeferred(props)) {
queuePostFlushCb(mountToTarget)
} else {
mountToTarget()
}
}
}
remove = (parent: ParentNode | undefined = this.parent!): void => {
// remove nodes
if (this.nodes) {
remove(this.nodes, this.currentParent)
this.nodes = []
}
// remove anchors
if (this.targetStart) {
remove(this.targetStart!, this.target!)
this.targetStart = undefined
remove(this.targetAnchor!, this.target!)
this.targetAnchor = undefined
}
if (this.placeholder) {
remove(this.placeholder!, parent)
this.placeholder = undefined
remove(this.mainAnchor!, parent)
this.mainAnchor = undefined
}
this.mountContainer = undefined
this.mountAnchor = undefined
}
hydrate(): void {
// TODO
}
}
export function isVaporTeleport(
value: unknown,
): value is typeof VaporTeleportImpl {
return value === VaporTeleportImpl
}
/**
* dev only
* during root component HMR reload, since the old component will be unmounted
* and a new one will be mounted, we need to update the teleport's nodes
* to ensure they are up to date.
*/
export function handleTeleportRootComponentHmrReload(
instance: VaporComponentInstance,
newInstance: VaporComponentInstance,
): void {
const teleport = instanceToTeleportMap.get(instance)
if (teleport) {
instanceToTeleportMap.set(newInstance, teleport)
if (teleport.nodes === instance) {
teleport.nodes = newInstance
} else if (isArray(teleport.nodes)) {
const i = teleport.nodes.indexOf(instance)
if (i !== -1) teleport.nodes[i] = newInstance
}
}
}

View File

@ -14,12 +14,7 @@ import {
useTransitionState, useTransitionState,
warn, warn,
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import { import type { Block, TransitionBlock, VaporTransitionHooks } from '../block'
type Block,
type TransitionBlock,
type VaporTransitionHooks,
isFragment,
} from '../block'
import { import {
type FunctionalVaporComponent, type FunctionalVaporComponent,
type VaporComponentInstance, type VaporComponentInstance,
@ -28,6 +23,7 @@ import {
} from '../component' } from '../component'
import { extend, isArray } from '@vue/shared' import { extend, isArray } from '@vue/shared'
import { renderEffect } from '../renderEffect' import { renderEffect } from '../renderEffect'
import { isFragment } from '../fragment'
const decorate = (t: typeof VaporTransition) => { const decorate = (t: typeof VaporTransition) => {
t.displayName = 'VaporTransition' t.displayName = 'VaporTransition'

View File

@ -17,11 +17,9 @@ import {
import { extend, isArray } from '@vue/shared' import { extend, isArray } from '@vue/shared'
import { import {
type Block, type Block,
DynamicFragment,
type TransitionBlock, type TransitionBlock,
type VaporTransitionHooks, type VaporTransitionHooks,
insert, insert,
isFragment,
} from '../block' } from '../block'
import { import {
resolveTransitionHooks, resolveTransitionHooks,
@ -37,6 +35,7 @@ import {
import { isForBlock } from '../apiCreateFor' import { isForBlock } from '../apiCreateFor'
import { renderEffect } from '../renderEffect' import { renderEffect } from '../renderEffect'
import { createElement } from '../dom/node' import { createElement } from '../dom/node'
import { DynamicFragment, isFragment } from '../fragment'
const positionMap = new WeakMap<TransitionBlock, DOMRect>() const positionMap = new WeakMap<TransitionBlock, DOMRect>()
const newPositionMap = new WeakMap<TransitionBlock, DOMRect>() const newPositionMap = new WeakMap<TransitionBlock, DOMRect>()

View File

@ -6,8 +6,9 @@ import {
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import { renderEffect } from '../renderEffect' import { renderEffect } from '../renderEffect'
import { isVaporComponent } from '../component' import { isVaporComponent } from '../component'
import { type Block, DynamicFragment, type TransitionBlock } from '../block' import type { Block, TransitionBlock } from '../block'
import { isArray } from '@vue/shared' import { isArray } from '@vue/shared'
import { DynamicFragment } from '../fragment'
export function applyVShow(target: Block, source: () => any): void { export function applyVShow(target: Block, source: () => any): void {
if (isVaporComponent(target)) { if (isVaporComponent(target)) {

View File

@ -0,0 +1,140 @@
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
import { createComment, createTextNode } from './dom/node'
import {
type Block,
type BlockFn,
type TransitionOptions,
type VaporTransitionHooks,
insert,
isValidBlock,
remove,
} from './block'
import type { TransitionHooks } from '@vue/runtime-dom'
import {
currentHydrationNode,
isComment,
isHydrating,
locateHydrationNode,
locateVaporFragmentAnchor,
} from './dom/hydration'
import {
applyTransitionHooks,
applyTransitionLeaveHooks,
} from './components/Transition'
export class VaporFragment implements TransitionOptions {
$key?: any
$transition?: VaporTransitionHooks | undefined
nodes: Block
anchor?: Node
insert?: (
parent: ParentNode,
anchor: Node | null,
transitionHooks?: TransitionHooks,
) => void
remove?: (parent?: ParentNode, transitionHooks?: TransitionHooks) => void
fallback?: BlockFn
target?: ParentNode | null
targetAnchor?: Node | null
getNodes?: () => Block
constructor(nodes: Block) {
this.nodes = nodes
}
}
export class DynamicFragment extends VaporFragment {
anchor!: Node
scope: EffectScope | undefined
current?: BlockFn
fallback?: BlockFn
/**
* slot only
* indicates forwarded slot
*/
forwarded?: boolean
constructor(anchorLabel?: string) {
super([])
if (isHydrating) {
locateHydrationNode(true)
this.hydrate(anchorLabel!)
} else {
this.anchor =
__DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
}
}
update(render?: BlockFn, key: any = render): void {
if (key === this.current) {
return
}
this.current = key
pauseTracking()
const parent = this.anchor.parentNode
const transition = this.$transition
const renderBranch = () => {
if (render) {
this.scope = new EffectScope()
this.nodes = this.scope.run(render) || []
if (transition) {
this.$transition = applyTransitionHooks(this.nodes, transition)
}
if (parent) insert(this.nodes, parent, this.anchor)
} else {
this.scope = undefined
this.nodes = []
}
}
// teardown previous branch
if (this.scope) {
this.scope.stop()
const mode = transition && transition.mode
if (mode) {
applyTransitionLeaveHooks(this.nodes, transition, renderBranch)
parent && remove(this.nodes, parent)
if (mode === 'out-in') {
resetTracking()
return
}
} else {
parent && remove(this.nodes, parent)
}
}
renderBranch()
if (this.fallback && !isValidBlock(this.nodes)) {
parent && remove(this.nodes, parent)
this.nodes =
(this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
[]
parent && insert(this.nodes, parent, this.anchor)
}
resetTracking()
}
hydrate(label: string): void {
// for `v-if="false"` the node will be an empty comment, use it as the anchor.
// otherwise, find next sibling vapor fragment anchor
if (isComment(currentHydrationNode!, '')) {
this.anchor = currentHydrationNode
} else {
const anchor = locateVaporFragmentAnchor(currentHydrationNode!, label)!
if (anchor) {
this.anchor = anchor
} else if (__DEV__) {
// this should not happen
throw new Error(`${label} fragment anchor node was not found.`)
}
}
}
}
export function isFragment(val: NonNullable<unknown>): val is VaporFragment {
return val instanceof VaporFragment
}

View File

@ -13,12 +13,17 @@ import {
mountComponent, mountComponent,
unmountComponent, unmountComponent,
} from './component' } from './component'
import { handleTeleportRootComponentHmrReload } from './components/Teleport'
export function hmrRerender(instance: VaporComponentInstance): void { export function hmrRerender(instance: VaporComponentInstance): void {
const normalized = normalizeBlock(instance.block) const normalized = normalizeBlock(instance.block)
const parent = normalized[0].parentNode! const parent = normalized[0].parentNode!
const anchor = normalized[normalized.length - 1].nextSibling const anchor = normalized[normalized.length - 1].nextSibling
remove(instance.block, parent) remove(instance.block, parent)
if (instance.hmrRerenderEffects) {
instance.hmrRerenderEffects.forEach(e => e())
instance.hmrRerenderEffects.length = 0
}
const prev = currentInstance const prev = currentInstance
simpleSetCurrentInstance(instance) simpleSetCurrentInstance(instance)
pushWarningContext(instance) pushWarningContext(instance)
@ -46,4 +51,5 @@ export function hmrReload(
) )
simpleSetCurrentInstance(prev, instance.parent) simpleSetCurrentInstance(prev, instance.parent)
mountComponent(newInstance, parent, anchor) mountComponent(newInstance, parent, anchor)
handleTeleportRootComponentHmrReload(instance, newInstance)
} }

View File

@ -3,9 +3,10 @@ export { createVaporApp, createVaporSSRApp } from './apiCreateApp'
export { defineVaporComponent } from './apiDefineComponent' export { defineVaporComponent } from './apiDefineComponent'
export { vaporInteropPlugin } from './vdomInterop' export { vaporInteropPlugin } from './vdomInterop'
export type { VaporDirective } from './directives/custom' export type { VaporDirective } from './directives/custom'
export { VaporTeleportImpl as VaporTeleport } from './components/Teleport'
// compiler-use only // compiler-use only
export { insert, prepend, remove, isFragment, VaporFragment } from './block' export { insert, prepend, remove } from './block'
export { setInsertionState } from './insertionState' export { setInsertionState } from './insertionState'
export { createComponent, createComponentWithFallback } from './component' export { createComponent, createComponentWithFallback } from './component'
export { renderEffect } from './renderEffect' export { renderEffect } from './renderEffect'
@ -46,3 +47,4 @@ export {
export { withVaporDirectives } from './directives/custom' export { withVaporDirectives } from './directives/custom'
export { VaporTransition } from './components/Transition' export { VaporTransition } from './components/Transition'
export { VaporTransitionGroup } from './components/TransitionGroup' export { VaporTransitionGroup } from './components/TransitionGroup'
export { isFragment, VaporFragment } from './fragment'

View File

@ -11,7 +11,10 @@ import {
import { type VaporComponentInstance, isVaporComponent } from './component' import { type VaporComponentInstance, isVaporComponent } from './component'
import { invokeArrayFns } from '@vue/shared' import { invokeArrayFns } from '@vue/shared'
export function renderEffect(fn: () => void, noLifecycle = false): void { export function renderEffect(
fn: () => void,
noLifecycle = false,
): ReactiveEffect<void> {
const instance = currentInstance as VaporComponentInstance | null const instance = currentInstance as VaporComponentInstance | null
const scope = getCurrentScope() const scope = getCurrentScope()
if (__DEV__ && !__TEST__ && !scope && !isVaporComponent(instance)) { if (__DEV__ && !__TEST__ && !scope && !isVaporComponent(instance)) {
@ -66,5 +69,6 @@ export function renderEffect(fn: () => void, noLifecycle = false): void {
effect.scheduler = () => queueJob(job) effect.scheduler = () => queueJob(job)
effect.run() effect.run()
return effect
// TODO recurse handling // TODO recurse handling
} }

View File

@ -37,15 +37,7 @@ import {
mountComponent, mountComponent,
unmountComponent, unmountComponent,
} from './component' } from './component'
import { import { type Block, type VaporTransitionHooks, insert, remove } from './block'
type Block,
DynamicFragment,
VaporFragment,
type VaporTransitionHooks,
insert,
isFragment,
remove,
} from './block'
import { import {
EMPTY_OBJ, EMPTY_OBJ,
extend, extend,
@ -65,6 +57,7 @@ import {
locateHydrationNode, locateHydrationNode,
hydrateNode as vaporHydrateNode, hydrateNode as vaporHydrateNode,
} from './dom/hydration' } from './dom/hydration'
import { DynamicFragment, VaporFragment, isFragment } from './fragment'
// mounting vapor components and slots in vdom // mounting vapor components and slots in vdom
const vaporInteropImpl: Omit< const vaporInteropImpl: Omit<

View File

@ -55,7 +55,7 @@ importers:
version: 5.0.4(rollup@4.44.0) version: 5.0.4(rollup@4.44.0)
'@swc/core': '@swc/core':
specifier: ^1.11.24 specifier: ^1.11.24
version: 1.12.3 version: 1.12.4
'@types/hash-sum': '@types/hash-sum':
specifier: ^1.0.2 specifier: ^1.0.2
version: 1.0.2 version: 1.0.2
@ -1288,68 +1288,68 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@swc/core-darwin-arm64@1.12.3': '@swc/core-darwin-arm64@1.12.4':
resolution: {integrity: sha512-QCV9vQ/s27AMxm8j8MTDL/nDoiEMrANiENRrWnb0Fxvz/O39CajPVShp/W7HlOkzt1GYtUXPdQJpSKylugfrWw==} resolution: {integrity: sha512-HihKfeitjZU2ab94Zf893sxzFryLKX0TweGsNXXOLNtkSMLw50auuYfpRM0BOL9/uXXtuCWgRIF6P030SAX5xQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@swc/core-darwin-x64@1.12.3': '@swc/core-darwin-x64@1.12.4':
resolution: {integrity: sha512-LylCMfzGhdvl5tyKaTT9ePetHUX7wSsST7hxWiHzS+cUMj7FnhcfdEr6kcNVT7y1RJn3fCvuv7T98ZB+T2q3HA==} resolution: {integrity: sha512-meYCXHyYb6RDdu2N5PNAf0EelyxPBFhRcVo4kBFLuvuNb0m6EUg///VWy8MUMXq9/s9uzGS9kJVXXdRdr/d6FA==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.12.3': '@swc/core-linux-arm-gnueabihf@1.12.4':
resolution: {integrity: sha512-DQODb7S+q+pwQY41Azcavwb2rb4rGxP70niScRDxB9X68hHOM9D0w9fxzC+Nr3AHcPSmVJUYUIiq5h38O5hVgQ==} resolution: {integrity: sha512-szfDbf7mE8V64of0q/LSqbk+em+T+TD3uqnH40Z7Qu/aL8vi5CHgyLjWG2SLkLLpyjgkAUF6AKrupgnBYcC2NA==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@swc/core-linux-arm64-gnu@1.12.3': '@swc/core-linux-arm64-gnu@1.12.4':
resolution: {integrity: sha512-nTxtJSq78AjeaQBueYImoFBs5j7qXbgOxtirpyt8jE29NQBd0VFzDzRBhkr6I9jq0hNiChgMkqBN4eUkEQjytg==} resolution: {integrity: sha512-n0IY76w+Scx8m3HIVRvLkoResuwsQgjDfAk9bxn99dq4leQO+mE0fkPl0Yw/1BIsPh+kxGfopIJH9zsZ1Z2YrA==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@swc/core-linux-arm64-musl@1.12.3': '@swc/core-linux-arm64-musl@1.12.4':
resolution: {integrity: sha512-lBGvC5UgPSxqLr/y1NZxQhyRQ7nXy3/Ec1Z47YNXtqtpKiG1EcOGPyS0UZgwiYQkXqq8NBFMHnyHmpKnXTvRDA==} resolution: {integrity: sha512-wE5jmFi5cEQyLy8WmCWmNwfKETrnzy2D8YNi/xpYWpLPWqPhcelpa6tswkfYlbsMmmOh7hQNoTba1QdGu0jvHQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@swc/core-linux-x64-gnu@1.12.3': '@swc/core-linux-x64-gnu@1.12.4':
resolution: {integrity: sha512-61wZ8hwxNYzBY9MCWB50v90ICzdIhOuPk1O1qXswz9AXw5O6iQStEBHQ1rozPkfQ/rmhepk0pOf/6LCwssJOwg==} resolution: {integrity: sha512-6S50Xd/7ePjEwrXyHMxpKTZ+KBrgUwMA8hQPbArUOwH4S5vHBr51heL0iXbUkppn1bkSr0J0IbOove5hzn+iqQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@swc/core-linux-x64-musl@1.12.3': '@swc/core-linux-x64-musl@1.12.4':
resolution: {integrity: sha512-NNeBiTpCgWt80vumTKVoaj6Fa/ZjUcaNQNM7np3PIgB8EbuXfyztboV7vUxpkmD/lUgsk8GlEFYViHvo6VMefQ==} resolution: {integrity: sha512-hbYRyaHhC13vYKuGG5BrAG5fjjWEQFfQetuFp/4QKEoXDzdnabJoixxWTQACDL3m0JW32nJ+gUzsYIPtFYkwXg==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@swc/core-win32-arm64-msvc@1.12.3': '@swc/core-win32-arm64-msvc@1.12.4':
resolution: {integrity: sha512-fxraM7exaPb1/W0CoHW45EFNOQUQh0nonBEcNFm2iv095mziBwttyxZyQBoDkQocpkd5NtsZw3xW5FTBPnn+Vw==} resolution: {integrity: sha512-e6EbfjPL8GA/bb1lc9Omtxjlz+1ThTsAuBsy4Q3Kpbuh6B3jclg8KzxU/6t91v23wG593mieTyR5f3Pr7X3AWw==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@swc/core-win32-ia32-msvc@1.12.3': '@swc/core-win32-ia32-msvc@1.12.4':
resolution: {integrity: sha512-FFIhMPXIDjRcewomwbYGPvem7Fj76AsuzbRahnAyp+OzJwrrtxVmra/kyUCfj4kix7vdGByY0WvVfiVCf5b7Mg==} resolution: {integrity: sha512-RG2FzmllBTUf4EksANlIvLckcBrLZEA0t13LIa6L213UZKQfEuDNHezqESgoVhJMg2S/tWauitATOCFgZNSmjg==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@swc/core-win32-x64-msvc@1.12.3': '@swc/core-win32-x64-msvc@1.12.4':
resolution: {integrity: sha512-Sf4iSg+IYT5AzFSDDmii08DfeKcvtkVxIuo+uS8BJMbiLjFNjgMkkVlBthknGyJcSK15ncg9248XjnM4jU8DZA==} resolution: {integrity: sha512-oRHKnZlR83zaMeVUCmHENa4j5uNRAWbmEpjYbzRcfC45LPFNWKGWGAGERLx0u87XMUtTGqnVYxnBTHN/rzDHOw==}
engines: {node: '>=10'} engines: {node: '>=10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@swc/core@1.12.3': '@swc/core@1.12.4':
resolution: {integrity: sha512-c4NeXW8P3gPqcFwtm+4aH+F2Cj5KJLMiLaKhSj3mpv19glq+jmekomdktAw/VHyjsXlsmouOeNWrk8rVlkCRsg==} resolution: {integrity: sha512-hn30ebV4njAn0NAUM+3a0qCF+MJgqTNSrfA/hUAbC6TVjOQy2OYGQwkUvCu/V7S2+rZxrUsTpKOnZ7qqECZV9Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
peerDependencies: peerDependencies:
'@swc/helpers': '>=0.5.17' '@swc/helpers': '>=0.5.17'
@ -4482,51 +4482,51 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.44.0': '@rollup/rollup-win32-x64-msvc@4.44.0':
optional: true optional: true
'@swc/core-darwin-arm64@1.12.3': '@swc/core-darwin-arm64@1.12.4':
optional: true optional: true
'@swc/core-darwin-x64@1.12.3': '@swc/core-darwin-x64@1.12.4':
optional: true optional: true
'@swc/core-linux-arm-gnueabihf@1.12.3': '@swc/core-linux-arm-gnueabihf@1.12.4':
optional: true optional: true
'@swc/core-linux-arm64-gnu@1.12.3': '@swc/core-linux-arm64-gnu@1.12.4':
optional: true optional: true
'@swc/core-linux-arm64-musl@1.12.3': '@swc/core-linux-arm64-musl@1.12.4':
optional: true optional: true
'@swc/core-linux-x64-gnu@1.12.3': '@swc/core-linux-x64-gnu@1.12.4':
optional: true optional: true
'@swc/core-linux-x64-musl@1.12.3': '@swc/core-linux-x64-musl@1.12.4':
optional: true optional: true
'@swc/core-win32-arm64-msvc@1.12.3': '@swc/core-win32-arm64-msvc@1.12.4':
optional: true optional: true
'@swc/core-win32-ia32-msvc@1.12.3': '@swc/core-win32-ia32-msvc@1.12.4':
optional: true optional: true
'@swc/core-win32-x64-msvc@1.12.3': '@swc/core-win32-x64-msvc@1.12.4':
optional: true optional: true
'@swc/core@1.12.3': '@swc/core@1.12.4':
dependencies: dependencies:
'@swc/counter': 0.1.3 '@swc/counter': 0.1.3
'@swc/types': 0.1.23 '@swc/types': 0.1.23
optionalDependencies: optionalDependencies:
'@swc/core-darwin-arm64': 1.12.3 '@swc/core-darwin-arm64': 1.12.4
'@swc/core-darwin-x64': 1.12.3 '@swc/core-darwin-x64': 1.12.4
'@swc/core-linux-arm-gnueabihf': 1.12.3 '@swc/core-linux-arm-gnueabihf': 1.12.4
'@swc/core-linux-arm64-gnu': 1.12.3 '@swc/core-linux-arm64-gnu': 1.12.4
'@swc/core-linux-arm64-musl': 1.12.3 '@swc/core-linux-arm64-musl': 1.12.4
'@swc/core-linux-x64-gnu': 1.12.3 '@swc/core-linux-x64-gnu': 1.12.4
'@swc/core-linux-x64-musl': 1.12.3 '@swc/core-linux-x64-musl': 1.12.4
'@swc/core-win32-arm64-msvc': 1.12.3 '@swc/core-win32-arm64-msvc': 1.12.4
'@swc/core-win32-ia32-msvc': 1.12.3 '@swc/core-win32-ia32-msvc': 1.12.4
'@swc/core-win32-x64-msvc': 1.12.3 '@swc/core-win32-x64-msvc': 1.12.4
'@swc/counter@0.1.3': {} '@swc/counter@0.1.3': {}