How to build a Vue.js App without Node, Webpack, npm or build tools
Tags: node, javascript, vue, webdev
Published: 2022-07-09 11:54:10

*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](https://webdeasy.de/en/jquery-to-javascript/) 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**](https://github.com/FranckFreiburger/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](https://vuejs.org/guide/quick-start.html#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](https://caniuse.com/import-maps) 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.

{% codesandbox zue0rt %}

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.

```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="./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.

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

#### Footer Component
The Footer component also contains only text at the beginning. In a later section I will show how to use scoped CSS here.
```html
<!-- 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.
```html
<!-- 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.

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

> Do you know my [blog](https://webdeasy.de/en/topics/?ref=dev-2b57) and [newsletter](https://webdeasy.de/en/newsletter-en/?ref=dev-2b57)? There I have already published many tutorials on web development. Would be happy if we meet there! 😋

### 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
](https://webdeasy.de/wp-content/uploads/2022/07/example-application-with-dog-e1657204489435.jpg)

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

```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.

```html
<!-- 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”.

```html
<!-- 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`.

```html
<!-- 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.

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

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

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

```html
<!-- 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](https://webdeasy.de/en/best-wordpress-plugins/?ref=dev-2b57) 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](https://webdeasy.de/en/wordpress-code-snippets-en/) for **functions.php** to load Vue, the SFC loader and the main JS file:
```php
<?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:

```javascript
// 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](https://webdeasy.de/en/3-practical-examples-vue-js/).

___

Thanks for reading! ❤️

_➡️ [On my blog](https://webdeasy.de/en/topics/?ref=dev-2b57) you can find many more tutorials and articles!_