]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: v-html
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Fri, 24 Nov 2023 06:44:57 +0000 (14:44 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Fri, 24 Nov 2023 06:44:57 +0000 (14:44 +0800)
README.md
packages/compiler-vapor/__tests__/__snapshots__/basic.test.ts.snap
packages/compiler-vapor/__tests__/fixtures/counter.vue
packages/compiler-vapor/src/generate.ts
packages/compiler-vapor/src/ir.ts
packages/compiler-vapor/src/transform.ts
packages/runtime-vapor/src/render.ts
playground/src/App.vue

index 3f7569ed3dc08dc286a6c32c20c63bcbd318b29f..93bfdc129e488e8c80f5051e59c2b3f6c2bdaaba 100644 (file)
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ See the To-do list below or `// TODO` comments in code (`compiler-vapor` and `ru
   - [ ] `v-if` / `v-else` / `v-else-if`
   - [ ] `v-for`
   - [ ] `v-once`
-  - [ ] `v-html`
+  - [x] `v-html`
   - [ ] `v-text`
   - [ ] `v-show`
   - [ ] `v-pre`
index efb1dbb7b3860d957a34f3aa1377a91a28e34ae9..90e3c5a8c8991958c18c7cc31a2b21c73ff70b99 100644 (file)
@@ -3,10 +3,11 @@
 exports[`basic 1`] = `
 "import { defineComponent as _defineComponent } from 'vue'
 import { watchEffect } from 'vue'
-import { template, insert, setText, on } from 'vue/vapor'
-const t0 = template(\`<h1 id=\\"title\\">Counter</h1><p>Count: </p><p>Double: </p><button>Increment</button>\`)
+import { template, insert, setText, on, setHtml } from 'vue/vapor'
+const t0 = template(\`<h1 id=\\"title\\">Counter</h1><p>Count: </p><p>Double: </p><button>Increment</button><div/>\`)
 import { ref, computed } from 'vue'
 
+const html = '<b>HTML</b>'
 
 export default /*#__PURE__*/_defineComponent({
   setup(__props) {
@@ -16,6 +17,7 @@ const double = computed(() => count.value * 2)
 
 const increment = () => count.value++
 
+
 return (() => {
 const root = t0()
 const n1 = document.createTextNode(count.value)
@@ -31,6 +33,9 @@ setText(n3, undefined, double.value)
 watchEffect(() => {
 on(n4, \\"click\\", increment)
 })
+watchEffect(() => {
+setHtml(n5, undefined, html)
+})
 return root
 })();
 }
index 9415168d7dc682754f9c38ea2036e89fe9a9886c..9ef7a97841b7a2715db733a57102ba7f0f8a49d8 100644 (file)
@@ -5,6 +5,8 @@ const count = ref(0)
 const double = computed(() => count.value * 2)
 
 const increment = () => count.value++
+
+const html = '<b>HTML</b>'
 </script>
 
 <template>
@@ -12,4 +14,5 @@ const increment = () => count.value++
   <p>Count: {{ count }}</p>
   <p>Double: {{ double }}</p>
   <button @click="increment">Increment</button>
+  <div v-html="html" />
 </template>
index 0a64b732d7617c165c396e6f68c8a554c134ad0e..c24040f725252ef601668af8ecc44105249a40a8 100644 (file)
@@ -1,6 +1,9 @@
 import type { CodegenOptions, CodegenResult } from '@vue/compiler-dom'
 import { type DynamicChildren, type RootIRNode, IRNodeTypes } from './ir'
 
+// remove when stable
+function checkNever(x: never): void {}
+
 // IR -> JS codegen
 export function generate(
   ir: RootIRNode,
@@ -74,6 +77,13 @@ export function generate(
           vaporHelpers.add('on')
           break
         }
+        case IRNodeTypes.SET_HTML: {
+          scope += `setHtml(n${effect.element}, undefined, ${expr})\n`
+          vaporHelpers.add('setHtml')
+          break
+        }
+        default:
+          checkNever(effect)
       }
     }
     scope += '})\n'
index c515797b9d5d0bcd1d31da939888ab59b2c8c8b6..9e45b062bf2636efd40dd9fc188411e23ff38151 100644 (file)
@@ -6,6 +6,7 @@ export const enum IRNodeTypes {
   SET_PROP,
   SET_TEXT,
   SET_EVENT,
+  SET_HTML,
 
   INSERT_NODE,
   TEXT_NODE,
@@ -48,6 +49,17 @@ export interface SetEventIRNode extends IRNode {
   name: string
 }
 
+export interface SetHtmlIRNode extends IRNode {
+  type: IRNodeTypes.SET_HTML
+  element: number
+}
+
+export type EffectNode =
+  | SetPropIRNode
+  | SetTextIRNode
+  | SetEventIRNode
+  | SetHtmlIRNode
+
 export interface TextNodeIRNode extends IRNode {
   type: IRNodeTypes.TEXT_NODE
   id: number
@@ -61,7 +73,6 @@ export interface InsertNodeIRNode extends IRNode {
   anchor: number | 'first' | 'last'
 }
 
-export type EffectNode = SetPropIRNode | SetTextIRNode | SetEventIRNode
 export type OprationNode = TextNodeIRNode | InsertNodeIRNode
 
 export interface DynamicChild {
index e2fe911a582aa9163a6c3d4d884862df392eee15..8436092c7c29cf2fe89a9df4faa05dd7d2ebbbda 100644 (file)
@@ -134,20 +134,26 @@ export function transform(
     vaporHelpers: new Set([]),
   }
   const ctx = createRootContext(ir, root, options)
-  transformChildren(ctx)
-  ctx.registerTemplate()
+
+  // TODO: transform presets, see packages/compiler-core/src/transforms
+  transformChildren(ctx, true)
   ir.children = ctx.children
 
   return ir
 }
 
-function transformChildren(ctx: TransformContext<RootNode | ElementNode>) {
+function transformChildren(
+  ctx: TransformContext<RootNode | ElementNode>,
+  root?: boolean,
+) {
   const {
     node: { children },
   } = ctx
   let index = 0
   children.forEach((child, i) => walkNode(child, i))
 
+  if (root) ctx.registerTemplate()
+
   function walkNode(node: TemplateChildNode, i: number) {
     const child = createContext(node, ctx, index)
     const isFirst = i === 0
@@ -298,30 +304,56 @@ function transformProp(
   } else if (node.exp.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)) {
     // TODO: CompoundExpressionNode: :foo="count + 1"
     return
-  } else if (!node.arg) {
-    // TODO support v-bind="{}"
-    return
-  } else if (node.arg.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)) {
-    // TODO support :[foo]="bar"
-    return
   }
 
-  const expr = processExpression(ctx, node.exp.content)
   ctx.store = true
-  if (name === 'bind') {
-    ctx.registerEffect(expr, {
-      type: IRNodeTypes.SET_PROP,
-      loc: node.loc,
-      element: ctx.getElementId(),
-      name: node.arg.content,
-    })
-  } else if (name === 'on') {
-    ctx.registerEffect(expr, {
-      type: IRNodeTypes.SET_EVENT,
-      loc: node.loc,
-      element: ctx.getElementId(),
-      name: node.arg.content,
-    })
+  const expr = processExpression(ctx, node.exp.content)
+  switch (name) {
+    case 'bind': {
+      if (!node.arg) {
+        // TODO support v-bind="{}"
+        return
+      } else if (
+        node.arg.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)
+      ) {
+        // TODO support :[foo]="bar"
+        return
+      }
+
+      ctx.registerEffect(expr, {
+        type: IRNodeTypes.SET_PROP,
+        loc: node.loc,
+        element: ctx.getElementId(),
+        name: node.arg.content,
+      })
+      break
+    }
+    case 'on': {
+      if (!node.arg) {
+        // TODO support v-on="{}"
+        return
+      } else if (
+        node.arg.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)
+      ) {
+        // TODO support @[foo]="bar"
+        return
+      }
+
+      ctx.registerEffect(expr, {
+        type: IRNodeTypes.SET_EVENT,
+        loc: node.loc,
+        element: ctx.getElementId(),
+        name: node.arg.content,
+      })
+      break
+    }
+    case 'html':
+      ctx.registerEffect(expr, {
+        type: IRNodeTypes.SET_HTML,
+        loc: node.loc,
+        element: ctx.getElementId(),
+      })
+      break
   }
 }
 
index aad51f3ab6b4abfba55ce51050addb3db0b78410..b155f8b091168758576f05a1a4b5eab1eadd0dd3 100644 (file)
@@ -74,6 +74,12 @@ export function setText(el: Element, oldVal: any, newVal: any) {
   }
 }
 
+export function setHtml(el: Element, oldVal: any, newVal: any) {
+  if (newVal !== oldVal) {
+    el.innerHTML = newVal
+  }
+}
+
 export function setClass(el: Element, oldVal: any, newVal: any) {
   if ((newVal = normalizeClass(newVal)) !== oldVal && (newVal || oldVal)) {
     el.className = newVal
index 9497d5bdb78999918f5fb3e8485793469cd120fe..cb0887d99ee31588416fdf3a7ce58199be505698 100644 (file)
@@ -3,6 +3,7 @@ import { ref, computed } from 'vue'
 
 const count = ref(0)
 const double = computed(() => count.value * 2)
+const html = computed(() => `<button>HTML! ${count.value}</button>`)
 
 const inc = () => count.value++
 const dec = () => count.value--
@@ -15,6 +16,8 @@ globalThis.double = double
 globalThis.inc = inc
 // @ts-expect-error
 globalThis.dec = dec
+// @ts-expect-error
+globalThis.html = html
 </script>
 
 <template>
@@ -26,6 +29,7 @@ globalThis.dec = dec
       <button @click="inc">inc</button>
       <button @click="dec">dec</button>
     </div>
+    <div v-html="html" />
   </div>
 </template>