Vue.js Apps ohne Node, Webpack, npm, Build Tools oder CLI erstellen

Ich liebe Vue.js und für größere Anwendungen ist die Entwicklung mit Node.js gut machbar. Wenn du allerdings eine „normale“ Website erstellen möchtest ist das oft viel Aufwand den du nicht haben möchtest. Aber auch ohne Webpack oder sonstige Build Tools ist das recht einfach möglich!

Wenn du einfach eine kleine oder mittelgroße Vue Anwendung auf deiner Website platzieren willst ist es oft unnötiger Aufwand dafür extra ein Projekt mit der Vue CLI zu erstellen, zu entwickeln und dann den fertigen Build in deine Website zu integrieren. Eine kleine Anwendung auf einer WordPress Seite und so ein Aufwand? Das muss nicht sein.

Auf vielen Webseiten kommt jQuery zum Einsatz. Ich denke, viele wissen gar nicht, dass es genauso einfach ist auch Vue.js dafür verwenden kann.

Vue.js ohne Build Tools & Co.? Die Lösung: Vue SFC Loader

Ich habe eine lange Zeit nur jQuery auf Webseiten eingesetzt, da ich diesen Weg einfach nicht gekannt habe. Nach langer Recherche und viel Ausprobieren und Testen habe ich dann allerdings eine einfach Lösung gefunden, die sehr einfach und zuverlässig funktioniert: Vue3 SFC Loader

Dieses Skript ist in der Lage .vue Dateien zur Laufzeit zu laden – ohne dass eine Node.js Umgebung dazu benötigt wird. Man benötigt also kein Bundler und auch keine Build Tools, die vor der Ausführung ausgeführt werden müssen.

In der Vue.js Doku gibt es zwar auch einen Abschnitt zum Thema „Without Build Tools„. Für ganz kleine Anwendungen mit nur einer Haupt-Component funktioniert das auch. Für größere Anwendungen hat das bei mir leider nicht funktioniert, so dass script-Attribut importmap nicht von allen Browsern supported wird.

Vor- und Nachteile

Ein Vorteil der direkt auf der Hand liegt ist, dass die Komplexität der Umgebung (Vue CLI, Node Server, etc.) stark verringert wird. Außerdem muss man nicht nach jeder Änderung die komplette Anwendung neu „kompilieren“ und nach Production schieben. Für kleinere und mittelgroße Anwendungen und bei der Nutzung eines CMS oder einfach kleinen Websites ist das also eine echte Alternative.

Damit wären wir aber auch direkt bei den Nachteilen: Wenn du eine Single-Page-Application (SPA) erstellen möchtest solltest du weiterhin die Vue CLI nutzen. Generell würde ich es bei größeren, alleinstehenden Anwendung nach wie vor bevorzugen. Mit einem guten CI/CD Workflow ist auch hier dann die Bereitstellung der Anwendung gut machbar.

Vue.js App ohne Build Tools – Beispiel App

Ich habe eine kleine Vue Anwendung gebaut, um möglichst viele verschiedene Funktionen von Vue zu zeigen. Die App besteht aus 2 Komponenten: Main Component und Footer.

Verdiene Geld mit deiner Website oder Blog

Gehen wir die einzelnen Funktionen Schritt für Schritt durch:

Grundgerüst (mit mehreren Vue Components)

Das Grundgerüst besteht aus einer HTML-Datei, einer JS-Datei in der die Vue Components registriert werden und den drei Vue Components.

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue Test</title>
  </head>
  <body>
    <div id="app">
      <vue-main-component></vue-main-component>
      <vue-footer></vue-footer>
    </div>

    <script src="https://unpkg.com/vue@latest"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader"></script>
    <script src="./js/vue-app.js"></script>
  </body>
</html>

Diese JavaScript Datei beinhaltet ein JavaScript Objekt mit Daten und Funktionen, die für den Vue SFC Loader benötigt werden. Daher kannst du diesen Teil einfach übernehmen und nur unten die markierten Vue Components für dein Projekt entsprechend anpassen.

// js/vue-app.js

const { createApp } = Vue;
const { loadModule } = window['vue3-sfc-loader'];

const options = {
  moduleCache: {
    vue: Vue,
  },
  getFile(url) {
    return fetch(url).then((resp) =>
      resp.ok ? resp.text() : Promise.reject(resp)
    );
  },
  addStyle(styleStr) {
    const style = document.createElement('style');
    style.textContent = styleStr;
    const ref = document.head.getElementsByTagName('style')[0] || null;
    document.head.insertBefore(style, ref);
  },
  log(type, ...args) {
    console.log(type, ...args);
  },
};

const app = createApp({
  components: {
    VueMainComponent: Vue.defineAsyncComponent(() =>
      loadModule('js/vue-components/vue-main-component.vue', options)
    ),
    VueFooter: Vue.defineAsyncComponent(() =>
      loadModule('js/vue-components/vue-footer.vue', options)
    ),
  },
}).mount('#app');

