]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compiler-vapor): extract gen operation
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sun, 21 Jan 2024 05:43:23 +0000 (13:43 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sun, 21 Jan 2024 05:43:23 +0000 (13:43 +0800)
12 files changed:
packages/compiler-vapor/src/generate.ts
packages/compiler-vapor/src/generators/directive.ts [new file with mode: 0644]
packages/compiler-vapor/src/generators/dom.ts [new file with mode: 0644]
packages/compiler-vapor/src/generators/event.ts [new file with mode: 0644]
packages/compiler-vapor/src/generators/expression.ts [new file with mode: 0644]
packages/compiler-vapor/src/generators/html.ts [new file with mode: 0644]
packages/compiler-vapor/src/generators/modelValue.ts [new file with mode: 0644]
packages/compiler-vapor/src/generators/prop.ts [new file with mode: 0644]
packages/compiler-vapor/src/generators/ref.ts [new file with mode: 0644]
packages/compiler-vapor/src/generators/text.ts [new file with mode: 0644]
packages/compiler-vapor/src/ir.ts
packages/compiler-vapor/src/transforms/vModel.ts

index c6e5202dec01e5d697dced5115d1f3e625bb02d2..bba27e32541edb236e582da8b533ef0831832756 100644 (file)
@@ -1,50 +1,36 @@
 import {
   type CodegenOptions as BaseCodegenOptions,
-  BindingTypes,
   type CodegenResult,
   NewlineType,
   type Position,
   type SourceLocation,
-  advancePositionWithClone,
   advancePositionWithMutation,
-  createSimpleExpression,
-  isMemberExpression,
-  isSimpleIdentifier,
   locStub,
-  walkIdentifiers,
 } from '@vue/compiler-dom'
 import {
-  type AppendNodeIRNode,
-  type CreateTextNodeIRNode,
   type IRDynamicChildren,
-  type IRExpression,
   IRNodeTypes,
-  type InsertNodeIRNode,
   type OperationNode,
-  type PrependNodeIRNode,
   type RootIRNode,
-  type SetEventIRNode,
-  type SetHtmlIRNode,
-  type SetModelValueIRNode,
-  type SetPropIRNode,
-  type SetRefIRNode,
-  type SetTextIRNode,
   type VaporHelper,
   type WithDirectiveIRNode,
 } from './ir'
 import { SourceMapGenerator } from 'source-map-js'
-import { camelize, isGloballyAllowed, isString, makeMap } from '@vue/shared'
-import type { Identifier } from '@babel/types'
+import { isString } from '@vue/shared'
 import type { ParserPlugin } from '@babel/parser'
+import { genSetProp } from './generators/prop'
+import { genCreateTextNode, genSetText } from './generators/text'
+import { genSetEvent } from './generators/event'
+import { genSetHtml } from './generators/html'
+import { genSetRef } from './generators/ref'
+import { genSetModelValue } from './generators/modelValue'
+import { genAppendNode, genInsertNode, genPrependNode } from './generators/dom'
+import { genWithDirective } from './generators/directive'
 
 interface CodegenOptions extends BaseCodegenOptions {
   expressionPlugins?: ParserPlugin[]
 }
 
-// TODO: share this with compiler-core
-const fnExpRE =
-  /^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
-
 // remove when stable
 // @ts-expect-error
 function checkNever(x: never): never {}
@@ -407,361 +393,3 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
       return checkNever(oper)
   }
 }
