Skip to content

Basic Vite Vue 3 TypeScript App

Julian Knight edited this page Mar 17, 2023 · 5 revisions

Basic Vite Vue3 Typescript App

Editors note: Please remember that if you are using Vite's live dev server to run your uibuilder app, you will need to give the uibuilder client a hint as to the correct Socket.IO namespace. This is done by including the namespace in a start function early in your custom code. For example, if your uibuilder url is aa and you are testing locally, uibuilder.start({ioNamespace:'http://localhost:1880/aa'}). You can and should remove this when running normally.

See the documentation for details.

In this article, a app would build with the combination of Vite + Vue 3 + TypeScript + Uibuilder.

The concept of Typescript, Vue 3 plugin, Vue 3 CompositionApi, Vue 3 inject and provide would be touched.

Node-RED flow: add uibuilder node and change its settings

  1. Add uibuilder node in your flow.

  2. Double click this node and change Properties Name and URL to vue3-app

  3. Expand Advanced Settings: set Template to dist instead of src

  4. Save and deploy

Create a vite project

Change directory to uibuilder

// path: node-red setting projects enabled
pathto/.node-red/projects/project-name/uibuilder/

// path: node-red setting projects disabled
pathto/.node-red/uibuilder/

Note: Remember that you can also now change the uibRoot folder (which defaults to ~/.node-red/uibuilder/ when not using node-red projects) to any accessible folder by making a change to the settings.js file.

Create a vite project

With npm:

$ npm create vite@latest

With yarn:

$ yarn create vite

Then following the prompts, for this article we make choice as below.

$ Project name: vue3-app
$ Select a framework: > vue
$ Select a variant: vue-ts

Done. Now run

$ cd vue3-app

File structure is:

. //Vue3-app
├── README.md
├── index.html
├── package.json
├── public
   └── favicon.ico
├── src
   ├── App.vue
   ├── assets
      └── logo.png
   ├── components
      └── HelloWorld.vue
   ├── env.d.ts
   └── main.ts //entry
├── tsconfig.json
└── vite.config.ts // vite configuration

Add libraries

With npm:

Install uibuilder

$ npm install node-red-contrib-uibuilder

Note: This is better done via Node-RED's palette manager.

Install uibuilder dependency socket.io-client

$ npm install socket.io-client

WARNING: Take care to match the client version that uibuilder uses otherwise you may get hard to analyse problems. If you can, avoid installing the client and instead use the existing client that uibuilder has already installed. However, this requires some tweaking of your build.

Install plugin legacy in case your browser doesn't support Navite ESM( This is baseline for vite ). You can skip this if your browser support Native ESM (which should be most current browsers).

$ npm install @vitejs/plugin-legacy

Modify vite.confg.ts

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// optional, the plugin-legacy is used to support legacy brower withou Native ESM.
import legacy from '@vitejs/plugin-legacy'


// https://vitejs.dev/config/
export default defineConfig({
  // set the base url as './', the default base url is '/'. 
  // In our approach, the vue3-app is host by node-red. 
  // The '/' would be intepreted into 'http://localhost:1880' which cause wrong url issue for the script, style , image and icon for index.html in dist folder. 
  // While './' would be intepreted into 'http://localhost:1880/vue3-app'
  base: './',
  plugins: [
    vue(),
    // optional, the plugin-legacy is used to support legacy brower withou Native ESM.
    legacy({
        targets: ['defaults', 'not IE 11']
    })

  ],
  // This a walkaround to let the vite solve the uibuilderfe.js correctly
  optimizeDeps: {
    include: [
      'node-red-contrib-uibuilder/front-end/src/uibuilderfe.js',
    ]
  },
  // The proxy is used to provide node-red connection in developing mode. 'yarn dev'
  server:{
    proxy:{
      '/uibuilder':{
        target: "http://localhost:1880/uibuilder",
        changeOrigin: true,
        ws:true,
        rewrite: (path)=>path.replace(/\/uibuilder/, ''),
      },
    }
  }
})

Add interface: src/interface/IData.ts

Create src/interface folder

$ mkdir src/interface

Add IData.ts

interface IData {
  topic: string
  payload: string
  // you can define more memeber here according to your model
}

export default IData;

Add plugin: src/plugin/uibuilderPlugin.ts

Create plugin folder

$ mkdir src/plugin

Add uibuilderPlugin.ts. In this plugin, all uibuilder related data and method is put inside. plugin. Data and method is registered vie Vue3 provide and inject pattern. It's convenient to receive message or send message by injection in any component. A hub for all datum. A compact version of vuex store instance.

import { App, provide, reactive, ref } from 'vue'
// this line is used to ignore tsc since uibuilderfe.js is not providing types.
// @ts-ignore
import uibuilder from 'node-red-contrib-uibuilder/front-end/src/uibuilderfe.js'
import IData from '../interface/IData';


