diff --git a/packages-private/sfc-playground/package.json b/packages-private/sfc-playground/package.json index 486971ad5..418b43d22 100644 --- a/packages-private/sfc-playground/package.json +++ b/packages-private/sfc-playground/package.json @@ -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:*" diff --git a/packages/compiler-sfc/__tests__/parse.spec.ts b/packages/compiler-sfc/__tests__/parse.spec.ts index 265655e47..82b8cf98f 100644 --- a/packages/compiler-sfc/__tests__/parse.spec.ts +++ b/packages/compiler-sfc/__tests__/parse.spec.ts @@ -381,6 +381,17 @@ h1 { color: red } }) }) + describe('vapor mode', () => { + test('on empty script', () => { + const { descriptor } = parse(``) + expect(descriptor.vapor).toBe(true) + }) + test('on template', () => { + const { descriptor } = parse(``) + expect(descriptor.vapor).toBe(true) + }) + }) + describe('warnings', () => { function assertWarning(errors: Error[], msg: string) { expect(errors.some(e => e.message.match(msg))).toBe(true) diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts index 8e8b23381..98b08a208 100644 --- a/packages/compiler-sfc/src/parse.ts +++ b/packages/compiler-sfc/src/parse.ts @@ -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) } /** diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap index 24585e39e..6e7d4229d 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap @@ -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("
", 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("", 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("", 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("") diff --git a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts index 5025997e2..9a5f6ab69 100644 --- a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts @@ -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(` + + `) + 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(` + + `) + 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(` diff --git a/packages/compiler-vapor/src/generators/expression.ts b/packages/compiler-vapor/src/generators/expression.ts index e128ccfbe..eedaeeb38 100644 --- a/packages/compiler-vapor/src/generators/expression.ts +++ b/packages/compiler-vapor/src/generators/expression.ts @@ -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) } diff --git a/packages/compiler-vapor/src/generators/text.ts b/packages/compiler-vapor/src/generators/text.ts index 3c9835f88..280d0a918 100644 --- a/packages/compiler-vapor/src/generators/text.ts +++ b/packages/compiler-vapor/src/generators/text.ts @@ -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 }) diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 6c80662a5..387bf1136 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -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 { diff --git a/packages/reactivity/src/debug.ts b/packages/reactivity/src/debug.ts index aa7c87c95..5503dc8a1 100644 --- a/packages/reactivity/src/debug.ts +++ b/packages/reactivity/src/debug.ts @@ -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) } diff --git a/packages/reactivity/src/system.ts b/packages/reactivity/src/system.ts index 056ccfdd1..c88914a39 100644 --- a/packages/reactivity/src/system.ts +++ b/packages/reactivity/src/system.ts @@ -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)) { diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 5750bc168..a73b5557d 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -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) { diff --git a/packages/runtime-vapor/__tests__/_utils.ts b/packages/runtime-vapor/__tests__/_utils.ts index c34eb05a0..0ed645544 100644 --- a/packages/runtime-vapor/__tests__/_utils.ts +++ b/packages/runtime-vapor/__tests__/_utils.ts @@ -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