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-maps
和 defineCustomElement
?
时出现 错误
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 null
和 ymaps.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,因此无需进行其他更改。
我在 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-maps
和 defineCustomElement
?
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 null
和 ymaps.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,因此无需进行其他更改。