Skip to content

Load Vue (v2 or v3) components without a build step (modern browsers only)

Julian Knight edited this page Jan 10, 2023 · 1 revision

Updated 2023-01-10: Updated for uibuilder v5/v6.

In the WIKI article Dynamically load .vue files without a build step, we show you how to load .vue files without needing to build them (Vue v2 only, for v3, see this page using vue3-sfc-loader.

Note that this approach also works with Vue v3 as well as v2.

In this article, we show you how to use a modern browser to do something similar using HTML modules.

In order to do this, you will need to write the Vue component in pure JavaScript.

The following is a Vue v3 example. Vue v2 has a different syntax for setup but the HTML and component would be the same.

mycomponent.js

export default {

    template: `
       <div>
          <p>Ooh, my very own Vue component!</p>
          <p>A component counter: {{ count }} <button @click="count++">Count</button></p>
       </div>
    `,

    data() {
        return { count: 0 }
    },
}

To load this, in your app javascript file:

index.js

// @ts-nocheck

import '../uibuilder/uibuilder.esm.min.js'  // Adds `uibuilder` and `$` to globals

// For Vue v3
// import { createApp } from '../uibuilder/vendor/vue/dist/vue.esm.browser.js' // Dev ver local install. Chg to .min.js for prod
import { createApp } from 'https://cdn.jsdelivr.net/npm/vue@3.2.45/dist/vue.esm-browser.js' // As above but loaded remotely

// Import the custom component directly (Note that it is a .js file, not a .vue file)
import MyComponent from './mycomponent.js'


// Using the Vue options API style for beginner simplicity
// No need to pre-define Quasar's $q when working with the options API
const app = createApp({
    // Define Vue reactive variables
    data() { return {

        message: 'Hello Vue!',
        count: 0,
        lastMsgRecvd: '[Nothing]',
        input1: '',

    } },

    components: {
        mycomponent: MyComponent
    },

    // Dynamic data
    computed: {

        // This is auto-recalculated by Vue when lastMsgRecvd changes
        formatLastRcvd() {
            const lastMsgRecvd = this.lastMsgRecvd
            if (typeof lastMsgRecvd === 'string') return 'Last Message Received = ' + lastMsgRecvd
            return 'Last Message Received = ' + uibuilder.syntaxHighlight(lastMsgRecvd)
        },

    },

    // Supporting functions
    methods: {

        // Use the uib helper function to send something to NR
        doEvent(event) { uibuilder.eventSend(event) },

        /** Runs when the change event for the source field is fired
         * @param {InputEvent} event The event object is passed by the browser automatically
         */
        doInputChange(event) {
            console.log('input1 text has changed: ', event.target.value )
            // Send the new text to Node-RED
            uibuilder.send({
                'topic': 'input1-changed',
                'payload': event.target.value,
            })
        },

    },

    // Lifecycle hooks
    mounted() {
        // If msg changes - msg is updated when a standard msg is received from Node-RED
        uibuilder.onChange('msg', (msg) => {
            console.log('>> msg recvd >>', msg, this)
            this.lastMsgRecvd = msg
        })
    },
})

app.mount('#app')

index.html

<!doctype html>
<html lang="en"><head>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Vue3 as EMS with new uib client - Node-RED uibuilder</title>
    <meta name="description" content="Node-RED uibuilder - Vue3 as ESM with new uib client">
    <link rel="icon" href="./images/node-blue.ico">

    <!-- Your own CSS -->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

    <!-- NOTE that this is loaded as a MODULE & other libraries loaded by it -->
    <script type="module" async src="./index.js">/* Imports uibuilder + your custom code */</script>

</head><body class="uib">

    <div id="app">
        <h1 style="margin-bottom:.1em;">Vue v3 App Using New uibuilder ESM Client - v6.1.0</h1>
        <div style="margin-bottom:1em;">Loads a Vue component directly without a build step</div>

        <div id="more"><!-- '#more' is used as a parent for dynamic content in examples --></div>

        <mycomponent></mycomponent>

        <p>
            <button @click="doEvent" data-mything="This is a thing" title="This is sent back to Node-RED">
                Click to send msg to Node-RED
            </button>
        </p>
        
        <p>
            <label for="input1">This is sent to Node-RED when you exit it: </label>
            <input v-model="input1" @change="doInputChange" id="input1" type="text" placeholder="Type text then exit field">
        </p>
             
        <pre id="msg" v-html="formatLastRcvd" class="syntax-highlight">Waiting for a message from Node-RED</pre>
    </div>

    <script nomodule>
        document.getElementById("app").innerHTML = "Your browser doesn't support JavaScript modules :(";
    </script>

</body></html>

Note how only 1 js file is loaded and as type="module". Without this, the above will not work. Also note the nomodule entry for telling users that this doesn't work with older browsers (IE11, we're looking at you!). Other libraries and the custom component are imported in that file.

Other approaches

http-vue-loader and vue3-sfc-loader are already mentioned and allow you to directly load .vue files.

Another alternative is to use a "build step". This uses a build tool such as Snowpack, Webpack, Rollup, etc. to build the component. This needs an extension to the chosen build tool that understands .vue files and "compiles" them to a stand-alone javascript file that you either load via a script tag in your HTML or fall back to in the nomodule part of your HTML (using the module version above for browsers that support them):

<script nomodule src="/dist/build.js"></script>

Of course, if using a build step, you might choose not to use modules at all. However, they are the future of JavaScript so are worth getting to grips with.

Build tools may also be able to output multiple versions. So if you are going to the bother of setting one up, maybe consider outputting both pre-compiled module and non-module versions. Module versions are often designated with esm (ECMA Script Module) and non-module versions umd (Universal Module Definition). IIFE (Immediately Invoked Function Expression) is another acronym sometimes mentioned, a library built with IIFE can be loaded directly in an HTML <script src="..."> tag.

Clone this wiki locally