]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: compound expression for v-bind
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Tue, 5 Dec 2023 16:15:57 +0000 (00:15 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Tue, 5 Dec 2023 16:17:16 +0000 (00:17 +0800)
README.md
packages/compiler-core/src/codegen.ts
packages/compiler-vapor/src/generate.ts
packages/compiler-vapor/src/ir.ts
packages/compiler-vapor/src/transform.ts
packages/compiler-vapor/src/transforms/transformElement.ts
packages/template-explorer/src/index.ts
playground/src/literal-expression.vue [new file with mode: 0644]

index 498bdcdc68daeb7df08fadddb01f91257192eeed..23426da1ce99fd4254fb58c382089108381071f2 100644 (file)
--- 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`
index 6ab8a998704761c55454202a3f3bf64bec12ee7d..765b20b553e0d31d1c3c6f37375f56462d869a43 100644 (file)
@@ -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
 }
 
index 2718801d1df9272dcb3c641b97c45fde237f0a68..a9c29d7cf35af2a571131cc1b0a4a29b57ebdbd4 100644 (file)
@@ -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)
+
+  const { content: rawExpr, ast, isStatic, loc } = node
+  if (__BROWSER__) {
+    return push(rawExpr)
+  }
+
+  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))
+  }
 
-  let { content } = exp
-  let name: string | undefined
+  if (ast === null) {
+    // the expression is a simple identifier
+    return genIdentifier(rawExpr, context, loc)
+  }
 
-  if (exp.isStatic) {
-    content = JSON.stringify(content)
-  } else {
-    switch (bindingMetadata[content]) {
+  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:
-        content += '.value'
+        name = id += '.value'
         break
       case BindingTypes.SETUP_MAYBE_REF:
-        content = `${vaporHelper('unref')}(${content})`
+        id = `${vaporHelper('unref')}(${id})`
+        name = undefined
         break
     }
-    if (prefixIdentifiers && !inline) {
-      if (isSimpleIdentifier(content)) name = content
-      content = `_ctx.${content}`
-    }
+  } else {
+    id = `_ctx.${id}`
   }
-
-  push(content, NewlineType.None, exp.loc, name)
+  push(id, NewlineType.None, loc, name)
 }
index 78ddc6ec8857828493093e0193b42802b5674dbd..bbfd6e7a446f421d4b4353e3ed7280ae1705ed0d 100644 (file)
@@ -1,4 +1,5 @@
 import type {
+  CompoundExpressionNode,
   DirectiveNode,
   RootNode,
   SimpleExpressionNode,
@@ -166,10 +167,10 @@ export type HackOptions<T> = Prettify<
   >
 >
 
-export type HackDirectiveNode = Overwrite<
+export type VaporDirectiveNode = Overwrite<
   DirectiveNode,
   {
-    exp: SimpleExpressionNode | undefined
-    arg: SimpleExpressionNode | undefined
+    exp: Exclude<DirectiveNode['exp'], CompoundExpressionNode>
+    arg: Exclude<DirectiveNode['arg'], CompoundExpressionNode>
   }
 >
index ce0b373c2acdd2abd7cd5a37cce75666fa972f1e..4496df6e6a343c2b517dace85989f3a113c149f1 100644 (file)
@@ -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<ElementNode>,
-  // 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<BaseTransformOptions>
index 6a3615b73ec334788c901c784ddfbdc73ae3d325..8ff3a5575fffc7081850f579824dd2a4d9ccffd7 100644 (file)
@@ -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<ElementNode>,
 ): void {
index ca89addfb40811299d46d247a00a4d64833e259a..90c030bfd926712c613adccfe55b5d6ab477ba5f 100644 (file)
@@ -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 (file)
index 0000000..a633315
--- /dev/null
@@ -0,0 +1,3 @@
+<template>
+  <div :id="'hello'"></div>
+</template>