I have 2 projects with vue yandex maps in vue 3:
First project
Demo first project where work vue yandex maps. In this project package registered like this:
Code main.js
where registered vue-yandex-maps components from js
file:
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');
Code MapComponent.vue
where used package 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>
Code App.vue
where used component MapComponent
:
<template>
<div id="app">
<MapComponent />
</div>
</template>
<script>
import MapComponent from './components/MapComponent.vue';
export default {
name: 'App',
components: {
MapComponent,
},
};
</script>
Second project
Demo second project where used new feature defineCustomElement from vue version 3.2 and get error message when use package vue-yandex-maps
:
Uncaught TypeError: Cannot read properties of null (reading 'offsetWidth')
Code main.js
where registered vue-yandex-maps
components from js
file:
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],
})
)
Code 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)
},
})
Code Home.ce.vue
where used component 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>
Code MapComponent.ce.vue
where used package 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>
Question
Where I have error in second project where I use vue-yandex-maps
with defineCustomElement
?
vue-yandex-maps
renders a map container with a randomly generated ID that is passed to the ymaps.Map
constructor, which later uses it to query the document
for the element. Unfortunately, the map container is rendered inside the Shadow DOM of the app-root
custom element, which is hidden from document
queries. The document.querySelector()
thus returns null
, and the ymaps.Map
code tries to get the size of the container via the null
reference, leading to the error you observed.
You would have to patch vue-yandex-maps
yourself, or submit a 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, so no other change would be necessary.