From 75b0937d31839cff1e00065f403f6f92c2af918a Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Mon, 5 Feb 2024 22:37:09 +0800 Subject: [PATCH] feat(compiler-vapor): complex identifier rewriting with vOn (#113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 三咲智子 Kevin Deng --- .../transforms/__snapshots__/vOn.spec.ts.snap | 12 +++ .../__tests__/transforms/vOn.spec.ts | 23 ++++++ .../src/generators/expression.ts | 80 +++++++++++++++---- 3 files changed, 98 insertions(+), 17 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap index ba5cd77b0..a14558772 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap @@ -353,6 +353,18 @@ export function render(_ctx) { }" `; +exports[`v-on > should wrap in unref if identifier is setup-maybe-ref w/ inline: true 1`] = ` +"(() => { + const t0 = _template("
") + const n0 = t0() + const { 0: [n1], 1: [n2], 2: [n3],} = _children(n0) + _on(n1, "click", $event => (x.value=_unref(y))) + _on(n2, "click", $event => (x.value++)) + _on(n3, "click", $event => ({ x: x.value } = _unref(y))) + return n0 +})()" +`; + exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = ` "import { template as _template, children as _children, withModifiers as _withModifiers, withKeys as _withKeys, on as _on } from 'vue/vapor'; diff --git a/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts b/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts index 80ba34445..f316301a8 100644 --- a/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts @@ -170,6 +170,29 @@ describe('v-on', () => { expect(code).contains('_on(n1, "click", $event => (_ctx.i++))') }) + test('should wrap in unref if identifier is setup-maybe-ref w/ inline: true', () => { + const { code, helpers, vaporHelpers } = compileWithVOn( + `
`, + { + mode: 'module', + inline: true, + bindingMetadata: { + x: BindingTypes.SETUP_MAYBE_REF, + y: BindingTypes.SETUP_MAYBE_REF, + }, + }, + ) + expect(code).matchSnapshot() + + expect(vaporHelpers).contains('unref') + expect(helpers.size).toBe(0) + expect(code).contains('_on(n1, "click", $event => (x.value=_unref(y)))') + expect(code).contains('_on(n2, "click", $event => (x.value++))') + expect(code).contains( + '_on(n3, "click", $event => ({ x: x.value } = _unref(y)))', + ) + }) + test('should handle multiple inline statement', () => { const { ir, code } = compileWithVOn(`
`) diff --git a/packages/compiler-vapor/src/generators/expression.ts b/packages/compiler-vapor/src/generators/expression.ts index 8cf5f30e7..e1675a274 100644 --- a/packages/compiler-vapor/src/generators/expression.ts +++ b/packages/compiler-vapor/src/generators/expression.ts @@ -3,6 +3,8 @@ import { NewlineType, type SourceLocation, advancePositionWithClone, + isInDestructureAssignment, + isStaticProperty, walkIdentifiers, } from '@vue/compiler-dom' import { isGloballyAllowed, isString, makeMap } from '@vue/shared' @@ -13,6 +15,7 @@ import { type CodegenContext, buildCodeFragment, } from '../generate' +import type { Node } from '@babel/types' export function genExpression( node: IRExpression, @@ -42,11 +45,21 @@ export function genExpression( // the expression is a simple identifier if (ast === null) { - return [genIdentifier(rawExpr, context, loc)] + return genIdentifier(rawExpr, context, loc) } const ids: Identifier[] = [] - walkIdentifiers(ast!, id => ids.push(id)) + const parentStackMap = new WeakMap() + const parentStack: Node[] = [] + walkIdentifiers( + ast!, + id => { + ids.push(id) + parentStackMap.set(id, parentStack.slice()) + }, + false, + parentStack, + ) if (ids.length) { ids.sort((a, b) => a.start! - b.start!) const [frag, push] = buildCodeFragment() @@ -60,12 +73,20 @@ export function genExpression( if (leadingText.length) push([leadingText, NewlineType.Unknown]) const source = rawExpr.slice(start, end) + const parentStack = parentStackMap.get(id)! push( - genIdentifier(source, context, { - start: advancePositionWithClone(node.loc.start, source, start), - end: advancePositionWithClone(node.loc.start, source, end), + ...genIdentifier( source, - }), + context, + { + start: advancePositionWithClone(node.loc.start, source, start), + end: advancePositionWithClone(node.loc.start, source, end), + source, + }, + id, + parentStack[parentStack.length - 1], + parentStack, + ), ) if (i === ids.length - 1 && end < rawExpr.length) { @@ -81,30 +102,55 @@ export function genExpression( const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this') function genIdentifier( - id: string, + raw: string, { options, vaporHelper, identifiers }: CodegenContext, loc?: SourceLocation, -): CodeFragment { + id?: Identifier, + parent?: Node, + parentStack?: Node[], +): CodeFragment[] { const { inline, bindingMetadata } = options - let name: string | undefined = id + let name: string | undefined = raw - const idMap = identifiers[id] + const idMap = identifiers[raw] if (idMap && idMap.length) { - return [idMap[0], NewlineType.None, loc] + return [[idMap[0], NewlineType.None, loc]] + } + + let prefix: string | undefined + if (isStaticProperty(parent!) && parent.shorthand) { + // property shorthand like { foo }, we need to add the key since + // we rewrite the value + prefix = `${raw}: ` } if (inline) { - switch (bindingMetadata[id]) { + switch (bindingMetadata[raw]) { case BindingTypes.SETUP_REF: - name = id += '.value' + name = raw = `${raw}.value` break case BindingTypes.SETUP_MAYBE_REF: - id = `${vaporHelper('unref')}(${id})` - name = undefined + // ({ x } = y) + const isDestructureAssignment = + parent && isInDestructureAssignment(parent, parentStack || []) + // x = y + const isAssignmentLVal = + parent && parent.type === 'AssignmentExpression' && parent.left === id + // x++ + const isUpdateArg = + parent && parent.type === 'UpdateExpression' && parent.argument === id + // const binding that may or may not be ref + // if it's not a ref, then assignments don't make sense - + // so we ignore the non-ref assignment case and generate code + // that assumes the value to be a ref for more efficiency + raw = + isAssignmentLVal || isUpdateArg || isDestructureAssignment + ? (name = `${raw}.value`) + : `${vaporHelper('unref')}(${raw})` break } } else { - id = `_ctx.${id}` + raw = `_ctx.${raw}` } - return [id, NewlineType.None, loc, name] + return [prefix, [raw, NewlineType.None, loc, name]] }