How to build Vue.js apps without Node, Webpack, npm, build tools or CLI

I love Vue.js and for larger applications the development with Node.js is quite feasible. However, if you want to build a “normal” website it’s often a lot of effort you don’t want to have. But even without webpack or other build tools it is quite easy to do!

If you simply want to place a small or medium sized Vue application on your website, it is often unnecessary effort to create a project with the Vue CLI, develop it and then integrate the finished build into your website. A small application on a WordPress site and such an effort? It doesn’t have to be.

On many websites jQuery is used. I think many don’t know that it is just as easy to use Vue.js for it.

Vue.js without build tools & co.? The solution: Vue SFC Loader

For a long time I only used jQuery on websites, because I simply didn’t know this way. However, after a long research and a lot of trial and testing, I found a simple solution that works very easily and reliably: Vue3 SFC Loader

This script is able to load .vue files at runtime – without the need of a Node.js environment. So you don’t need a bundler and also no build tools which have to be executed before the execution.

In the Vue.js documentation there is also a section about “Without Build Tools“. For very small applications with only one main component this also works. For larger applications this unfortunately did not work for me, so that script attribute importmap is not supported by all browsers.

Advantages and disadvantages

An obvious advantage is that the complexity of the environment (Vue CLI, Node Server, etc.) is greatly reduced. In addition, you do not have to “recompile” the entire application after each change and push it to Production. So for small and medium sized applications and when using a CMS or simply small websites, this is a real alternative.

But that brings us directly to the disadvantages: If you want to create a single page application (SPA) you should still use the Vue CLI. In general, I would still prefer it for larger, standalone applications. With a good CI/CD workflow, the deployment of the application is then also well feasible.

Vue.js App without Build Tools – Example App

I built a small Vue application to show as many different features of Vue as possible. The app consists of 2 components: Main Component and Footer.

Let’s go through the individual functions step by step:

Basic structure (with several Vue Components)

The basic structure consists of an HTML file, a JS file in which the Vue Components are registered and the three 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>

This JavaScript file contains a JavaScript object with data and functions that are needed for the Vue SFC Loader. Therefore, you can simply take this part and only adjust the marked Vue Components for your project below.

// 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');

The Footer component also contains only text at the beginning. In a later section I will show how to use scoped CSS here.

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

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

Main Component

In the main component I have summarized the main logic. In general, you can make the division as you want or put everything in a single component.

<!-- 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>

It includes a {{ headline }} variable that displays plain text and a JavaScript alert() that is executed once the component is mounted.

We will now extend this component with further functions in the next chapters.

Events & Methods

For example, we add a new button with a click event. When the button is clicked, the doAxiosRequest() function is executed. Don’t be confused by the name, in the next step we add the package Axios.

<!-- 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

External packages/modules can also be used without build tools. In many applications Axios is used to make asynchronous server requests. With this example I will demonstrate how to include such packages.

Vue.js app example with random dog image

To load a package, we just need to add the link in our index.html:

<!-- 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>

Then we can access all the functionality directly through the axios variable. In this example we load a random dog image into the 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 are attributes that we can pass when including our components. They can be fixed or dynamic attributes. In the example, I simply passed the static text “Foobar”.

<!-- 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 our main component we now add a new button that calls the clickButton() function when clicked. We also need to register the property as a string. In the clickButton() function we can then retrieve the value with this.msg.

<!-- 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

With Scoped CSS CSS properties can be defined only for the current component. To do this, the CSS style tag must also have the scoped attribute.

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

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

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

Full 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>

Using Vue.js without build tools in WordPress

I work a lot with WordPress and would like to be able to use Vue.js there as well to the same extent. You can keep the structure of the files also in WordPress. Additionally I wrote a small code snippet for functions.php to load Vue, the SFC loader and the main JS file:

<?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');
?>

To make the path to our Vue files dynamic a small adjustment in vue-app.js is necessary:

// 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');

And that’s all. Now you can use Vue.js with your WordPress website.

If you’re new to Vue.js, here are a few sample projects to help you learn Vue.js.

Show external resources
Related Posts
Join the Conversation

2 Comments

Your email address will not be published. Required fields are marked *

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

This can also interest you