]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: transformElement
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 2 Dec 2023 08:59:43 +0000 (16:59 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 2 Dec 2023 08:59:43 +0000 (16:59 +0800)
packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
packages/compiler-vapor/src/compile.ts
packages/compiler-vapor/src/transform.ts
packages/compiler-vapor/src/transforms/transformElement.ts [new file with mode: 0644]

index 515fc539b9601003bde2754bf62f5560d45ec4ef..232189808eb5a8762758abbf5c67c5c94af05a41 100644 (file)
@@ -136,38 +136,38 @@ export function render(_ctx) {
 `;
 
 exports[`compile > directives > v-pre > self-closing v-pre 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, effect as _effect, setAttr as _setAttr, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, effect as _effect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template(\\"<div></div><div><Comp></Comp></div>\\")
   const n0 = t0()
-  const { 1: [n1],} = _children(n0)
-  const n2 = _createTextNode(bar)
-  _append(n1, n2)
+  const { 1: [n2],} = _children(n0)
+  const n1 = _createTextNode(bar)
+  _append(n2, n1)
   _effect(() => {
-    _setAttr(n1, \\"id\\", undefined, foo)
+    _setText(n1, undefined, bar)
   })
   _effect(() => {
-    _setText(n2, undefined, bar)
+    _setAttr(n2, \\"id\\", undefined, foo)
   })
   return n0
 }"
 `;
 
 exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, effect as _effect, setAttr as _setAttr, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, effect as _effect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template(\\"<div :id=\\\\\\"foo\\\\\\"><Comp></Comp>{{ bar }}</div><div><Comp></Comp></div>\\")
   const n0 = t0()
-  const { 1: [n1],} = _children(n0)
-  const n2 = _createTextNode(bar.value)
-  _append(n1, n2)
+  const { 1: [n2],} = _children(n0)
+  const n1 = _createTextNode(bar.value)
+  _append(n2, n1)
   _effect(() => {
-    _setAttr(n1, \\"id\\", undefined, foo.value)
+    _setText(n1, undefined, bar.value)
   })
   _effect(() => {
-    _setText(n2, undefined, bar.value)
+    _setAttr(n2, \\"id\\", undefined, foo.value)
   })
   return n0
 }"
@@ -220,23 +220,20 @@ export function render(_ctx) {
 `;
 
 exports[`compile > dynamic root nodes and interpolation 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, effect as _effect, on as _on, setAttr as _setAttr, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, effect as _effect, setText as _setText, on as _on, setAttr as _setAttr } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template(\\"<button>foo<!>foo</button>\\")
   const n0 = t0()
-  const { 0: [n1, { 1: [n5],}],} = _children(n0)
+  const { 0: [n4, { 1: [n5],}],} = _children(n0)
+  const n1 = _createTextNode(count)
   const n2 = _createTextNode(count)
   const n3 = _createTextNode(count)
-  const n4 = _createTextNode(count)
-  _prepend(n1, n2)
-  _insert(n3, n1, n5)
-  _append(n1, n4)
+  _prepend(n4, n1)
+  _insert(n2, n4, n5)
+  _append(n4, n3)
   _effect(() => {
-    _on(n1, \\"click\\", handleClick)
-  })
-  _effect(() => {
-    _setAttr(n1, \\"id\\", undefined, count)
+    _setText(n1, undefined, count)
   })
   _effect(() => {
     _setText(n2, undefined, count)
@@ -245,7 +242,10 @@ export function render(_ctx) {
     _setText(n3, undefined, count)
   })
   _effect(() => {
-    _setText(n4, undefined, count)
+    _on(n4, \\"click\\", handleClick)
+  })
+  _effect(() => {
+    _setAttr(n4, \\"id\\", undefined, count)
   })
   return n0
 }"
index 1892d4974c220d36738595651ba2b8a1c0c62c45..f9a922b3310ecdcbf419315e12b6262d9e4c8f2a 100644 (file)
@@ -11,8 +11,9 @@ import {
 import { extend, isString } from '@vue/shared'
 import { NodeTransform, transform } from './transform'
 import { generate } from './generate'
-import { transformOnce } from './transforms/vOnce'
 import { HackOptions } from './hack'
+import { transformOnce } from './transforms/vOnce'
+import { transformElement } from './transforms/transformElement'
 
 export type CompilerOptions = HackOptions<BaseCompilerOptions>
 
@@ -84,5 +85,5 @@ export type TransformPreset = [
 export function getBaseTransformPreset(
   prefixIdentifiers?: boolean,
 ): TransformPreset {
-  return [[transformOnce], {}]
+  return [[transformOnce, transformElement], {}]
 }
index 9dfe6a15c41bde2ac93f495eda0cc1e4cf6f3d66..bfb3d732f720fa39696ad3665a222972285e4758 100644 (file)
@@ -2,22 +2,16 @@ import {
   type RootNode,
   type TemplateChildNode,
   type ElementNode,
-  type AttributeNode,
   type InterpolationNode,
   type TransformOptions as BaseTransformOptions,
-  type DirectiveNode,
   type ParentNode,
   type AllNode,
   type CompilerCompatOptions,
   NodeTypes,
   defaultOnError,
   defaultOnWarn,
-  ErrorCodes,
-  createCompilerError,
-  DOMErrorCodes,
-  createDOMCompilerError,
 } from '@vue/compiler-dom'
-import { EMPTY_OBJ, NOOP, isArray, isVoidTag } from '@vue/shared'
+import { EMPTY_OBJ, NOOP, isArray } from '@vue/shared'
 import {
   type OperationNode,
   type RootIRNode,
@@ -29,7 +23,7 @@ import type { HackOptions } from './hack'
 
 export type NodeTransform = (
   node: RootNode | TemplateChildNode,
-  context: TransformContext,
+  context: TransformContext<RootNode | TemplateChildNode>,
 ) => void | (() => void) | (() => void)[]
 
 export type TransformOptions = HackOptions<BaseTransformOptions>
@@ -44,6 +38,7 @@ export interface TransformContext<T extends AllNode = AllNode> {
   >
 
   template: string
+  childrenTemplate: string[]
   dynamic: IRDynamicInfo
 
   inVOnce: boolean
@@ -121,6 +116,7 @@ function createRootContext(
     },
 
     template: '',
+    childrenTemplate: [],
     registerTemplate() {
       if (!ctx.template) return -1
 
@@ -164,6 +160,7 @@ function createContext<T extends TemplateChildNode>(
     index,
 
     template: '',
+    childrenTemplate: [],
     dynamic: {
       id: null,
       referenced: false,
@@ -200,9 +197,11 @@ export function transform(
   }
 
   const ctx = createRootContext(ir, root, options)
-
-  // TODO: transform presets, see packages/compiler-core/src/transforms
   transformNode(ctx)
+
+  if (ctx.node.type === NodeTypes.ROOT) {
+    ctx.registerTemplate()
+  }
   if (ir.template.length === 0) {
     ir.template.push({
       type: IRNodeTypes.FRAGMENT_FACTORY,
@@ -222,8 +221,7 @@ function transformNode(
   const { nodeTransforms } = context.options
   const exitFns = []
   for (const nodeTransform of nodeTransforms) {
-    // TODO nodeTransform type
-    const onExit = nodeTransform(node, context as any)
+    const onExit = nodeTransform(node, context)
     if (onExit) {
       if (isArray(onExit)) {
         exitFns.push(...onExit)
@@ -240,18 +238,14 @@ function transformNode(
     }
   }
 
-  if (node.type === NodeTypes.ROOT) {
-    transformChildren(context as TransformContext<RootNode>)
-    return
-  }
-
-  const parentChildren = context.parent!.node.children
+  const parentChildren = context.parent ? context.parent.node.children : []
   const isFirst = index === 0
   const isLast = index === parentChildren.length - 1
 
   switch (node.type) {
+    case NodeTypes.ROOT:
     case NodeTypes.ELEMENT: {
-      transformElement(context as TransformContext<ElementNode>)
+      transformChildren(context as TransformContext<RootNode | ElementNode>)
       break
     }
     case NodeTypes.TEXT: {
@@ -289,93 +283,81 @@ function transformNode(
   while (i--) {
     exitFns[i]()
   }
+
+  if (context.node.type === NodeTypes.ROOT)
+    context.template += context.childrenTemplate.join('')
 }
 
 function transformChildren(ctx: TransformContext<RootNode | ElementNode>) {
-  const {
-    node: { children },
-  } = ctx
-  const childrenTemplate: string[] = []
-  children.forEach((child, index) => {
-    const childContext = createContext(child, ctx, index)
+  const { children } = ctx.node
+  let i = 0
+  // const nodeRemoved = () => {
+  //   i--
+  // }
+  for (; i < children.length; i++) {
+    const child = children[i]
+    const childContext = createContext(child, ctx, i)
     transformNode(childContext)
-
-    childrenTemplate.push(childContext.template)
+    ctx.childrenTemplate.push(childContext.template)
     if (
       childContext.dynamic.ghost ||
       childContext.dynamic.referenced ||
       childContext.dynamic.placeholder ||
       Object.keys(childContext.dynamic.children).length
     ) {
-      ctx.dynamic.children[index] = childContext.dynamic
-    }
-  })
-
-  processDynamicChildren()
-  ctx.template += childrenTemplate.join('')
-
-  if (ctx.node.type === NodeTypes.ROOT) ctx.registerTemplate()
-
-  function processDynamicChildren() {
-    let prevChildren: IRDynamicInfo[] = []
-    let hasStatic = false
-    for (let index = 0; index < children.length; index++) {
-      const child = ctx.dynamic.children[index]
-
-      if (!child || !child.ghost) {
-        if (prevChildren.length)
-          if (hasStatic) {
-            childrenTemplate[index - prevChildren.length] = `<!>`
-            const anchor = (prevChildren[0].placeholder = ctx.increaseId())
-
-            ctx.registerOperation({
-              type: IRNodeTypes.INSERT_NODE,
-              loc: ctx.node.loc,
-              element: prevChildren.map((child) => child.id!),
-              parent: ctx.reference(),
-              anchor,
-            })
-          } else {
-            ctx.registerOperation({
-              type: IRNodeTypes.PREPEND_NODE,
-              loc: ctx.node.loc,
-              elements: prevChildren.map((child) => child.id!),
-              parent: ctx.reference(),
-            })
-          }
-        hasStatic = true
-        prevChildren = []
-        continue
-      }
-
-      prevChildren.push(child)
-
-      if (index === children.length - 1) {
-        ctx.registerOperation({
-          type: IRNodeTypes.APPEND_NODE,
-          loc: ctx.node.loc,
-          elements: prevChildren.map((child) => child.id!),
-          parent: ctx.reference(),
-        })
-      }
+      ctx.dynamic.children[i] = childContext.dynamic
     }
   }
+
+  processDynamicChildren(ctx)
 }
 
-function transformElement(ctx: TransformContext<ElementNode>) {
+function processDynamicChildren(ctx: TransformContext<RootNode | ElementNode>) {
   const { node } = ctx
-  const { tag, props, children } = node
 
-  ctx.template += `<${tag}`
+  let prevChildren: IRDynamicInfo[] = []
+  let hasStatic = false
+
+  for (let index = 0; index < node.children.length; index++) {
+    const child = ctx.dynamic.children[index]
+
+    if (!child || !child.ghost) {
+      if (prevChildren.length) {
+        if (hasStatic) {
+          ctx.childrenTemplate[index - prevChildren.length] = `<!>`
+          const anchor = (prevChildren[0].placeholder = ctx.increaseId())
 
-  props.forEach((prop) => transformProp(prop, ctx))
-  ctx.template += `>`
+          ctx.registerOperation({
+            type: IRNodeTypes.INSERT_NODE,
+            loc: node.loc,
+            element: prevChildren.map((child) => child.id!),
+            parent: ctx.reference(),
+            anchor,
+          })
+        } else {
+          ctx.registerOperation({
+            type: IRNodeTypes.PREPEND_NODE,
+            loc: node.loc,
+            elements: prevChildren.map((child) => child.id!),
+            parent: ctx.reference(),
+          })
+        }
+      }
+      hasStatic = true
+      prevChildren = []
+      continue
+    }
 
-  if (children.length) transformChildren(ctx)
+    prevChildren.push(child)
 
-  // TODO remove unnecessary close tag, e.g. if it's the last element of the template
-  if (!isVoidTag(tag)) {
-    ctx.template += `</${tag}>`
+    if (index === node.children.length - 1) {
+      ctx.registerOperation({
+        type: IRNodeTypes.APPEND_NODE,
+        loc: node.loc,
+        elements: prevChildren.map((child) => child.id!),
+        parent: ctx.reference(),
+      })
+    }
   }
 }
 
@@ -424,134 +406,3 @@ function transformInterpolation(
     )
   }
 }
-
-function transformProp(
-  node: DirectiveNode | AttributeNode,
-  ctx: TransformContext<ElementNode>,
-): void {
-  const { name } = node
-
-  if (node.type === NodeTypes.ATTRIBUTE) {
-    if (node.value) {
-      ctx.template += ` ${name}="${node.value.content}"`
-    } else {
-      ctx.template += ` ${name}`
-    }
-    return
-  }
-
-  const { arg, exp, loc, modifiers } = node
-
-  switch (name) {
-    case 'bind': {
-      if (
-        !exp ||
-        (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
-      ) {
-        ctx.options.onError(
-          createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
-        )
-        return
-      }
-
-      if (exp === null) {
-        // TODO: Vue 3.4 supported shorthand syntax
-        // https://github.com/vuejs/core/pull/9451
-        return
-      } else if (!arg) {
-        // TODO support v-bind="{}"
-        return
-      }
-
-      ctx.registerEffect(
-        [exp],
-        [
-          {
-            type: IRNodeTypes.SET_PROP,
-            loc: node.loc,
-            element: ctx.reference(),
-            name: arg,
-            value: exp,
-          },
-        ],
-      )
-      break
-    }
-    case 'on': {
-      if (!exp && !modifiers.length) {
-        ctx.options.onError(
-          createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc),
-        )
-        return
-      }
-
-      if (!arg) {
-        // TODO support v-on="{}"
-        return
-      } else if (exp === undefined) {
-        // TODO: support @foo
-        // https://github.com/vuejs/core/pull/9451
-        return
-      }
-
-      ctx.registerEffect(
-        [exp],
-        [
-          {
-            type: IRNodeTypes.SET_EVENT,
-            loc: node.loc,
-            element: ctx.reference(),
-            name: arg,
-            value: exp,
-            modifiers,
-          },
-        ],
-      )
-      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.node.children.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/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts
new file mode 100644 (file)
index 0000000..b205bf1
--- /dev/null
@@ -0,0 +1,175 @@
+import {
+  type ElementNode,
+  type AttributeNode,
+  type DirectiveNode,
+  NodeTypes,
+  ErrorCodes,
+  createCompilerError,
+  DOMErrorCodes,
+  createDOMCompilerError,
+  ElementTypes,
+} from '@vue/compiler-dom'
+import { isVoidTag } from '@vue/shared'
+import { NodeTransform, TransformContext } from '../transform'
+import { IRNodeTypes } from '../ir'
+
+export const transformElement: NodeTransform = (node, ctx) => {
+  return function postTransformElement() {
+    node = ctx.node
+
+    if (
+      !(
+        node.type === NodeTypes.ELEMENT &&
+        (node.tagType === ElementTypes.ELEMENT ||
+          node.tagType === ElementTypes.COMPONENT)
+      )
+    ) {
+      return
+    }
+
+    const { tag, props } = node
+
+    ctx.template += `<${tag}`
+    props.forEach((prop) =>
+      transformProp(prop, ctx as TransformContext<ElementNode>),
+    )
+    ctx.template += `>`
+    ctx.template += ctx.childrenTemplate.join('')
+
+    // TODO remove unnecessary close tag, e.g. if it's the last element of the template
+    if (!isVoidTag(tag)) {
+      ctx.template += `</${tag}>`
+    }
+  }
+}
+
+function transformProp(
+  node: DirectiveNode | AttributeNode,
+  ctx: TransformContext<ElementNode>,
+): void {
+  const { name } = node
+
+  if (node.type === NodeTypes.ATTRIBUTE) {
+    if (node.value) {
+      ctx.template += ` ${name}="${node.value.content}"`
+    } else {
+      ctx.template += ` ${name}`
+    }
+    return
+  }
+
+  const { arg, exp, loc, modifiers } = node
+
+  switch (name) {
+    case 'bind': {
+      if (
+        !exp ||
+        (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
+      ) {
+        ctx.options.onError(
+          createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
+        )
+        return
+      }
+
+      if (exp === null) {
+        // TODO: Vue 3.4 supported shorthand syntax
+        // https://github.com/vuejs/core/pull/9451
+        return
+      } else if (!arg) {
+        // TODO support v-bind="{}"
+        return
+      }
+
+      ctx.registerEffect(
+        [exp],
+        [
+          {
+            type: IRNodeTypes.SET_PROP,
+            loc: node.loc,
+            element: ctx.reference(),
+            name: arg,
+            value: exp,
+          },
+        ],
+      )
+      break
+    }
+    case 'on': {
+      if (!exp && !modifiers.length) {
+        ctx.options.onError(
+          createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc),
+        )
+        return
+      }
+
+      if (!arg) {
+        // TODO support v-on="{}"
+        return
+      } else if (exp === undefined) {
+        // TODO: support @foo
+        // https://github.com/vuejs/core/pull/9451
+        return
+      }
+
+      ctx.registerEffect(
+        [exp],
+        [
+          {
+            type: IRNodeTypes.SET_EVENT,
+            loc: node.loc,
+            element: ctx.reference(),
+            name: arg,
+            value: exp,
+            modifiers,
+          },
+        ],
+      )
+      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
+    }
+  }
+}