Merge branch 'vapor' into edison/feat/vaporTransition

This commit is contained in:
edison 2025-03-05 16:08:41 +08:00 committed by GitHub
commit 14f102a650
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 167 additions and 72 deletions

View File

@ -13,7 +13,7 @@
"vite": "catalog:"
},
"dependencies": {
"@vue/repl": "^4.5.0",
"@vue/repl": "^4.5.1",
"file-saver": "^2.0.5",
"jszip": "^3.10.1",
"vue": "workspace:*"

View File

@ -381,6 +381,17 @@ h1 { color: red }
})
})
describe('vapor mode', () => {
test('on empty script', () => {
const { descriptor } = parse(`<script vapor></script>`)
expect(descriptor.vapor).toBe(true)
})
test('on template', () => {
const { descriptor } = parse(`<template vapor><div/></template>`)
expect(descriptor.vapor).toBe(true)
})
})
describe('warnings', () => {
function assertWarning(errors: Error[], msg: string) {
expect(errors.some(e => e.message.match(msg))).toBe(true)

View File

@ -162,8 +162,9 @@ export function parse(
ignoreEmpty &&
node.tag !== 'template' &&
isEmpty(node) &&
!hasSrc(node)
!hasAttr(node, 'src')
) {
descriptor.vapor ||= hasAttr(node, 'vapor')
return
}
switch (node.tag) {
@ -409,13 +410,8 @@ function padContent(
}
}
function hasSrc(node: ElementNode) {
return node.props.some(p => {
if (p.type !== NodeTypes.ATTRIBUTE) {
return false
}
return p.name === 'src'
})
function hasAttr(node: ElementNode, name: string) {
return node.props.some(p => p.type === NodeTypes.ATTRIBUTE && p.name === name)
}
/**

View File

@ -1,5 +1,20 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`cache multiple access > cache variable used in both property shorthand and normal binding 1`] = `
"import { setStyle as _setStyle, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _color = _ctx.color
_setStyle(n0, {color: _color})
_setProp(n0, "id", _color)
})
return n0
}"
`;
exports[`cache multiple access > dynamic key bindings with expressions 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
@ -60,6 +75,17 @@ export function render(_ctx) {
}"
`;
exports[`cache multiple access > not cache variable only used in property shorthand 1`] = `
"import { setStyle as _setStyle, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setStyle(n0, {color: _ctx.color}))
return n0
}"
`;
exports[`cache multiple access > object property chain access 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")

View File

@ -785,6 +785,23 @@ describe('cache multiple access', () => {
expect(code).contains('_setProp(n0, "id", _obj[1][_ctx.baz] + _obj.bar)')
})
test('cache variable used in both property shorthand and normal binding', () => {
const { code } = compileWithVBind(`
<div :style="{color}" :id="color"/>
`)
expect(code).matchSnapshot()
expect(code).contains('const _color = _ctx.color')
expect(code).contains('_setStyle(n0, {color: _color})')
})
test('not cache variable only used in property shorthand', () => {
const { code } = compileWithVBind(`
<div :style="{color}" />
`)
expect(code).matchSnapshot()
expect(code).not.contains('const _color = _ctx.color')
})
test('not cache variable and member expression with the same name', () => {
const { code } = compileWithVBind(`
<div :id="bar + obj.bar"></div>

View File

@ -131,7 +131,11 @@ function genIdentifier(
if (idMap && idMap.length) {
const replacement = idMap[0]
if (isString(replacement)) {
return [[replacement, NewlineType.None, loc]]
if (parent && parent.type === 'ObjectProperty' && parent.shorthand) {
return [[`${name}: ${replacement}`, NewlineType.None, loc]]
} else {
return [[replacement, NewlineType.None, loc]]
}
} else {
// replacement is an expression - process it again
return genExpression(replacement, context, assignment)
@ -292,7 +296,7 @@ function analyzeExpressions(expressions: SimpleExpressionNode[]) {
}
walk(exp.ast, {
enter(currentNode: Node) {
enter(currentNode: Node, parent: Node | null) {
if (currentNode.type === 'MemberExpression') {
const memberExp = extractMemberExpression(
currentNode,
@ -304,6 +308,16 @@ function analyzeExpressions(expressions: SimpleExpressionNode[]) {
return this.skip()
}
// skip shorthand or non-computed property keys
if (
parent &&
parent.type === 'ObjectProperty' &&
parent.key === currentNode &&
(parent.shorthand || !parent.computed)
) {
return this.skip()
}
if (currentNode.type === 'Identifier') {
registerVariable(currentNode.name, exp, true)
}

View File

@ -14,8 +14,8 @@ export function genSetText(
context: CodegenContext,
): CodeFragment[] {
const { helper } = context
const { element, values, generated } = oper
const texts = combineValues(values, context)
const { element, values, generated, jsx } = oper
const texts = combineValues(values, context, jsx)
return [
NEWLINE,
...genCall(helper('setText'), `${generated ? 'x' : 'n'}${element}`, texts),
@ -27,13 +27,13 @@ export function genCreateTextNode(
context: CodegenContext,
): CodeFragment[] {
const { helper } = context
const { id, values } = oper
const { id, values, jsx } = oper
return [
NEWLINE,
`const n${id} = `,
...genCall(
helper('createTextNode'),
values && combineValues(values, context),
values && combineValues(values, context, jsx),
),
]
}
@ -41,15 +41,16 @@ export function genCreateTextNode(
function combineValues(
values: SimpleExpressionNode[],
context: CodegenContext,
jsx?: boolean,
): CodeFragment[] {
return values.flatMap((value, i) => {
let exp = genExpression(value, context)
if (getLiteralExpressionValue(value) == null) {
if (!jsx && getLiteralExpressionValue(value) == null) {
// dynamic, wrap with toDisplayString
exp = genCall(context.helper('toDisplayString'), exp)
}
if (i > 0) {
exp.unshift(' + ')
exp.unshift(jsx ? ', ' : ' + ')
}
return exp
})

View File

@ -123,6 +123,7 @@ export interface SetTextIRNode extends BaseIRNode {
element: number
values: SimpleExpressionNode[]
generated?: boolean // whether this is a generated empty text node by `processTextLikeContainer`
jsx?: boolean
}
export type KeyOverride = [find: string, replacement: string]
@ -163,6 +164,7 @@ export interface CreateTextNodeIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_TEXT_NODE
id: number
values?: SimpleExpressionNode[]
jsx?: boolean
}
export interface InsertNodeIRNode extends BaseIRNode {

View File

@ -69,11 +69,8 @@ function setupFlagsHandler(target: Subscriber): void {
},
set(value) {
if (
!(
(target as any)._flags &
(SubscriberFlags.PendingComputed | SubscriberFlags.Dirty)
) &&
!!(value & (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty))
!((target as any)._flags & SubscriberFlags.Propagated) &&
!!(value & SubscriberFlags.Propagated)
) {
onTrigger(this)
}

View File

@ -1,5 +1,5 @@
/* eslint-disable */
// Ported from https://github.com/stackblitz/alien-signals/blob/v1.0.0/src/system.ts
// Ported from https://github.com/stackblitz/alien-signals/blob/v1.0.4/src/system.ts
import type { ComputedRefImpl as Computed } from './computed.js'
import type { ReactiveEffect as Effect } from './effect.js'
@ -35,7 +35,6 @@ export const enum SubscriberFlags {
let batchDepth = 0
let queuedEffects: Effect | undefined
let queuedEffectsTail: Effect | undefined
let linkPool: Link | undefined
export function startBatch(): void {
++batchDepth
@ -195,24 +194,18 @@ export function processComputedUpdate(
computed: Computed,
flags: SubscriberFlags,
): void {
if (flags & SubscriberFlags.Dirty) {
if (
flags & SubscriberFlags.Dirty ||
(checkDirty(computed.deps!)
? true
: ((computed.flags = flags & ~SubscriberFlags.PendingComputed), false))
) {
if (computed.update()) {
const subs = computed.subs
if (subs !== undefined) {
shallowPropagate(subs)
}
}
} else if (flags & SubscriberFlags.PendingComputed) {
if (checkDirty(computed.deps!)) {
if (computed.update()) {
const subs = computed.subs
if (subs !== undefined) {
shallowPropagate(subs)
}
}
} else {
computed.flags = flags & ~SubscriberFlags.PendingComputed
}
}
}
@ -238,22 +231,12 @@ function linkNewDep(
nextDep: Link | undefined,
depsTail: Link | undefined,
): Link {
let newLink: Link
if (linkPool !== undefined) {
newLink = linkPool
linkPool = newLink.nextDep
newLink.nextDep = nextDep
newLink.dep = dep
newLink.sub = sub
} else {
newLink = {
dep,
sub,
nextDep,
prevSub: undefined,
nextSub: undefined,
}
const newLink: Link = {
dep,
sub,
nextDep,
prevSub: undefined,
nextSub: undefined,
}
if (depsTail === undefined) {
@ -327,7 +310,7 @@ function checkDirty(link: Link): boolean {
if (sub.update()) {
if ((link = subSubs.prevSub!) !== undefined) {
subSubs.prevSub = undefined
shallowPropagate(sub.subs!)
shallowPropagate(subSubs)
sub = link.sub as Computed
} else {
sub = subSubs.sub as Computed
@ -400,25 +383,16 @@ function clearTracking(link: Link): void {
if (nextSub !== undefined) {
nextSub.prevSub = prevSub
link.nextSub = undefined
} else {
dep.subsTail = prevSub
}
if (prevSub !== undefined) {
prevSub.nextSub = nextSub
link.prevSub = undefined
} else {
dep.subs = nextSub
}
// @ts-expect-error
link.dep = undefined
// @ts-expect-error
link.sub = undefined
link.nextDep = linkPool
linkPool = link
if (dep.subs === undefined && 'deps' in dep) {
const depFlags = dep.flags
if (!(depFlags & SubscriberFlags.Dirty)) {

View File

@ -2410,6 +2410,9 @@ function baseCreateRenderer(
const getNextHostNode: NextFn = vnode => {
if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
if ((vnode.type as ConcreteComponent).__vapor) {
return hostNextSibling((vnode.component! as any).block)
}
return getNextHostNode(vnode.component!.subTree)
}
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {

View File

@ -1,4 +1,4 @@
import { createVaporApp, defineVaporComponent } from '../src'
import { createVaporApp } from '../src'
import type { App } from '@vue/runtime-dom'
import type { VaporComponent, VaporComponentInstance } from '../src/component'
import type { RawProps } from '../src/componentProps'
@ -36,7 +36,8 @@ export function makeRender<C = VaporComponent>(
})
function define(comp: C) {
const component = defineVaporComponent(comp as any)
const component = comp as any
component.__vapor = true
let instance: VaporComponentInstance | undefined
let app: App

View File

@ -127,6 +127,27 @@ describe('component: props', () => {
expect(props).toBe(attrs)
})
test('functional defineVaporComponent without declaration', () => {
let props: any
let attrs: any
const { render } = define(
defineVaporComponent((_props: any, { attrs: _attrs }: any) => {
props = _props
attrs = _attrs
return []
}),
)
render({ foo: () => 1 })
expect(props).toEqual({})
expect(attrs).toEqual({ foo: 1 })
render({ bar: () => 2 })
expect(props).toEqual({})
expect(attrs).toEqual({ bar: 2 })
})
test('boolean casting', () => {
let props: any
const { render } = define({

View File

@ -1,7 +1,20 @@
import type { VaporComponent } from './component'
import type { ObjectVaporComponent, VaporComponent } from './component'
import { extend, isFunction } from '@vue/shared'
/*! #__NO_SIDE_EFFECTS__ */
export function defineVaporComponent(comp: VaporComponent): VaporComponent {
export function defineVaporComponent(
comp: VaporComponent,
extraOptions?: Omit<ObjectVaporComponent, 'setup'>,
): VaporComponent {
if (isFunction(comp)) {
// #8236: extend call and options.name access are considered side-effects
// by Rollup, so we have to wrap it in a pure-annotated IIFE.
return /*@__PURE__*/ (() =>
extend({ name: comp.name }, extraOptions, {
setup: comp,
__vapor: true,
}))()
}
// TODO type inference
comp.__vapor = true
return comp

View File

@ -5,7 +5,7 @@ export { vaporInteropPlugin } from './vdomInterop'
export type { VaporDirective } from './directives/custom'
// compiler-use only
export { insert, prepend, remove } from './block'
export { insert, prepend, remove, isFragment, VaporFragment } from './block'
export { createComponent, createComponentWithFallback } from './component'
export { renderEffect } from './renderEffect'
export { createSlot } from './componentSlots'

View File

@ -251,8 +251,8 @@ importers:
packages-private/sfc-playground:
dependencies:
'@vue/repl':
specifier: ^4.5.0
version: 4.5.0
specifier: ^4.5.1
version: 4.5.1
file-saver:
specifier: ^2.0.5
version: 2.0.5
@ -1039,30 +1039,35 @@ packages:
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-glibc@2.4.1':
resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.4.1':
resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.4.1':
resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.4.1':
resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.4.1':
resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==}
@ -1195,51 +1200,61 @@ packages:
resolution: {integrity: sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.31.0':
resolution: {integrity: sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.31.0':
resolution: {integrity: sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.31.0':
resolution: {integrity: sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.31.0':
resolution: {integrity: sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-powerpc64le-gnu@4.31.0':
resolution: {integrity: sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.31.0':
resolution: {integrity: sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.31.0':
resolution: {integrity: sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.31.0':
resolution: {integrity: sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.31.0':
resolution: {integrity: sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.31.0':
resolution: {integrity: sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==}
@ -1279,24 +1294,28 @@ packages:
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@swc/core-linux-arm64-musl@1.10.8':
resolution: {integrity: sha512-wI0Hny8fHbBK/OjJ7eFYP0uDKiCMMMr5OBWGKMRRUvWs2zlGeJQZbwUeCnWuLLXzDfL+feMfh5TieYlqKTTtRw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@swc/core-linux-x64-gnu@1.10.8':
resolution: {integrity: sha512-24FCRUFO8gzPP2eu3soHTm3lk+ktcsIhdM2DTOlXGA+2TBYFWgAZX/yZV+eeRrtIZYSr4OcOWsNWnQ5Ma4budA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-musl@1.10.8':
resolution: {integrity: sha512-mBo7M/FmUhoWpUG17MLbS98iRA7t6ThxQBWDJZd322whkN1GqrvumYm2wvvjmoMTeDOPwAL3hIIa5H+Q4vb1zA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@swc/core-win32-arm64-msvc@1.10.8':
resolution: {integrity: sha512-rXJ9y77JZZXoZkgFR0mObKa3TethRBJ6Exs/pwhScl9pz4qsfxhj/bQbEu1g1i/ihmd0l+IKZwGSC7Ibh3HA2Q==}
@ -1534,8 +1553,8 @@ packages:
'@vue/reactivity@3.5.13':
resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
'@vue/repl@4.5.0':
resolution: {integrity: sha512-nWQfTzBePs5zN4qIK+vwEMEDHCuWWx2AY0utun37cSD2Qi4C84dlTtO/OL0xDzBB8Ob7250KYzIzDP3N3l3qLg==}
'@vue/repl@4.5.1':
resolution: {integrity: sha512-YYXvFue2GOrZ6EWnoA8yQVKzdCIn45+tpwJHzMof1uwrgyYAVY9ynxCsDYeAuWcpaAeylg/nybhFuqiFy2uvYA==}
'@vue/runtime-core@3.5.13':
resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==}
@ -4753,7 +4772,7 @@ snapshots:
dependencies:
'@vue/shared': 3.5.13
'@vue/repl@4.5.0': {}
'@vue/repl@4.5.1': {}
'@vue/runtime-core@3.5.13':
dependencies: