Vue 3:为什么 vue yandex 地图包在具有 defineCustomElement 功能的 vue 3 中不起作用?

Vue 3: Why vue yandex maps package doesn't work in vue 3 with defineCustomElement feature?

我在 vue 3 中有 2 个 vue yandex maps 项目:

第一个项目

Demo 工作 vue yandex 地图的第一个项目。在这个项目包中这样注册:

代码 main.js 其中注册的 vue-yandex-maps components 来自 js 文件:

const { createApp } = require('vue');
import App from './App.vue';
import ymapPlugin from 'vue-yandex-maps/dist/vue-yandex-maps.esm.js';

const app = createApp(App);

app.config.isCustomElement = (tag) => tag.startsWith('y'); // <= This is doesn't work
app.use(ymapPlugin);
app.mount('#app');

代码 MapComponent.vue使用包vue-yandex-maps的地方:

<template>
  <yandex-map :coords="coords">
    <ymap-marker
      marker-id="123"
      :coords="coords"
      :marker-events="['click']"
    ></ymap-marker>
  </yandex-map>
</template>

<script>
export default {
  name: 'MapComponent',
  setup() {
    return {
      coords: [54, 39],
    };
  },
};
</script>

代码 App.vue使用组件的地方MapComponent:

<template>
  <div id="app">
    <MapComponent />
  </div>
</template>

<script>
import MapComponent from './components/MapComponent.vue';

export default {
  name: 'App',
  components: {
    MapComponent,
  },
};
</script>

第二个项目

Demo second project where used new feature defineCustomElement 来自 vue 版本 3.2 并在使用包 vue-yandex-maps:

时收到错误消息

Uncaught TypeError: Cannot read properties of null (reading 'offsetWidth')

代码 main.js 其中注册的 vue-yandex-maps 组件来自 js 文件:

import { defineCustomElement } from './defineCustomElementWithStyles'
import App from './App.ce.vue'
import store from './store'
import router from './router'
import ymapPlugin from 'vue-yandex-maps/dist/vue-yandex-maps.esm.js'

customElements.define(
  'app-root',
  defineCustomElement(App, {
    plugins: [store, router, ymapPlugin],
  })
)

代码 defineCustomElementWithStyles.js:

import { defineCustomElement as VueDefineCustomElement, h, createApp, getCurrentInstance } from 'vue'

const getNearestElementParent = (el) => {
  while (el?.nodeType !== 1 /* ELEMENT */) {
    el = el.parentElement
  }
  return el
}

export const defineCustomElement = (component, { plugins = [] }) =>
  VueDefineCustomElement({
    props: component.props,
    setup(props) {
      const app = createApp()

      // install plugins
      plugins.forEach(app.use)

      app.mixin({
        mounted() {
          const insertStyles = (styles) => {
            if (styles?.length) {
              this.__style = document.createElement('style')
              this.__style.innerText = styles.join().replace(/\n/g, '')
              getNearestElementParent(this.$el).prepend(this.__style)
            }
          }

          // load own styles
          insertStyles(this.$?.type.styles)

          // load styles of child components
          if (this.$options.components) {
            for (const comp of Object.values(this.$options.components)) {
              insertStyles(comp.styles)
            }
          }
        },
        unmounted() {
          this.__style?.remove()
        },
      })

      const inst = getCurrentInstance()
      Object.assign(inst.appContext, app._context)
      Object.assign(inst.provides, app._context.provides)
      console.log({ props })
      return () => h(component, props)
    },
  })

代码 Home.ce.vue 使用组件 MapComponent:

<script>
export default {
  name: 'Home',
}
</script>

<script setup>
import HelloWorld from '@/components/HelloWorld.ce.vue'
import MapComponent from '@/components/MapComponent.ce.vue'
</script>

<template>
  <h2>Home</h2>
  <HelloWorld msg="hello world" />
  <MapComponent />
</template>

代码 MapComponent.ce.vue 使用包 vue-yandex-maps:

<template>
  <yandex-map :coords="coords">
    <ymap-marker marker-id="123" :coords="coords" :marker-events="['click']"></ymap-marker>
  </yandex-map>
</template>

<script>
export default {
  name: 'MapComponent',
  setup() {
    return {
      coords: [54, 39],
    }
  },
}
</script>

<style>
.ymap-container {
  height: 600px;
}
</style>

问题

我在 second project 中使用 vue-yandex-mapsdefineCustomElement?

时出现 错误

vue-yandex-maps renders a map container with a randomly generated ID that is passed to the ymaps.Map constructor,稍后使用它来查询元素的 document。不幸的是,地图容器在 app-root 自定义元素的 Shadow DOM 内呈现,这对 document 查询是隐藏的。 document.querySelector() 因此 returns nullymaps.Map 代码试图通过 null 引用获取容器的大小,导致您观察到的错误。

您必须自己修补 vue-yandex-maps,或提交 GitHub issue to request a feature change, where you could pass in the map container element (from the custom element's Shadow DOM) instead of an ID. It looks like ymaps.Map already accepts either an element or a string ID,因此无需进行其他更改。