]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: DirectiveTransform
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 2 Dec 2023 17:40:26 +0000 (01:40 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 2 Dec 2023 17:43:31 +0000 (01:43 +0800)
README.md
packages/compiler-vapor/__tests__/compile.test.ts
packages/compiler-vapor/src/compile.ts
packages/compiler-vapor/src/hack.ts
packages/compiler-vapor/src/transform.ts
packages/compiler-vapor/src/transforms/transformElement.ts
packages/compiler-vapor/src/transforms/vHtml.ts [new file with mode: 0644]
packages/compiler-vapor/src/transforms/vText.ts [new file with mode: 0644]

index b7100cc01c31f326de3a3412baaa003a1a9259f0..3b721f1559665526fa67733cf9c6d308d6ce9554 100644 (file)
--- a/README.md
+++ b/README.md
@@ -16,9 +16,9 @@ PR are welcome! However, please create an issue before you start to work on it,
   - [x] simple bindings
   - [x] simple events
 - [ ] TODO-MVC App
-- [ ] transform
+- [x] transform
   - [x] NodeTransform
-  - [ ] DirectiveTransform
+  - [x] DirectiveTransform
 - [ ] directives
   - [x] `v-once`
   - [x] `v-html`
index 197a1ecd7c9b6f0c8eb3b43ea2c62e414e5a3fb4..4174187269fd2c760e1e54d45fcf73009e2d1944 100644 (file)
@@ -172,8 +172,12 @@ describe('compile', () => {
       })
 
       test('no expression', async () => {
-        const code = await compile(`<div v-text></div>`)
+        const onError = vi.fn()
+        const code = await compile(`<div v-text></div>`, { onError })
         expect(code).matchSnapshot()
+        expect(onError.mock.calls).toMatchObject([
+          [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }],
+        ])
       })
     })
 
index f9a922b3310ecdcbf419315e12b6262d9e4c8f2a..4e40a206d6d9928c1fee85c409b006302b1ab55e 100644 (file)
@@ -2,18 +2,19 @@ import {
   type CodegenResult,
   type CompilerOptions as BaseCompilerOptions,
   type RootNode,
-  type DirectiveTransform,
   parse,
   defaultOnError,
   createCompilerError,
   ErrorCodes,
 } from '@vue/compiler-dom'
 import { extend, isString } from '@vue/shared'
-import { NodeTransform, transform } from './transform'
+import { DirectiveTransform, NodeTransform, transform } from './transform'
 import { generate } from './generate'
 import { HackOptions } from './hack'
 import { transformOnce } from './transforms/vOnce'
 import { transformElement } from './transforms/transformElement'
+import { transformVHtml } from './transforms/vHtml'
+import { transformVText } from './transforms/vText'
 
 export type CompilerOptions = HackOptions<BaseCompilerOptions>
 