Die Footer Komponente beinhaltet zu Beginn auch nur Text. In einem späteren Abschnitt werde ich zeigen, wie man hier Scoped CSS verwenden kann.

<!-- js/vue-components/vue-footer.vue -->

<template>
  <p>This is the footer with custom scoped CSS.</p>
</template>

Main Komponente

In der Main Komponente habe ich die Hauptlogik zusammengefasst. Generell kannst du die Aufteilung machen wie du willst oder auch alles in eine einzige Komponente packen.

<!-- js/vue-components/vue-main-component.vue -->

<template>
  <div>
    <h2>{{ headline }}</h2>
  </div>
</template>

<script>
export default {
  data() {
    return {
      headline: 'Vue Main Component Headline'
    };
  },
  mounted() {
    alert('Vue Main Component mounted');
  },
};
</script>

Sie beinhaltet eine {{ headline }} Variable die einfachen Text anzeigt und einen JavaScript alert(), der ausgeführt wird, sobald die Komponente gemounted ist.

Diese Komponente erweitern wir jetzt in den nächsten Kapiteln um weitere Funktionen.

Events & Methoden

Beispielhaft fügen wir einen neuen Button mit einem Klick-Event hinzu. Wenn man den Button klickt, wird die Funktion doAxiosRequest() ausgeführt. Nicht vom Namen verwirren lassen, im nächsten Schritt fügen wir das Package Axios hinzu.

<!-- js/vue-components/vue-main-component.vue -->

<template>
  <div>
    <h2>{{ headline }}</h2>
    <button @click="doAxiosRequest">Load random dog image</button>
    <br />
    <img :src="dogImage" width="200" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      headline: 'Vue Main Component Headline',
      dogImage: undefined,
    };
  },
  methods: {
    doAxiosRequest: function () {
      this.dogImage = 'https://images.dog.ceo/breeds/pembroke/n02113023_436.jpg';
    },
  },
  mounted() {
    alert('Vue Main Component mounted');
  },
};
</script>

Packages

Auch externe Packages/Module können ohne Build Tools verwendet werden. In vielen Anwendungen wird Axios verwendet um asynchrone Serveranfragen zu stellen. An diesem Beispiel werde ich demonstrieren, wie man solche Packages einbindet.

Vue.js App Beispiel mit zufälligem Hunde Bild

Um ein Package zu laden, müssen wir lediglich den Link in unserer index.html ergänzen:

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue Test</title>
  </head>
  <body>
    <div id="app">
      <vue-main-component></vue-main-component>
      <vue-footer></vue-footer>
    </div>

    <script src="https://unpkg.com/vue@latest"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="./js/vue-app.js"></script>
  </body>
</html>

Dann können wir direkt über die axios Variable auf alle Funktionalitäten zugreifen. In diesem Beispiel laden wir ein zufälliges Hundebild in das img Tag.

<!-- js/vue-components/vue-main-component.vue -->


...
    doAxiosRequest: function () {
      axios.get('https://dog.ceo/api/breeds/image/random').then((response) => {
        this.dogImage = response.data.message;
      });
    },
  },
...

Props (Properties)

Props sind Attribute, die wir beim Einbinden unserer Components übergeben können. Das können feste oder dynamische Attribute sein. Im Beispiel habe ich einfach den statischen Text „Foobar“ übergeben.

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue Test</title>
  </head>
  <body>
    <div id="app" class="container">
      <vue-main-component msg="Foobar"></vue-main-component>
      <vue-footer></vue-footer>
    </div>

    <script src="https://unpkg.com/vue@latest"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="./js/vue-app.js"></script>
  </body>
</html>

In unserer Main Komponente ergänzen wir jetzt einen neuen Button, der beim Klick die clickButton() Funktion aufruft. Außerdem müssen wir noch die Property als String registrieren. In der clickButton() Funktion können wir dann mit this.msg den Wert abrufen.

<!-- js/vue-components/vue-main-component.vue -->

<template>
  <div>
    <h2>{{ headline }}</h2>
    <button @click="clickButton">Show the msg property!</button>
    <button @click="doAxiosRequest">Load random dog image</button>
    <br />
    <img :src="dogImage" width="200" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      headline: 'Vue Main Component Headline',
      dogImage: undefined,
    };
  },
  props: {
    msg: String,
  },
  methods: {
    clickButton: function () {
      alert('msg property: ' + this.msg);
    },
    doAxiosRequest: function () {
      axios.get('https://dog.ceo/api/breeds/image/random').then((response) => {
        this.dogImage = response.data.message;
      });
    },
  },
  mounted() {
    alert('Vue Main Component mounted');
  },
};
</script>

Scoped CSS

Mit Scoped CSS können CSS-Eigenschaften nur für die aktuelle Komponente definiert werden. Dazu muss der CSS style Tag zusätzlich das Attribut scoped erhalten.

<!-- js/vue-components/vue-footer.vue -->

<template>
  <p>This is the footer with custom scoped CSS.</p>
</template>

<style scoped>
* {
  color: red;
}
</style>

