From f644ed4081abbe5b87b9628003fe7ff727bc6c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 6 Dec 2023 00:15:57 +0800 Subject: [PATCH] feat: compound expression for v-bind --- README.md | 6 +- packages/compiler-core/src/codegen.ts | 4 + packages/compiler-vapor/src/generate.ts | 123 ++++++++++++------ packages/compiler-vapor/src/ir.ts | 7 +- packages/compiler-vapor/src/transform.ts | 7 +- .../src/transforms/transformElement.ts | 6 +- packages/template-explorer/src/index.ts | 4 + playground/src/literal-expression.vue | 3 + 8 files changed, 109 insertions(+), 51 deletions(-) create mode 100644 playground/src/literal-expression.vue diff --git a/README.md b/README.md index 498bdcdc6..23426da1c 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,13 @@ PR are welcome! However, please create an issue before you start to work on it, - [x] `v-text` - [x] `v-pre` - [x] `v-cloak` + - [x] `v-bind` + - [x] simple expression + - [x] compound expression - [ ] `v-on` - [x] simple expression - [ ] compound expression - [x] modifiers - - [ ] `v-bind` - - [x] simple expression - - [ ] compound expression - [ ] runtime directives - #19 - [ ] `v-memo` diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 6ab8a9987..765b20b55 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -70,9 +70,13 @@ export interface CodegenResult { } export enum NewlineType { + /** Start with `\n` */ Start = 0, + /** Ends with `\n` */ End = -1, + /** No `\n` included */ None = -2, + /** Don't know, calc it */ Unknown = -3 } diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 2718801d1..a9c29d7cf 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -7,8 +7,9 @@ import { advancePositionWithMutation, locStub, BindingTypes, - isSimpleIdentifier, createSimpleExpression, + walkIdentifiers, + advancePositionWithClone, } from '@vue/compiler-dom' import { type IRDynamicChildren, @@ -20,15 +21,16 @@ import { type SetEventIRNode, type WithDirectiveIRNode, type SetTextIRNode, + type SetHtmlIRNode, + type CreateTextNodeIRNode, + type InsertNodeIRNode, + type PrependNodeIRNode, + type AppendNodeIRNode, IRNodeTypes, - SetHtmlIRNode, - CreateTextNodeIRNode, - InsertNodeIRNode, - PrependNodeIRNode, - AppendNodeIRNode, } from './ir' import { SourceMapGenerator } from 'source-map-js' import { camelize, isString } from '@vue/shared' +import type { Identifier } from '@babel/types' // remove when stable // @ts-expect-error @@ -467,7 +469,9 @@ function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) { // TODO resolve directive const directiveReference = camelize(`v-${oper.name}`) if (bindingMetadata[directiveReference]) { - genExpression(createSimpleExpression(directiveReference), context) + const directiveExpression = createSimpleExpression(directiveReference) + directiveExpression.ast = null + genExpression(directiveExpression, context) } if (oper.binding) { @@ -483,37 +487,82 @@ function genArrayExpression(elements: string[]) { return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]` } -function genExpression( - exp: IRExpression, - { - inline, - prefixIdentifiers, - bindingMetadata, - vaporHelper, - push, - }: CodegenContext, -) { - if (isString(exp)) return push(exp) +function genExpression(node: IRExpression, context: CodegenContext): void { + const { push } = context + if (isString(node)) return push(node) - let { content } = exp - let name: string | undefined - - if (exp.isStatic) { - content = JSON.stringify(content) - } else { - switch (bindingMetadata[content]) { - case BindingTypes.SETUP_REF: - content += '.value' - break - case BindingTypes.SETUP_MAYBE_REF: - content = `${vaporHelper('unref')}(${content})` - break - } - if (prefixIdentifiers && !inline) { - if (isSimpleIdentifier(content)) name = content - content = `_ctx.${content}` - } + const { content: rawExpr, ast, isStatic, loc } = node + if (__BROWSER__) { + return push(rawExpr) } - push(content, NewlineType.None, exp.loc, name) + if ( + !context.prefixIdentifiers || + !node.content.trim() || + // there was a parsing error + ast === false + ) { + return push(rawExpr, NewlineType.None, node.loc) + } + if (isStatic) { + // TODO + return push(JSON.stringify(rawExpr)) + } + + if (ast === null) { + // the expression is a simple identifier + return genIdentifier(rawExpr, context, loc) + } + + const ids: Identifier[] = [] + walkIdentifiers( + ast!, + (id) => { + ids.push(id) + }, + true, + ) + ids.sort((a, b) => a.start! - b.start!) + ids.forEach((id, i) => { + // range is offset by -1 due to the wrapping parens when parsed + const start = id.start! - 1 + const end = id.end! - 1 + const last = ids[i - 1] + + const leadingText = rawExpr.slice(last ? last.end! - 1 : 0, start) + if (leadingText.length) push(leadingText, NewlineType.Unknown) + + const source = rawExpr.slice(start, end) + genIdentifier(source, context, { + start: advancePositionWithClone(node.loc.start, source, start), + end: advancePositionWithClone(node.loc.start, source, end), + source, + }) + + if (i === ids.length - 1 && end < rawExpr.length) { + push(rawExpr.slice(end), NewlineType.Unknown) + } + }) +} + +function genIdentifier( + id: string, + { inline, bindingMetadata, vaporHelper, push }: CodegenContext, + loc?: SourceLocation, +): void { + let name: string | undefined = id + if (inline) { + switch (bindingMetadata[id]) { + case BindingTypes.SETUP_REF: + name = id += '.value' + break + case BindingTypes.SETUP_MAYBE_REF: + id = `${vaporHelper('unref')}(${id})` + name = undefined + break + } + } else { + id = `_ctx.${id}` + } + push(id, NewlineType.None, loc, name) } diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 78ddc6ec8..bbfd6e7a4 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -1,4 +1,5 @@ import type { + CompoundExpressionNode, DirectiveNode, RootNode, SimpleExpressionNode, @@ -166,10 +167,10 @@ export type HackOptions = Prettify< > > -export type HackDirectiveNode = Overwrite< +export type VaporDirectiveNode = Overwrite< DirectiveNode, { - exp: SimpleExpressionNode | undefined - arg: SimpleExpressionNode | undefined + exp: Exclude + arg: Exclude } > diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index ce0b373c2..4496df6e6 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -18,7 +18,7 @@ import { type IRExpression, IRNodeTypes, } from './ir' -import type { HackDirectiveNode, HackOptions } from './ir' +import type { VaporDirectiveNode, HackOptions } from './ir' export type NodeTransform = ( node: RootNode | TemplateChildNode, @@ -26,12 +26,9 @@ export type NodeTransform = ( ) => void | (() => void) | (() => void)[] export type DirectiveTransform = ( - dir: HackDirectiveNode, + dir: VaporDirectiveNode, node: ElementNode, context: TransformContext, - // a platform specific compiler can import the base transform and augment - // it by passing in this optional argument. - // augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult, ) => void export type TransformOptions = HackOptions diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 6a3615b73..8ff3a5575 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -8,7 +8,7 @@ import { } from '@vue/compiler-dom' import { isBuiltInDirective, isVoidTag } from '@vue/shared' import { NodeTransform, TransformContext } from '../transform' -import { HackDirectiveNode, IRNodeTypes } from '../ir' +import { VaporDirectiveNode, IRNodeTypes } from '../ir' export const transformElement: NodeTransform = (node, ctx) => { return function postTransformElement() { @@ -52,12 +52,12 @@ function buildProps( isComponent: boolean, ) { for (const prop of props) { - transformProp(prop as HackDirectiveNode | AttributeNode, node, context) + transformProp(prop as VaporDirectiveNode | AttributeNode, node, context) } } function transformProp( - prop: HackDirectiveNode | AttributeNode, + prop: VaporDirectiveNode | AttributeNode, node: ElementNode, context: TransformContext, ): void { diff --git a/packages/template-explorer/src/index.ts b/packages/template-explorer/src/index.ts index ca89addfb..90c030bfd 100644 --- a/packages/template-explorer/src/index.ts +++ b/packages/template-explorer/src/index.ts @@ -78,6 +78,10 @@ window.init = () => { const start = performance.now() const { code, ast, map } = compileFn(source, { ...compilerOptions, + prefixIdentifiers: + compilerOptions.prefixIdentifiers || + compilerOptions.mode === 'module' || + compilerOptions.ssr, filename: 'ExampleTemplate.vue', sourceMap: true, onError: err => { diff --git a/playground/src/literal-expression.vue b/playground/src/literal-expression.vue new file mode 100644 index 000000000..a633315d9 --- /dev/null +++ b/playground/src/literal-expression.vue @@ -0,0 +1,3 @@ +