-
-function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
-  const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context
-
-  newline()
-  pushFnCall(
-    vaporHelper('setDynamicProp'),
-    `n${oper.element}`,
-    // 2. key name
-    () => {
-      if (oper.runtimeCamelize) {
-        pushFnCall(helper('camelize'), () => genExpression(oper.key, context))
-      } else if (oper.runtimePrefix) {
-        pushMulti([`\`${oper.runtimePrefix}\${`, `}\``], () =>
-          genExpression(oper.key, context),
-        )
-      } else {
-        genExpression(oper.key, context)
-      }
-    },
-    'undefined',
-    () => genExpression(oper.value, context),
-  )
-}
-
-function genSetText(oper: SetTextIRNode, context: CodegenContext) {
-  const { pushFnCall, newline, vaporHelper } = context
-  newline()
-  pushFnCall(vaporHelper('setText'), `n${oper.element}`, 'undefined', () =>
-    genExpression(oper.value, context),
-  )
-}
-
-function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
-  const { newline, pushFnCall, vaporHelper } = context
-  newline()
-  pushFnCall(vaporHelper('setHtml'), `n${oper.element}`, 'undefined', () =>
-    genExpression(oper.value, context),
-  )
-}
-
-function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
-  const { newline, pushFnCall, vaporHelper } = context
-  newline()
-  pushFnCall(vaporHelper('setRef'), `n${oper.element}`, () =>
-    genExpression(oper.value, context),
-  )
-}
-
-function genSetModelValue(oper: SetModelValueIRNode, context: CodegenContext) {
-  const { vaporHelper, push, newline, pushFnCall } = context
-
-  newline()
-  pushFnCall(
-    vaporHelper('on'),
-    // 1st arg: event name
-    () => push(`n${oper.element}`),
-    // 2nd arg: event name
-    () => {
-      if (isString(oper.key)) {
-        push(JSON.stringify(`update:${camelize(oper.key)}`))
-      } else {
-        push('`update:${')
-        genExpression(oper.key, context)
-        push('}`')
-      }
-    },
-    // 3rd arg: event handler
-    () => {
-      push((context.isTS ? `($event: any)` : `$event`) + ' => ((')
-      // TODO handle not a ref
-      genExpression(oper.value, context)
-      push(') = $event)')
-    },
-  )
-}
-
-function genCreateTextNode(
-  oper: CreateTextNodeIRNode,
-  context: CodegenContext,
-) {
-  const { pushNewline, pushFnCall, vaporHelper } = context
-  pushNewline(`const n${oper.id} = `)
-  pushFnCall(vaporHelper('createTextNode'), () =>
-    genExpression(oper.value, context),
-  )
-}
-
-function genInsertNode(oper: InsertNodeIRNode, context: CodegenContext) {
-  const { newline, pushFnCall, vaporHelper } = context
-  const elements = ([] as number[]).concat(oper.element)
-  let element = elements.map((el) => `n${el}`).join(', ')
-  if (elements.length > 1) element = `[${element}]`
-  newline()
-  pushFnCall(
-    vaporHelper('insert'),
-    element,
-    `n${oper.parent}`,
-    `n${oper.anchor}`,
-  )
-}
-
-function genPrependNode(oper: PrependNodeIRNode, context: CodegenContext) {
-  const { newline, pushFnCall, vaporHelper } = context
-  newline()
-  pushFnCall(
-    vaporHelper('prepend'),
-    `n${oper.parent}`,
-    oper.elements.map((el) => `n${el}`).join(', '),
-  )
-}
-
-function genAppendNode(oper: AppendNodeIRNode, context: CodegenContext) {
-  const { newline, pushFnCall, vaporHelper } = context
-  newline()
-  pushFnCall(
-    vaporHelper('append'),
-    `n${oper.parent}`,
-    oper.elements.map((el) => `n${el}`).join(', '),
-  )
-}
-
-function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
-  const { vaporHelper, push, newline, pushMulti, pushFnCall } = context
-  const { keys, nonKeys, options } = oper.modifiers
-
-  newline()
-  pushFnCall(
-    vaporHelper('on'),
-    // 1st arg: event name
-    () => push(`n${oper.element}`),
-    // 2nd arg: event name
-    () => {
-      if (oper.keyOverride) {
-        const find = JSON.stringify(oper.keyOverride[0])
-        const replacement = JSON.stringify(oper.keyOverride[1])
-        pushMulti(['(', ')'], () => genExpression(oper.key, context))
-        push(` === ${find} ? ${replacement} : `)
-        pushMulti(['(', ')'], () => genExpression(oper.key, context))
-      } else {
-        genExpression(oper.key, context)
-      }
-    },
-    // 3rd arg: event handler
-    () => {
-      const pushWithKeys = (fn: () => void) => {
-        push(`${vaporHelper('withKeys')}(`)
-        fn()
-        push(`, ${genArrayExpression(keys)})`)
-      }
-      const pushWithModifiers = (fn: () => void) => {
-        push(`${vaporHelper('withModifiers')}(`)
-        fn()
-        push(`, ${genArrayExpression(nonKeys)})`)
-      }
-      const pushNoop = (fn: () => void) => fn()
-
-      ;(keys.length ? pushWithKeys : pushNoop)(() =>
-        (nonKeys.length ? pushWithModifiers : pushNoop)(() => {
-          genEventHandler(context)
-        }),
-      )
-    },
-    // 4th arg, gen options
-    !!options.length &&
-      (() => push(`{ ${options.map((v) => `${v}: true`).join(', ')} }`)),
-  )
-
-  function genEventHandler(context: CodegenContext) {
-    const exp = oper.value
-    if (exp && exp.content.trim()) {
-      const isMemberExp = isMemberExpression(exp.content, context)
-      const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
-      const hasMultipleStatements = exp.content.includes(`;`)
-
-      if (isInlineStatement) {
-        push('$event => ')
-        push(hasMultipleStatements ? '{' : '(')
-        const knownIds = Object.create(null)
-        knownIds['$event'] = 1
-        genExpression(exp, context, knownIds)
-        push(hasMultipleStatements ? '}' : ')')
-      } else if (isMemberExp) {
-        push('(...args) => (')
-        genExpression(exp, context)
-        push(' && ')
-        genExpression(exp, context)
-        push('(...args))')
-      } else {
-        genExpression(exp, context)
-      }
-    } else {
-      push('() => {}')
-    }
-  }
-}
-
-function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) {
-  const { push, newline, pushFnCall, pushMulti, vaporHelper, bindingMetadata } =
-    context
-  const { dir, builtin } = oper
-
-  // TODO merge directive for the same node
-  newline()
-  pushFnCall(
-    vaporHelper('withDirectives'),
-    // 1st arg: node
-    `n${oper.element}`,
-    // 2nd arg: directives
-    () => {
-      push('[')
-      // directive
-      pushMulti(['[', ']', ', '], () => {
-        if (dir.name === 'show') {
-          push(vaporHelper('vShow'))
-        } else if (builtin) {
-          push(vaporHelper(builtin))
-        } else {
-          const directiveReference = camelize(`v-${dir.name}`)
-          // TODO resolve directive
-          if (bindingMetadata[directiveReference]) {
-            const directiveExpression =
-              createSimpleExpression(directiveReference)
-            directiveExpression.ast = null
-            genExpression(directiveExpression, context)
-          }
-        }
-
-        if (dir.exp) {
-          push(', () => ')
-          genExpression(dir.exp, context)
-        } else if (dir.arg || dir.modifiers.length) {
-          push(', void 0')
-        }
-
-        if (dir.arg) {
-          push(', ')
-          genExpression(dir.arg, context)
-        } else if (dir.modifiers.length) {
-          push(', void 0')
-        }
-
-        if (dir.modifiers.length) {
-          push(', ')
-          push('{ ')
-          push(genDirectiveModifiers(dir.modifiers))
-          push(' }')
-        }
-      })
-      push(']')
-    },
-  )
-}
-
-// TODO: other types (not only string)
-function genArrayExpression(elements: string[]) {
-  return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]`
-}
-
-const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
-
-function genExpression(
-  node: IRExpression,
-  context: CodegenContext,
-  knownIds: Record<string, number> = Object.create(null),
-): void {
-  const { push } = context
-  if (isString(node)) return push(node)
-
-  const { content: rawExpr, ast, isStatic, loc } = node
-  if (isStatic) {
-    return push(JSON.stringify(rawExpr), NewlineType.None, loc)
-  }
-  if (
-    __BROWSER__ ||
-    !context.prefixIdentifiers ||
-    !node.content.trim() ||
-    // there was a parsing error
-    ast === false ||
-    isGloballyAllowed(rawExpr) ||
-    isLiteralWhitelisted(rawExpr)
-  ) {
-    return push(rawExpr, NewlineType.None, loc)
-  }
-
-  if (ast === null) {
-    // the expression is a simple identifier
-    return genIdentifier(rawExpr, context, loc)
-  }
-
-  const ids: Identifier[] = []
-  walkIdentifiers(
-    ast!,
-    (id, parent, parentStack, isReference, isLocal) => {
-      if (isLocal) return
-      ids.push(id)
-    },
-    false,
-    [],
-    knownIds,
-  )
-  if (ids.length) {
-    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)
-      }
-    })
-  } else {
-    push(rawExpr, 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)
-}
-
-function genDirectiveModifiers(modifiers: string[]) {
-  return modifiers
-    .map(
-      (value) =>
-        `${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
-    )
-    .join(', ')
-}
diff --git a/packages/compiler-vapor/src/generators/directive.ts b/packages/compiler-vapor/src/generators/directive.ts
new file mode 100644 (file)
index 0000000..4b54bc5
--- /dev/null
@@ -0,0 +1,74 @@
+import { createSimpleExpression, isSimpleIdentifier } from '@vue/compiler-dom'
+import { camelize } from '@vue/shared'
+import { genExpression } from './expression'
+import type { CodegenContext } from '../generate'
+import type { WithDirectiveIRNode } from '../ir'
+
+export function genWithDirective(
+  oper: WithDirectiveIRNode,
+  context: CodegenContext,
+) {
+  const { push, newline, pushFnCall, pushMulti, vaporHelper, bindingMetadata } =
+    context
+  const { dir, builtin } = oper
+
+  // TODO merge directive for the same node
+  newline()
+  pushFnCall(
+    vaporHelper('withDirectives'),
+    // 1st arg: node
+    `n${oper.element}`,
+    // 2nd arg: directives
+    () => {
+      push('[')
+      // directive
+      pushMulti(['[', ']', ', '], () => {
+        if (dir.name === 'show') {
+          push(vaporHelper('vShow'))
+        } else if (builtin) {
+          push(vaporHelper(builtin))
+        } else {
+          const directiveReference = camelize(`v-${dir.name}`)
+          // TODO resolve directive
+          if (bindingMetadata[directiveReference]) {
+            const directiveExpression =
+              createSimpleExpression(directiveReference)
+            directiveExpression.ast = null
+            genExpression(directiveExpression, context)
+          }
+        }
+
+        if (dir.exp) {
+          push(', () => ')
+          genExpression(dir.exp, context)
+        } else if (dir.arg || dir.modifiers.length) {
+          push(', void 0')
+        }
+
+        if (dir.arg) {
+          push(', ')
+          genExpression(dir.arg, context)
+        } else if (dir.modifiers.length) {
+          push(', void 0')
+        }
+
+        if (dir.modifiers.length) {
+          push(', ')
+          push('{ ')
+          push(genDirectiveModifiers(dir.modifiers))
+          push(' }')
+        }
+      })
+      push(']')
+    },
+  )
+}
+
+function genDirectiveModifiers(modifiers: string[]) {
+  return modifiers
+    .map(
+      (value) =>
+        `${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
+    )
+    .join(', ')
+}
diff --git a/packages/compiler-vapor/src/generators/dom.ts b/packages/compiler-vapor/src/generators/dom.ts
new file mode 100644 (file)
index 0000000..b6c2440
--- /dev/null
@@ -0,0 +1,43 @@
+import type { CodegenContext } from '../generate'
+import type {
+  AppendNodeIRNode,
+  InsertNodeIRNode,
+  PrependNodeIRNode,
+} from '../ir'
+
+export function genInsertNode(oper: InsertNodeIRNode, context: CodegenContext) {
+  const { newline, pushFnCall, vaporHelper } = context
+  const elements = ([] as number[]).concat(oper.element)
+  let element = elements.map((el) => `n${el}`).join(', ')
+  if (elements.length > 1) element = `[${element}]`
+  newline()
+  pushFnCall(
+    vaporHelper('insert'),
+    element,
+    `n${oper.parent}`,
+    `n${oper.anchor}`,
+  )
+}
+
+export function genPrependNode(
+  oper: PrependNodeIRNode,
+  context: CodegenContext,
+) {
+  const { newline, pushFnCall, vaporHelper } = context
+  newline()
+  pushFnCall(
+    vaporHelper('prepend'),
+    `n${oper.parent}`,
+    oper.elements.map((el) => `n${el}`).join(', '),
+  )
+}
+
+export function genAppendNode(oper: AppendNodeIRNode, context: CodegenContext) {
+  const { newline, pushFnCall, vaporHelper } = context
+  newline()
+  pushFnCall(
+    vaporHelper('append'),
+    `n${oper.parent}`,
+    oper.elements.map((el) => `n${el}`).join(', '),
+  )
+}
diff --git a/packages/compiler-vapor/src/generators/event.ts b/packages/compiler-vapor/src/generators/event.ts
new file mode 100644 (file)
index 0000000..e45fa58
--- /dev/null
@@ -0,0 +1,87 @@
+import { isMemberExpression } from '@vue/compiler-dom'
+import type { CodegenContext } from '../generate'
+import type { SetEventIRNode } from '../ir'
+import { genExpression } from './expression'
+
+// TODO: share this with compiler-core
+const fnExpRE =
+  /^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
+
+export function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
+  const { vaporHelper, push, newline, pushMulti, pushFnCall } = context
+  const { keys, nonKeys, options } = oper.modifiers
+
+  newline()
+  pushFnCall(
+    vaporHelper('on'),
+    // 1st arg: event name
+    () => push(`n${oper.element}`),
+    // 2nd arg: event name
+    () => {
+      if (oper.keyOverride) {
+        const find = JSON.stringify(oper.keyOverride[0])
+        const replacement = JSON.stringify(oper.keyOverride[1])
+        pushMulti(['(', ')'], () => genExpression(oper.key, context))
+        push(` === ${find} ? ${replacement} : `)
+        pushMulti(['(', ')'], () => genExpression(oper.key, context))
+      } else {
+        genExpression(oper.key, context)
+      }
+    },
+    // 3rd arg: event handler
+    () => {
+      const pushWithKeys = (fn: () => void) => {
+        push(`${vaporHelper('withKeys')}(`)
+        fn()
+        push(`, ${genArrayExpression(keys)})`)
+      }
+      const pushWithModifiers = (fn: () => void) => {
+        push(`${vaporHelper('withModifiers')}(`)
+        fn()
+        push(`, ${genArrayExpression(nonKeys)})`)
+      }
+      const pushNoop = (fn: () => void) => fn()
+
+      ;(keys.length ? pushWithKeys : pushNoop)(() =>
+        (nonKeys.length ? pushWithModifiers : pushNoop)(() => {
+          genEventHandler(context)
+        }),
+      )
+    },
+    // 4th arg, gen options
+    !!options.length &&
+      (() => push(`{ ${options.map((v) => `${v}: true`).join(', ')} }`)),
+  )
+
+  function genEventHandler(context: CodegenContext) {
+    const exp = oper.value
+    if (exp && exp.content.trim()) {
+      const isMemberExp = isMemberExpression(exp.content, context)
+      const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
+      const hasMultipleStatements = exp.content.includes(`;`)
+
+      if (isInlineStatement) {
+        push('$event => ')
+        push(hasMultipleStatements ? '{' : '(')
+        const knownIds = Object.create(null)
+        knownIds['$event'] = 1
+        genExpression(exp, context, knownIds)
+        push(hasMultipleStatements ? '}' : ')')
+      } else if (isMemberExp) {
+        push('(...args) => (')
+        genExpression(exp, context)
+        push(' && ')
+        genExpression(exp, context)
+        push('(...args))')
+      } else {
+        genExpression(exp, context)
+      }
+    } else {
+      push('() => {}')
+    }
+  }
+}
+
+function genArrayExpression(elements: string[]) {
+  return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]`
+}
diff --git a/packages/compiler-vapor/src/generators/expression.ts b/packages/compiler-vapor/src/generators/expression.ts
new file mode 100644 (file)
index 0000000..d6ae21f
--- /dev/null
@@ -0,0 +1,102 @@
+import {
+  BindingTypes,
+  NewlineType,
+  type SourceLocation,
+  advancePositionWithClone,
+  walkIdentifiers,
+} from '@vue/compiler-dom'
+import { isGloballyAllowed, isString, makeMap } from '@vue/shared'
+import type { Identifier } from '@babel/types'
+import type { IRExpression } from '../ir'
+import type { CodegenContext } from '../generate'
+
+export function genExpression(
+  node: IRExpression,
+  context: CodegenContext,
+  knownIds: Record<string, number> = Object.create(null),
+): void {
+  const { push } = context
+  if (isString(node)) return push(node)
+
+  const { content: rawExpr, ast, isStatic, loc } = node
+  if (isStatic) {
+    return push(JSON.stringify(rawExpr), NewlineType.None, loc)
+  }
+  if (
+    __BROWSER__ ||
+    !context.prefixIdentifiers ||
+    !node.content.trim() ||
+    // there was a parsing error
+    ast === false ||
+    isGloballyAllowed(rawExpr) ||
+    isLiteralWhitelisted(rawExpr)
+  ) {
+    return push(rawExpr, NewlineType.None, loc)
+  }
+
+  if (ast === null) {
+    // the expression is a simple identifier
+    return genIdentifier(rawExpr, context, loc)
+  }
+
+  const ids: Identifier[] = []
+  walkIdentifiers(
+    ast!,
+    (id, parent, parentStack, isReference, isLocal) => {
+      if (isLocal) return
+      ids.push(id)
+    },
+    false,
+    [],
+    knownIds,
+  )
+  if (ids.length) {
+    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)
+      }
+    })
+  } else {
+    push(rawExpr, NewlineType.Unknown)
+  }
+}
+
+const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
+
+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/generators/html.ts b/packages/compiler-vapor/src/generators/html.ts
new file mode 100644 (file)
index 0000000..503d56a
--- /dev/null
@@ -0,0 +1,11 @@
+import type { CodegenContext } from '../generate'
+import type { SetHtmlIRNode } from '../ir'
+import { genExpression } from './expression'
+
+export function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
+  const { newline, pushFnCall, vaporHelper } = context
+  newline()
+  pushFnCall(vaporHelper('setHtml'), `n${oper.element}`, 'undefined', () =>
+    genExpression(oper.value, context),
+  )
+}
diff --git a/packages/compiler-vapor/src/generators/modelValue.ts b/packages/compiler-vapor/src/generators/modelValue.ts
new file mode 100644 (file)
index 0000000..1cc7624
--- /dev/null
@@ -0,0 +1,35 @@
+import { camelize, isString } from '@vue/shared'
+import { genExpression } from './expression'
+import type { SetModelValueIRNode } from '../ir'
+import type { CodegenContext } from '../generate'
+
+export function genSetModelValue(
+  oper: SetModelValueIRNode,
+  context: CodegenContext,
+) {
+  const { vaporHelper, push, newline, pushFnCall } = context
+
+  newline()
+  pushFnCall(
+    vaporHelper('on'),
+    // 1st arg: event name
+    () => push(`n${oper.element}`),
+    // 2nd arg: event name
+    () => {
+      if (isString(oper.key)) {
+        push(JSON.stringify(`update:${camelize(oper.key)}`))
+      } else {
+        push('`update:${')
+        genExpression(oper.key, context)
+        push('}`')
+      }
+    },
+    // 3rd arg: event handler
+    () => {
+      push((context.isTS ? `($event: any)` : `$event`) + ' => ((')
+      // TODO handle not a ref
+      genExpression(oper.value, context)
+      push(') = $event)')
+    },
+  )
+}
diff --git a/packages/compiler-vapor/src/generators/prop.ts b/packages/compiler-vapor/src/generators/prop.ts
new file mode 100644 (file)
index 0000000..89e0fc6
--- /dev/null
@@ -0,0 +1,27 @@
+import type { CodegenContext } from '../generate'
+import type { SetPropIRNode } from '../ir'
+import { genExpression } from './expression'
+
+export function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
+  const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context
+
+  newline()
+  pushFnCall(
+    vaporHelper('setDynamicProp'),
+    `n${oper.element}`,
+    // 2. key name
+    () => {
+      if (oper.runtimeCamelize) {
+        pushFnCall(helper('camelize'), () => genExpression(oper.key, context))
+      } else if (oper.runtimePrefix) {
+        pushMulti([`\`${oper.runtimePrefix}\${`, `}\``], () =>
+          genExpression(oper.key, context),
+        )
+      } else {
+        genExpression(oper.key, context)
+      }
+    },
+    'undefined',
+    () => genExpression(oper.value, context),
+  )
+}
diff --git a/packages/compiler-vapor/src/generators/ref.ts b/packages/compiler-vapor/src/generators/ref.ts
new file mode 100644 (file)
index 0000000..78b8024
--- /dev/null
@@ -0,0 +1,11 @@
+import type { CodegenContext } from '../generate'
+import type { SetRefIRNode } from '../ir'
+import { genExpression } from './expression'
+
+export function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
+  const { newline, pushFnCall, vaporHelper } = context
+  newline()
+  pushFnCall(vaporHelper('setRef'), `n${oper.element}`, () =>
+    genExpression(oper.value, context),
+  )
+}
diff --git a/packages/compiler-vapor/src/generators/text.ts b/packages/compiler-vapor/src/generators/text.ts
new file mode 100644 (file)
index 0000000..c1d708a
--- /dev/null
@@ -0,0 +1,22 @@
+import type { CodegenContext } from '../generate'
+import type { CreateTextNodeIRNode, SetTextIRNode } from '../ir'
+import { genExpression } from './expression'
+
+export function genSetText(oper: SetTextIRNode, context: CodegenContext) {
+  const { pushFnCall, newline, vaporHelper } = context
+  newline()
+  pushFnCall(vaporHelper('setText'), `n${oper.element}`, 'undefined', () =>
+    genExpression(oper.value, context),
+  )
+}
+
+export function genCreateTextNode(
+  oper: CreateTextNodeIRNode,
+  context: CodegenContext,
+) {
+  const { pushNewline, pushFnCall, vaporHelper } = context
+  pushNewline(`const n${oper.id} = `)
+  pushFnCall(vaporHelper('createTextNode'), () =>
+    genExpression(oper.value, context),
+  )
+}
index dbf381dcb69bc2835810d32cfa72bb590a822285..00fcdb587588562b4c5f8fae7137243e94fa7ba0 100644 (file)
@@ -1,4 +1,5 @@
 import type {
+  BindingTypes,
   CompoundExpressionNode,
   DirectiveNode,
   RootNode,
@@ -106,6 +107,7 @@ export interface SetModelValueIRNode extends BaseIRNode {
   element: number
   key: IRExpression
   value: IRExpression
+  bindingType?: BindingTypes
   isComponent: boolean
 }
 
index adc344ff5de7195c455d77b14354eeb8e4de1eab..d91a9e090a7dd6222239faeefc0db3d33a0a9161 100644 (file)
@@ -27,7 +27,6 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
   // we assume v-model directives are always parsed
   // (not artificially created by a transform)
   const rawExp = exp.loc.source
-  const expString = exp.content
 
   // in SFC <script setup> inline mode, the exp may have been transformed into
   // _unref(exp)
@@ -44,13 +43,13 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
     return
   }
 
+  const expString = exp.content
   const maybeRef =
     !__BROWSER__ &&
     context.options.inline &&
     (bindingType === BindingTypes.SETUP_LET ||
       bindingType === BindingTypes.SETUP_REF ||
       bindingType === BindingTypes.SETUP_MAYBE_REF)
-
   if (
     !expString.trim() ||
     (!isMemberExpression(expString, context.options) && !maybeRef)