Gutenberg basiert ja (leider) auf React. Ich programmiere aber schon seit Jahren viel lieber mit Vue.js 2. Zwar habe ich mich damit abgefunden Blöcke für den Block Editor aka Gutenberg in React zu schreiben (wobei man nur sehr begrenzt eigentlich React verwendet, eher noch JSX), aber trotzdem verwende ich für einige Blöcke Vue.js. Im Fronend kein Problem, aber eine Vorschau im Block Editor angezeigt zu bekommen, inkl. Änderungen an den Attributen, war eine echte Herausforderung.
I.d.R. wird Vue.js 2 für die Komponenten einmal beim Laden der Seite initialisiert:
new Vue({
el: document.querySelector('.vue'),
})
Code-Sprache: CSS (css)
Wird aber, wie im Block Editor üblich, das HTML ständig ausgetauscht, muss auch ständig Vue neu initialisiert werden. Mein Problem: Ich weiß nicht genau wann! Jedes Mal, wenn die edit
-Funktion des Blocks ausgeführt wird bedeutet dies massive Performanceprobleme.
Längere Zeit hatte ich deswegen Vuera im Einsatz um Vue in React zu nutzen. So richtig rund lief das aber bei mir nie. Vor allem nicht, wenn der Block serverseitig mit ServerSideRender
im Editor gerendert wird. Hier gibt es keinen Hinweis, wann das Laden abgeschlossen worden ist und Vue neu initialisiert werden müsste.
Web Components to the rescue
Seit einigen Wochen habe ich Web Components für mich entdeckt. Dabei werden die Benutzerdefinierten Elemente = Custom Elements im window
Interface registriert und immer, wenn der Browser ein definiertes Element erkennt, z.B. <vue-wrapper>,
wird es initialisiert. Einmal registriert, muss ich mich also nicht darum kümmern, Vue.js 2 zum richtigen Zeitpunkt erneut zu initialisieren.
Ich erstelle mir also ein einfaches Custom Element, das als einzige Aufgabe hat, beim Verbinden über den connectedCallback
mit dem DOM, Vue für dieses Element el
über thi
s zu initialisieren.
import Vue from 'vue';
import MyComponent from './my-component';
Vue.component('my-component', MyComponent); // Component global registrieren
class VueWrapper extends HTMLElement {
connectedCallback() {
new Vue({
el: this,
});
}
}
window.customElements.define('vue-wrapper', VueWrapper );
Code-Sprache: JavaScript (javascript)
Im HTML muss ich dann nur noch meinen Vue Componen mit dem vue-wrapper
umschließen:
<vue-wrapper>
<my-component></my-component>
</vue-wrapper>
Code-Sprache: HTML, XML (xml)
Immer wenn der Browser im DOM ein neues <vue-wrapper>
Custom Element erkennt, wird er Vue für euch zuverlässig initialisieren.
Vue.js Component als Web Component
Ich bin sogar noch einen Schritt weiter gegangen und habe meinen Vue.js 2 Component als Web Component bzw. Custom Element umgesetzt. Dafür gibt es aktuell zwei NPM Packages:
- @vue/web-component-wrapper: Die offizielle Implementation von Vue.js, welche allerdings schon seit zwei Jahren (Stand Januar 2021) keine Liebe mehr abgekommen hat. Probleme gibt es hier bei Objekten und Arrays als Props.
- vue-custom-element: Meine Empfehlung aktuell, vor allem wenn man Objekte oder Arrays als Props übergeben möchte. Bekommt mehr Liebe ab.
Mit Vue.js 3 kann man Vue.js-Components als Web Components direkt erstellen. Dafür müsst ihr die defineCustomElement
Funktion importieren und den Rückgabewert könnt ihr ganz normal über die Browser-API über customElements.define
registrieren.
Hier ein Beispiel aus der Dokumentation von Vue 3:
import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
// normal Vue component options here
props: {},
emits: {},
template: `...`,
// defineCustomElement only: CSS to be injected into shadow root
styles: [`/* inlined css */`]
})
// Register the custom element.
// After registration, all `<my-vue-element>` tags
// on the page will be upgraded.
customElements.define('my-vue-element', MyVueElement)
// You can also programmatically instantiate the element:
// (can only be done after registration)
document.body.appendChild(
new MyVueElement({
// initial props (optional)
})
)
Code-Sprache: JavaScript (javascript)
Anschließend könnt ihr den Web Component einfach ganz normal im DOM nutzen:
<my-vue-element></my-vue-element>
Code-Sprache: HTML, XML (xml)
Sein Vue Component registriert man dann wie folgt:
import Vue from 'vue';
import vueCustomElement from 'vue-custom-element'
import MyComponent from './my-component';
Vue.use(vueCustomElement);
Vue.customElement('my-component', MyComponent);
Code-Sprache: JavaScript (javascript)
Jetzt benötigt man auch keinen Wrapper mehr, sondern kann das Custom Element direkt verwenden:
<my-component></my-component>
Code-Sprache: HTML, XML (xml)
Objekte & Arrays als Props
Möchte man Daten an seinen Component übergeben, ist das bei String, Number und Boolean kein Problem:
<my-component :status="true" :count="1" :text="My Component"></my-component>
Code-Sprache: HTML, XML (xml)
Nur Objekte und Parameter machen hier Probleme. Es wird empfohlen diese direkt über JavaScript zu übergeben:
document.querySelector('my-component').data = [
...
];
Code-Sprache: JavaScript (javascript)
Schöner wäre aber wie bei Vue.js üblich:
<my-component :data="[ ... ]"></my-component>
Code-Sprache: HTML, XML (xml)
Damit es so funktioniert, muss man das Array/Objekt als JSON-String übergeben und im connectedCallback
in ein echtes Array oder Objekt umwandeln:
import Vue from 'vue';
import vueCustomElement from 'vue-custom-element'
import MyComponent from './my-component';
Vue.use(vueCustomElement);
Vue.customElement('my-component', MyComponent, {
connectedCallback() {
this.data = this.hasAttribute(':data') ? JSON.parse(this.getAttribute(':data')) : undefined;
}
});
Code-Sprache: JavaScript (javascript)
Ich konnte als Attribute nicht den selber Namen wie für den Prop verwenden. Deswegen habe ich als Attribute :data
gewählt, als Prop data
ohne :
Das kommt der Vue-Syntax sehr nah.
In JSX wird aber kein :
akzeptiert, hier könnte man stattdessen das Attribut mit data prefixen, z.B.
this.mydata = this.hasAttribute('data-mydata') ? JSON.parse(this.getAttribute('data-mydata') : undefined;
Code-Sprache: JavaScript (javascript)
Fazit
JavaScript über Custom Elements initialisieren funktioniert nicht nur für Vue.js, sondern grundsätzlich für viele JavaScript-Frameworks und andere Libraries überall wo man ein Element für die Initialisierung übergeben muss.
Danke für den Beitrag. Werde künftig einige Zeit mit Gutenberg verbringen und es ist gut zu wissen, dass ich dafür weiter mit Vue.js arbeiten kann.
Als kleines Update: Das offizielle Package von Vue (@vue/web-component-wrapper) wurde im Februar zuletzt geupdated. Vielleicht laufen damit dann auch die bisherigen Versäumnisse besser.
Danke für den Hinweis! Ich konnte leider nicht herausfinden, ob die Übergabe von Arrays und Objects jetzt auch unterstützt wird?
Ich habe mich mittlerweile komplett von meinem Vue-Componenten in WordPress verabschiedet und setzte voll auf native Web Components.
Ich kann dir Vue 3 wärmstens empfehlen, damit benötigst du keine externen Pakete mehr um aus deinen Vue Components, Web Components zu machen: https://vuejs.org/guide/extras/web-components.html#building-custom-elements-with-vue. Ggf. kannst du dadurch auch nochmal ein paar KBs sparen.
Hallo Chris,
stimmt, ich habe mal ein Beispiel für Vue.js 3 ergänzt.
Mittlerweile bin ich aber fürs Frontend komplett auf Web Components umgestiegen und nutze dort selber kein Vue.js mehr.