Browse Source

fix(compiler-vapor): normalize vue vnode hook listeners

daiwei 4 days ago
parent
commit
c666f76652

+ 10 - 0
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap

@@ -45,6 +45,16 @@ export function render(_ctx) {
 }"
 `;
 
+exports[`compiler: element transform > component > component vue vnode hooks 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx, $props, $emit, $attrs, $slots) {
+  const _component_Foo = _resolveComponent("Foo")
+  const n0 = _createComponentWithFallback(_component_Foo, { onVnodeMounted: () => _ctx.handleMounted }, null, true)
+  return n0
+}"
+`;
+
 exports[`compiler: element transform > component > do not resolve component from non-script-setup bindings 1`] = `
 "import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
 

+ 26 - 0
packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts

@@ -409,6 +409,32 @@ describe('compiler: element transform', () => {
       })
     })
 
+    test('component vue vnode hooks', () => {
+      const { code, ir } = compileWithElementTransform(
+        `<Foo @vue:mounted="handleMounted" />`,
+        {
+          bindingMetadata: {
+            handleMounted: BindingTypes.SETUP_CONST,
+          },
+        },
+      )
+      expect(code).toMatchSnapshot()
+      expect(code).contains(`onVnodeMounted`)
+      expect(ir.block.dynamic.children[0].operation).toMatchObject({
+        type: IRNodeTypes.CREATE_COMPONENT_NODE,
+        tag: 'Foo',
+        props: [
+          [
+            {
+              key: { content: 'vnode-mounted' },
+              handler: true,
+              values: [{ content: 'handleMounted' }],
+            },
+          ],
+        ],
+      })
+    })
+
     test('v-on expression is a function call', () => {
       const { code, ir } = compileWithElementTransform(
         `<Foo v-on:bar="handleBar($event)" />`,

+ 6 - 0
packages/compiler-vapor/src/transforms/vOn.ts

@@ -34,6 +34,12 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
   }
   arg = resolveExpression(arg!)
 
+  if (arg.isStatic && arg.content.startsWith('vue:')) {
+    arg = extend({}, arg, {
+      content: `vnode-${arg.content.slice(4)}`,
+    })
+  }
+
   const { keyModifiers, nonKeyModifiers, eventOptionModifiers } =
     resolveModifiers(
       arg.isStatic ? `on${arg.content}` : arg,

+ 61 - 0
packages/runtime-vapor/__tests__/hydration.spec.ts

@@ -5592,6 +5592,67 @@ describe('Vapor Mode hydration', () => {
       `)
     })
 
+    test('trigger @vue:mounted for VDOM async component mounted after hydration', async () => {
+      const data = ref({
+        started: false,
+        loaded: false,
+      })
+      const ResolvedComp = defineComponent({
+        setup() {
+          return () =>
+            h('div', { id: 'docsearch' }, [
+              h('button', { type: 'button' }, 'loaded'),
+            ])
+        },
+      })
+      const appCode = `
+        <components.AsyncComp v-if="data.started" @vue:mounted="data.loaded = true" />
+        <div v-if="!data.loaded" id="docsearch">placeholder</div>
+      `
+      const SSRApp = compileVaporComponent(
+        appCode,
+        data,
+        { AsyncComp: ResolvedComp },
+        true,
+      )
+      const html = await VueServerRenderer.renderToString(
+        runtimeDom.createSSRApp(SSRApp),
+      )
+
+      let clientResolve: any
+      const AsyncComp = defineAsyncComponent(
+        () =>
+          new Promise(r => {
+            clientResolve = r
+          }),
+      )
+
+      const App = compileVaporComponent(appCode, data, { AsyncComp })
+
+      const container = document.createElement('div')
+      container.innerHTML = html
+      document.body.appendChild(container)
+      const app = createVaporSSRApp(App)
+      app.use(runtimeVapor.vaporInteropPlugin)
+      app.mount(container)
+
+      expect(container.querySelectorAll('#docsearch')).toHaveLength(1)
+
+      data.value.started = true
+      await nextTick()
+      clientResolve(ResolvedComp)
+      await new Promise(r => setTimeout(r))
+      await nextTick()
+
+      expect(data.value.loaded).toBe(true)
+      expect(container.querySelectorAll('#docsearch')).toHaveLength(1)
+      expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
+        "
+        <!--[--><div id="docsearch"><button type="button">loaded</button></div><!----><!--if--><!--]-->
+        "
+      `)
+    })
+
     describe('suspense', () => {
       describe('VDOM suspense', () => {
         test('hydrate VDOM Suspense vapor async setup updates empty v-for before trailing sibling', async () => {