]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(runtime-vapor): dynamic components work with v-html and v-text edison/fix/vhtmlAndvTextWithDynamicComponent 13496/head
authordaiwei <daiwei521@126.com>
Thu, 19 Jun 2025 03:23:54 +0000 (11:23 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 19 Jun 2025 03:23:54 +0000 (11:23 +0800)
packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/vHtml.spec.ts
packages/compiler-vapor/__tests__/transforms/vText.spec.ts
packages/compiler-vapor/src/generators/component.ts
packages/compiler-vapor/src/generators/html.ts
packages/compiler-vapor/src/generators/text.ts
packages/compiler-vapor/src/ir/index.ts
packages/compiler-vapor/src/transforms/utils.ts
packages/runtime-vapor/src/dom/prop.ts
packages/runtime-vapor/src/index.ts

index ecf886d7cbbdfa03d6b9cd631d002028a899bd47..f78e395a75af7491738cce6fd18849a1e5ce7256 100644 (file)
@@ -32,3 +32,13 @@ export function render(_ctx) {
   return n0
 }"
 `;
+
+exports[`v-html > work with dynamic component 1`] = `
+"import { createDynamicComponent as _createDynamicComponent, setHtml as _setHtml, renderEffect as _renderEffect } from 'vue';
+
+export function render(_ctx) {
+  const n0 = _createDynamicComponent(() => ('button'), null, null, true)
+  _renderEffect(() => _setHtml(n0.nodes, _ctx.foo))
+  return n0
+}"
+`;
index 9a3b88acba308231b58f1d2eb1b31ed4de2adae4..b17692a9812130bf53482d8b7d2fba8b39c670b5 100644 (file)
@@ -33,3 +33,13 @@ export function render(_ctx) {
   return n0
 }"
 `;
+
+exports[`v-text > work with dynamic component 1`] = `
+"import { createDynamicComponent as _createDynamicComponent, toDisplayString as _toDisplayString, setElementText as _setElementText, renderEffect as _renderEffect } from 'vue';
+
+export function render(_ctx) {
+  const n0 = _createDynamicComponent(() => ('button'), null, null, true)
+  _renderEffect(() => _setElementText(n0.nodes, _toDisplayString(_ctx.foo), true))
+  return n0
+}"
+`;
index 0de0b6abca6a055ae1f9d4249148906107a108d2..0fcff932c18f6c925b369964f1efe70504bd9997 100644 (file)
@@ -54,6 +54,14 @@ describe('v-html', () => {
     expect(code).matchSnapshot()
   })
 
+  test('work with dynamic component', () => {
+    const { code } = compileWithVHtml(
+      `<component :is="'button'" v-html="foo"/>`,
+    )
+    expect(code).matchSnapshot()
+    expect(code).contains('setHtml(n0.nodes, _ctx.foo))')
+  })
+
   test('should raise error and ignore children when v-html is present', () => {
     const onError = vi.fn()
     const { code, ir, helpers } = compileWithVHtml(
index 4f074fee87e16b8a2b1d15e468028d7893125dcd..e596062a798c71aab51d706924cb1a9e62c8a7cd 100644 (file)
@@ -58,6 +58,16 @@ describe('v-text', () => {
     expect(code).matchSnapshot()
   })
 
+  test('work with dynamic component', () => {
+    const { code } = compileWithVText(
+      `<component :is="'button'" v-text="foo"/>`,
+    )
+    expect(code).matchSnapshot()
+    expect(code).contains(
+      'setElementText(n0.nodes, _toDisplayString(_ctx.foo), true)',
+    )
+  })
+
   test('should raise error and ignore children when v-text is present', () => {
     const onError = vi.fn()
     const { code, ir } = compileWithVText(`<div v-text="test">hello</div>`, {
index 7c232db754be5c6574fd04b8098eeff1e2cb368f..fdbc3415ff4e579b9b339d96b2ada3c64cf4034d 100644 (file)
@@ -60,12 +60,15 @@ export function genCreateComponent(
     [],
   )
 
+  const isDynamicComponent = operation.dynamic && !operation.dynamic.isStatic
+  if (isDynamicComponent) context.block.dynamicComponents.push(operation.id)
+
   return [
     NEWLINE,
     ...inlineHandlers,
     `const n${operation.id} = `,
     ...genCall(
-      operation.dynamic && !operation.dynamic.isStatic
+      isDynamicComponent
         ? helper('createDynamicComponent')
         : operation.asset
           ? helper('createComponentWithFallback')
index 72af699dd03d7bd04a927e725d0cfcc6639ebd06..8fc6d07a4c702f7ee0b46aa1cc2a6b4792b64cf0 100644 (file)
@@ -7,10 +7,21 @@ export function genSetHtml(
   oper: SetHtmlIRNode,
   context: CodegenContext,
 ): CodeFragment[] {
-  const { helper } = context
+  const {
+    helper,
+    block: { dynamicComponents },
+  } = context
+
+  const isDynamicComponent = dynamicComponents.includes(oper.element)
   const { value, element } = oper
   return [
     NEWLINE,
-    ...genCall(helper('setHtml'), `n${element}`, genExpression(value, context)),
+    ...genCall(
+      helper('setHtml'),
+      // if the element is a dynamic component (VaporFragment)
+      // it should set html to the VaporFragment's nodes
+      `n${element}${isDynamicComponent ? '.nodes' : ''}`,
+      genExpression(value, context),
+    ),
   ]
 }
index 89e3167c664dedaf111b8b1f7e1f576cfe363375..1f706ebd1229b2702797dc6deb48bb030115870a 100644 (file)
@@ -9,13 +9,33 @@ export function genSetText(
   oper: SetTextIRNode,
   context: CodegenContext,
 ): CodeFragment[] {
-  const { helper } = context
+  const {
+    helper,
+    block: { dynamicComponents },
+  } = context
   const { element, values, generated, jsx } = oper
   const texts = combineValues(values, context, jsx)
-  return [
-    NEWLINE,
-    ...genCall(helper('setText'), `${generated ? 'x' : 'n'}${element}`, texts),
-  ]
+
+  // if the element is a dynamic component, we need to use `setElementText`
+  // to set the textContent of the VaporFragment's nodes.
+  return dynamicComponents.includes(oper.element)
+    ? [
+        NEWLINE,
+        ...genCall(
+          helper('setElementText'),
+          `n${element}.nodes`,
+          texts,
+          'true', // isConverted
+        ),
+      ]
+    : [
+        NEWLINE,
+        ...genCall(
+          helper('setText'),
+          `${generated ? 'x' : 'n'}${element}`,
+          texts,
+        ),
+      ]
 }
 
 function combineValues(
@@ -40,6 +60,14 @@ export function genGetTextChild(
   oper: GetTextChildIRNode,
   context: CodegenContext,
 ): CodeFragment[] {
+  const {
+    block: { dynamicComponents },
+  } = context
+
+  // if the parent is a dynamic component, don't need to generate a child
+  // because it will use the `setElementText` helper directly.
+  if (dynamicComponents.includes(oper.parent)) return []
+
   return [
     NEWLINE,
     `const x${oper.parent} = ${context.helper('child')}(n${oper.parent})`,
index da636113224c3bc7685b9c4de6d95ddfcd4f623e..dd105b08d52e9eca5a9a10418d373d01eb276769 100644 (file)
@@ -49,6 +49,7 @@ export interface BlockIRNode extends BaseIRNode {
   type: IRNodeTypes.BLOCK
   node: RootNode | TemplateChildNode
   dynamic: IRDynamicInfo
+  dynamicComponents: number[]
   tempId: number
   effect: IREffect[]
   operation: OperationNode[]
index b8e7adc60596dace721318181395ba43abf35c44..222449fe4009f42c535be37b33429e309ae5fb21 100644 (file)
@@ -26,6 +26,7 @@ export const newBlock = (node: BlockIRNode['node']): BlockIRNode => ({
   type: IRNodeTypes.BLOCK,
   node,
   dynamic: newDynamic(),
+  dynamicComponents: [],
   effect: [],
   operation: [],
   returns: [],
index 8c42ad766a51d46d942831a5d81d112db21295e6..04e5767613c282b16a53e6230bd05ccf7dccd7bc 100644 (file)
@@ -185,13 +185,16 @@ export function setText(el: Text & { $txt?: string }, value: string): void {
 }
 
 /**
- * Used by setDynamicProps only, so need to guard with `toDisplayString`
+ * Used by
+ * - setDynamicProps, need to guard with `toDisplayString`
+ * - v-text on dynamic component, value passed here is already converted
  */
 export function setElementText(
   el: Node & { $txt?: string },
   value: unknown,
+  isConverted: boolean = false,
 ): void {
-  if (el.$txt !== (value = toDisplayString(value))) {
+  if (el.$txt !== (value = isConverted ? value : toDisplayString(value))) {
     el.textContent = el.$txt = value as string
   }
 }
index 682532fa4d80aa01a23a948bbb538b049715a9ff..da060e7eb088fc10c5fa8204758e0e8c90d856d0 100644 (file)
@@ -22,6 +22,7 @@ export {
   setProp,
   setDOMProp,
   setDynamicProps,
+  setElementText,
 } from './dom/prop'
 export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
 export { createIf } from './apiCreateIf'