@@ -85,5 +86,11 @@ export type TransformPreset = [
 export function getBaseTransformPreset(
   prefixIdentifiers?: boolean,
 ): TransformPreset {
-  return [[transformOnce, transformElement], {}]
+  return [
+    [transformOnce, transformElement],
+    {
+      html: transformVHtml,
+      text: transformVText,
+    },
+  ]
 }
index 6b74dceb66283243667f3fe15413fdda5d067fcf..a2fb6cb5a33bd56e57e97117468af3d3fcf9c4b9 100644 (file)
@@ -1,9 +1,15 @@
 import type { Prettify } from '@vue/shared'
-import type { NodeTransform } from './transform'
+import type { DirectiveTransform, NodeTransform } from './transform'
 
 type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> &
   Pick<U, Extract<keyof U, keyof T>>
 
 export type HackOptions<T> = Prettify<
-  Overwrite<T, { nodeTransforms?: NodeTransform[] }>
+  Overwrite<
+    T,
+    {
+      nodeTransforms?: NodeTransform[]
+      directiveTransforms?: Record<string, DirectiveTransform | undefined>
+    }
+  >
 >
index bfb3d732f720fa39696ad3665a222972285e4758..78f83f94976bea2624ca6442254a1897854b0a37 100644 (file)
@@ -10,6 +10,7 @@ import {
   NodeTypes,
   defaultOnError,
   defaultOnWarn,
+  DirectiveNode,
 } from '@vue/compiler-dom'
 import { EMPTY_OBJ, NOOP, isArray } from '@vue/shared'
 import {
@@ -26,6 +27,15 @@ export type NodeTransform = (
   context: TransformContext<RootNode | TemplateChildNode>,
 ) => void | (() => void) | (() => void)[]
 
+export type DirectiveTransform = (
+  dir: DirectiveNode,
+  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<BaseTransformOptions>
 
 export interface TransformContext<T extends AllNode = AllNode> {
index b205bf177076da8fc546a5557bea1f8b71097f73..00537baa5a4520f025f123d46fb03b920d1ecec8 100644 (file)
@@ -5,8 +5,6 @@ import {
   NodeTypes,
   ErrorCodes,
   createCompilerError,
-  DOMErrorCodes,
-  createDOMCompilerError,
   ElementTypes,
 } from '@vue/compiler-dom'
 import { isVoidTag } from '@vue/shared'
@@ -28,13 +26,18 @@ export const transformElement: NodeTransform = (node, ctx) => {
     }
 
     const { tag, props } = node
+    const isComponent = node.tagType === ElementTypes.COMPONENT
 
     ctx.template += `<${tag}`
-    props.forEach((prop) =>
-      transformProp(prop, ctx as TransformContext<ElementNode>),
-    )
-    ctx.template += `>`
-    ctx.template += ctx.childrenTemplate.join('')
+    if (props.length) {
+      buildProps(
+        node,
+        ctx as TransformContext<ElementNode>,
+        undefined,
+        isComponent,
+      )
+    }
+    ctx.template += `>` + ctx.childrenTemplate.join('')
 
     // TODO remove unnecessary close tag, e.g. if it's the last element of the template
     if (!isVoidTag(tag)) {
@@ -43,22 +46,35 @@ export const transformElement: NodeTransform = (node, ctx) => {
   }
 }
 
+function buildProps(
+  node: ElementNode,
+  context: TransformContext<ElementNode>,
+  props: ElementNode['props'] = node.props,
+  isComponent: boolean,
+) {
+  for (const prop of props) {
+    transformProp(prop, node, context)
+  }
+}
+
 function transformProp(
-  node: DirectiveNode | AttributeNode,
-  ctx: TransformContext<ElementNode>,
+  prop: DirectiveNode | AttributeNode,
+  node: ElementNode,
+  context: TransformContext<ElementNode>,
 ): void {
-  const { name } = node
+  const { name } = prop
 
-  if (node.type === NodeTypes.ATTRIBUTE) {
-    if (node.value) {
-      ctx.template += ` ${name}="${node.value.content}"`
-    } else {
-      ctx.template += ` ${name}`
-    }
+  if (prop.type === NodeTypes.ATTRIBUTE) {
+    context.template += ` ${name}`
+    if (prop.value) context.template += `="${prop.value.content}"`
     return
   }
 
-  const { arg, exp, loc, modifiers } = node
+  const { arg, exp, loc, modifiers } = prop
+  const directiveTransform = context.options.directiveTransforms[name]
+  if (directiveTransform) {
+    directiveTransform(prop, node, context)
+  }
 
   switch (name) {
     case 'bind': {
@@ -66,7 +82,7 @@ function transformProp(
         !exp ||
         (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
       ) {
-        ctx.options.onError(
+        context.options.onError(
           createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
         )
         return
@@ -81,13 +97,13 @@ function transformProp(
         return
       }
 
-      ctx.registerEffect(
+      context.registerEffect(
         [exp],
         [
           {
             type: IRNodeTypes.SET_PROP,
-            loc: node.loc,
-            element: ctx.reference(),
+            loc: prop.loc,
+            element: context.reference(),
             name: arg,
             value: exp,
           },
@@ -97,7 +113,7 @@ function transformProp(
     }
     case 'on': {
       if (!exp && !modifiers.length) {
-        ctx.options.onError(
+        context.options.onError(
           createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc),
         )
         return
@@ -112,13 +128,13 @@ function transformProp(
         return
       }
 
-      ctx.registerEffect(
+      context.registerEffect(
         [exp],
         [
           {
             type: IRNodeTypes.SET_EVENT,
-            loc: node.loc,
-            element: ctx.reference(),
+            loc: prop.loc,
+            element: context.reference(),
             name: arg,
             value: exp,
             modifiers,
@@ -127,49 +143,5 @@ function transformProp(
       )
       break
     }
-    case 'html': {
-      if (!exp) {
-        ctx.options.onError(
-          createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc),
-        )
-      }
-      if (ctx.node.children.length) {
-        ctx.options.onError(
-          createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc),
-        )
-        ctx.childrenTemplate.length = 0
-      }
-
-      ctx.registerEffect(
-        [exp],
-        [
-          {
-            type: IRNodeTypes.SET_HTML,
-            loc: node.loc,
-            element: ctx.reference(),
-            value: exp || '""',
-          },
-        ],
-      )
-      break
-    }
-    case 'text': {
-      ctx.registerEffect(
-        [exp],
-        [
-          {
-            type: IRNodeTypes.SET_TEXT,
-            loc: node.loc,
-            element: ctx.reference(),
-            value: exp || '""',
-          },
-        ],
-      )
-      break
-    }
-    case 'cloak': {
-      // do nothing
-      break
-    }
   }
 }
diff --git a/packages/compiler-vapor/src/transforms/vHtml.ts b/packages/compiler-vapor/src/transforms/vHtml.ts
new file mode 100644 (file)
index 0000000..5321f89
--- /dev/null
@@ -0,0 +1,30 @@
+import { IRNodeTypes } from '../ir'
+import { DirectiveTransform } from '../transform'
+import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
+
+export const transformVHtml: DirectiveTransform = (dir, node, context) => {
+  const { exp, loc } = dir
+  if (!exp) {
+    context.options.onError(
+      createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc),
+    )
+  }
+  if (node.children.length) {
+    context.options.onError(
+      createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc),
+    )
+    context.childrenTemplate.length = 0
+  }
+
+  context.registerEffect(
+    [exp],
+    [
+      {
+        type: IRNodeTypes.SET_HTML,
+        loc: dir.loc,
+        element: context.reference(),
+        value: exp || '""',
+      },
+    ],
+  )
+}
diff --git a/packages/compiler-vapor/src/transforms/vText.ts b/packages/compiler-vapor/src/transforms/vText.ts
new file mode 100644 (file)
index 0000000..7a235a7
--- /dev/null
@@ -0,0 +1,30 @@
+import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
+import { DirectiveTransform } from '../transform'
+import { IRNodeTypes } from '../ir'
+
+export const transformVText: DirectiveTransform = (dir, node, context) => {
+  const { exp, loc } = dir
+  if (!exp) {
+    context.options.onError(
+      createDOMCompilerError(DOMErrorCodes.X_V_TEXT_NO_EXPRESSION, loc),
+    )
+  }
+  if (node.children.length) {
+    context.options.onError(
+      createDOMCompilerError(DOMErrorCodes.X_V_TEXT_WITH_CHILDREN, loc),
+    )
+    node.children.length = 0
+  }
+
+  context.registerEffect(
+    [exp],
+    [
+      {
+        type: IRNodeTypes.SET_TEXT,
+        loc: dir.loc,
+        element: context.reference(),
+        value: exp || '""',
+      },
+    ],
+  )
+}