export default {
  install: (app: App, options: {nameSpace:string}) => {
    /* our code for the plugin goes here
       app is the result of createApp()
       options is user options passed in. We pass the uibuilder node url here for connection */

    // get nameSpace the same as uibuilder node url. eg: '/vue3-app' - only needed for the Vite dev server
    const { nameSpace } = options;


    // defien the reactive data used for this app
    const reactiveAsyncData = reactive<IData>({
      topic: '',
      payload: '',
      // define more here
    });

    // provide asyncData global
    app.provide('asyncData', reactiveAsyncData);


    // messageHandler
    const messageHandler = () => {
      uibuilder.onChange('msg', (newValue:{topic:string, payload:string}) =>{
        reactiveAsyncData.topic = newValue.topic;
        reactiveAsyncData.payload = newValue.payload;

        // topic logic here
        switch(newValue.topic){

        }
      })
    }

    // start messageHandler
    messageHandler();

    // start uibuilder instance
    uibuilder.start(nameSpace);

    /* send message back to node-red*/
    const send2NR = (topic: string, payload: string):void => {
      console.log(topic, payload);
      uibuilder.send({
        'topic': topic,
        'payload': payload
      });
    }

    /* send control command to node-red*/
    app.provide("send2NR", send2NR);
  }
}

Modify src/main.ts to use uibuilderPlugin we defined previous

In this component, we display the received message from node-red and can also send back message with specific topic and payload. Have fun.

// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import uibuilderPlugin from './plugin/uibuilderPlugin';

const app = createApp(App);

// use uibuilderPlugin, we pass the nameSpace for uibuilder.start('/vue-app') here
app.use(uibuilderPlugin, { 'nameSpace': '/vue3-app' });

// mount app
app.mount('#app')

Modify the Helloworld.vue

// src/components/Helloworld.vue
<script setup lang="ts">
import { ref, inject } from 'vue'
import IData from '../interface/IData'

defineProps<{
  msg: String,
}>()

// inject method 'send2NR' from plugin uibuilderPlugin
const send2NR = inject('send2NR') as (topic: string, payload:string) => void;

// inject data 'asyncData' from plugin uibuilderPlugin
const asyncData = inject('asyncData') as IData;

// local variable for topic to send
const topic = ref('');

// local variable for payload in the textarea to send
const payload = ref('');

const count = ref(0);

</script>

<template>
 <h1>{{ msg }} </h1>
 <div className="container">
   <div className="box">
      <h2>Received</h2>
      <input className="box-topic" readonly type="text" placeholder="Received Topic"  v-model="asyncData.topic"/>
      <textarea className="box-message" readonly type="text" placeholder="Received Payload" v-model="asyncData.payload" />
    </div>
    <div className="box">
      <h2>Send</h2>
      <input className="box-topic" type="text" placeholder="type topic" v-model="topic"/>
      <textarea className="box-message" type="text" placeholder="press enter to send" v-model="payload" @keyup.enter.exact="send2NR(topic, payload)"/>
    </div>
  </div>
  <br/>
  <br/>
  <button type="button" @click="count++">count is: {{ count }}</button>
  <p>
    Recommended IDE setup:
    <a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
    +
    <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
  </p>

  <p>
    <a href="https://vitejs.dev/guide/features.html" target="_blank">
      Vite Documentation
    </a>
    |
    <a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
  </p>

  <p>
    Edit
    <code>components/HelloWorld.vue</code> to test hot module replacement.
  </p>
</template>

<style scoped>
a {
  color: #42b983;
}


.container {
  display: inline;
  text-align: center;
}

.box {
  font: 15px arial, sans-serif;
  border-style: solid;
  border-width: 1px;
  padding: 10px;
  border-radius: 5px;
  display: inline-block;
}

.box-topic {
  height: 50px;
  font: 20px bold, arial, sans-serif;
  width: 95%;
  padding: 5px;
}

.box-message {
  height: 200px;
  font: 17px arial, sans-serif;
  width: 95%;
  padding: 5px;
}
</style>

How to develope

With npm

$ npm run dev

With yarn

$ yarn dev

Modify and check the hot update in the http://localhost:3000. Lightening fast!

How to build

With npm

$ npm run build

With yarn

$ yarn build

The static file is generated in dist folder.

File structure

. //vue3-app
├── README.md
├── dist
   ├── assets
      ├── index.54df34ff.js
      ├── index.effd24ec.css
      ├── logo.03d6d6da.png
      └── vendor.c0cb906b.js
   ├── favicon.ico
   └── index.html
├── index.html
├── package.json
├── public
   └── favicon.ico
├── src
   ├── App.vue
   ├── assets
      └── logo.png
   ├── components
      └── HelloWorld.vue
   ├── env.d.ts
   ├── interface
      └── IData.ts
   ├── main.ts
   └── plugin
       └── uibuilderPlugin.ts
├── tsconfig.json
├── vite.config.ts
└── yarn.lock

package.json

{
  "name": "vue3-app",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "node-red-contrib-uibuilder": "^4.1.4",
    "socket.io-client": "^4.4.1",
    "vue": "^3.2.25"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^2.0.0",
    "typescript": "^4.4.4",
    "vite": "^2.7.2",
    "vue-tsc": "^0.29.8"
  }
}

For the detail source code, you can check this link node-red-uibuilder-vite-vue3-typescript-example. You can create issue in this repo if you have any question or problem. Happy coding.

Clone this wiki locally