Kompletter Code

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue Test</title>
  </head>
  <body>
    <div id="app">
      <vue-main-component msg="Foobar"></vue-main-component>
      <vue-footer></vue-footer>
    </div>

    <script src="https://unpkg.com/vue@latest"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="./js/vue-app.js"></script>
  </body>
</html>
// js/vue-app.js

const { createApp } = Vue;
const { loadModule } = window['vue3-sfc-loader'];

const options = {
  moduleCache: {
    vue: Vue,
  },
  getFile(url) {
    return fetch(url).then((resp) =>
      resp.ok ? resp.text() : Promise.reject(resp)
    );
  },
  addStyle(styleStr) {
    const style = document.createElement('style');
    style.textContent = styleStr;
    const ref = document.head.getElementsByTagName('style')[0] || null;
    document.head.insertBefore(style, ref);
  },
  log(type, ...args) {
    console.log(type, ...args);
  },
};

const app = createApp({
  components: {
    VueMainComponent: Vue.defineAsyncComponent(() =>
      loadModule('js/vue-components/vue-main-component.vue', options)
    ),
    VueFooter: Vue.defineAsyncComponent(() =>
      loadModule('js/vue-components/vue-footer.vue', options)
    ),
  },
}).mount('#app');
<!-- vue-components/vue-main-component.vue -->

<template>
  <div>
    <h2>{{ headline }}</h2>
    <button @click="clickButton">Show the msg property!</button>
    <button @click="doAxiosRequest">Load random dog image</button>
    <br />
    <img :src="dogImage" width="200" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      headline: 'Vue Main Component Headline',
      dogImage: undefined,
    };
  },
  props: {
    msg: String,
  },
  methods: {
    clickButton: function () {
      alert('msg property: ' + this.msg);
    },
    doAxiosRequest: function () {
      axios.get('https://dog.ceo/api/breeds/image/random').then((response) => {
        this.dogImage = response.data.message;
      });
    },
  },
  mounted() {
    alert('Vue Main Component mounted');
  },
};
</script>
<!-- vue-components/vue-footer.vue -->

<template>
  <p>This is the footer with custom scoped CSS.</p>
</template>

<style scoped>
* {
  color: red;
}
</style>

Vue.js ohne Build Tools in WordPress verwenden

Ich arbeite viel mit WordPress und würde auch dort gerne Vue.js in gleichem Umfang nutzen können. Die Struktur der Dateien kannst du auch in WordPress beibehalten. Zusätzlich habe ich hier noch ein kleines Code Snippet für die functions.php geschrieben, um Vue, den SFC Loader und die Haupt-JS Datei zu laden:

<?php
function setup_vue_app() {
    // latest vue version
    wp_enqueue_script("vuejs", 'https://unpkg.com/vue@latest', array(), '1.0.0', true);  

    // sfc-loader for vue
    wp_enqueue_script("vue3-sfc-loader", 'https://cdn.jsdelivr.net/npm/vue3-sfc-loader', array('vuejs'), '1.0.0', true);  

    // main vue js file to register all components
    wp_enqueue_script("vue-app", get_stylesheet_directory_uri() . '/js/vue-app.js', array('vuejs', 'vue3-sfc-loader'), '1.0.0', true);    

    // includes the path to your vue files into vue-app.js
    wp_localize_script("vue-app", "WPVars", array(
        "vueFiles" => get_stylesheet_directory_uri() . '/js/vue-components',
      )
    );

    // load packages (e.g. axios)
    wp_enqueue_script("vue-axios", 'https://unpkg.com/axios/dist/axios.min.js', array('vuejs', 'vue3-sfc-loader'), '1.0.0', true);
}
add_action('wp_enqueue_scripts', 'setup_vue_app');
?>

Um den Pfad zu unseren Vue Dateien dynamisch zu gestalten ist noch ein kleine Anpassung in der vue-app.js notwendig:

// js/vue-app.js

...

const app = createApp({
  components: {
    VueMainComponent: Vue.defineAsyncComponent(() =>
      loadModule(WPVars.vueFiles + '/vue-main-component.vue', options)
    ),
    VueFooter: Vue.defineAsyncComponent(() =>
      loadModule(WPVars.vueFiles + '/vue-footer.vue', options)
    ),
  },
}).mount('#app');

Und das ist auch schon alles. Jetzt kannst du Vue.js auch mit deiner WordPress Webseite benutzen.

Wenn du erst neu mit Vue.js arbeitest, findest du hier ein paar Beispielprojekte um Vue.js zu lernen.

Alle Inhalte stelle ich komplett kostenfrei zur Verfügung! Wenn du die Kosten für den Betrieb und meine Zeit etwas supporten möchtest, kannst du mir gerne einen kleinen Betrag deiner Wahl donaten. Thanks! 💙

Donation
Externe Quellen anzeigen
Ähnliche Beiträge
Beteilige dich an der Unterhaltung

1 Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

bold italic underline strikeThrough
insertOrderedList insertUnorderedList outdent indent
removeFormat
createLink unlink
code

Das könnte dich auch interessieren