How to transform a vue.js application to uibuilder

Hello,

I am new to node-red and experimenting a lot right now. This is my first time on the forum as the issue i face at this point in time is how actually to make an existing vue.js app run with uibuilder. What i tried to do is to get the scaffold once generated with the vue/cli to be exposed through the uibuilder node but i don't get anywhere besides getting a blank page ...

Any ideas would be welcome. If someone already has an example of a vue.js app transformed to work with uibuilder that would be great!

Maybe thats a stupid idea and i didn't understand anything but it should work, no ?

KRgds
//g

Have you read through the extensive docs in the wiki?

If you have an existing Vue app, does it require a build step? If so, the following example in the WIKI should help you:

uibuilder creates a folder for every instance and within that there are 2 sub-folders. src for your source files - this is used if you don't have a build step. dist for "compiled" (aka built) code. If the dist sub-folder contains the index.html file then it will use that folder automatically otherwise it uses the src folder only. All of your resources need to be in the appropriate folder.

Hello all,
thanks for your replies! Indeed I did go through the wiki etc. but didn't find a solution yet. @TotallyInformation I am going through the example you provided and imediatley ran into an issue none of uibuilders fault though when installing webpack with the command

sudo npm install webpack -g  

inside the .node-red directory
Error:

npm WARN notsup Unsupported engine for watchpack-chokidar2@2.0.0: wanted: {"node":"<8.10.0"} (current: {"node":"12.18.0","npm":"6.14.4"})
npm WARN notsup Not compatible with your version of node/npm: watchpack-chokidar2@2.0.0

and after that the install stops.
I will use nvm to install node 8 and see if that gets any further and doesn't break anything else.
//

Looks like you need to move to v3 as v2 is significantly out of date. It would be better to do that rather than mess with multiple node versions. Certainly node v8.10 won't work with a lot of Node-RED and may not work with uibuilder v2 either, certainly wont when v3 appears.

In terms of webpack with uibuilder. All you need to remember is the location of the parent folder, something like ~/.node-red/uibuilder/myuiburl Then you should use that folder as the master folder for working in webpack. Configure webpack so that it looks at ./src for the source files and outputs to ./dist. Then ensure that you configure webpack to end up with an ./dist/index.html. That's it, you can then open your uibuilder front-end as normal.

Let me know if you get any issues or if there is anything that would make life easier. On the roadmap is an update to allow uibuilder to know about a package.json file in that master folder such that if you had a script entry called build, there will be a button in uibuilder's configuration panel that will let you run the build from there as well as be able to edit the configuration files.

Hello again,

just to be sure that i understood this right:

I can run "vue create vuetest" inside the folder for the uibuilder node called let's say 'vuetest'. this gives me the scaffolded standard vue application. Then apply everything which has been outlined before and as of the wiki page?

Once all configured i shall just run npm run build which should package everything and put all what is needed into the dist directory? The thing i observed though is that the scaffold doesn't make any index.js file but a main.js file. Sorry for those noob questions ...

I think that you want to run vue create src from the master folder (~/.node-red/uibuilder/myuiburl assuming you aren't using projects, you have a standard installation and the url setting in uibuilder is set to "myuiburl").

Assuming that you've configured webpack accoridngly.

It doesn't really matter since you wont be using it. Keeping the 3 main files as index.[html|css|js] in the standard template is a convenience for people writing things by hand.

The only file that you need to keep as a fixed name is the html file. you must end up with an index.html file, the rest can be anything you like.

Not a problem. All questions are useful to someone.

Hello to keep you updated what i did so far is

What I did

In the uibuilder directory ran: vue create vuetest so I have now

./node-red/uibuilder/vuetest as folder available with the vue scaffold

Added new uibuilder node and set name and url to ‘vuetest’

I have now two index.html files : one in src ( from the uibuilder copying it into the directory ) and one in the public folder created by vue. The vue app expect this to be in public and not in src how to resolve this ? Can I just link the one in public to index.html in the src folder where uibuilder expects it ( as this is the starting point of what I would like to see in the end before adding all the links from uibuilder into node-red)? Does it matter ?

In the src folder I have also index.css, index.js, manifest.json and uibuilder.appcache

Deployed it and navigated to vuetest which gives me the uibuilder standard page not the vue app page as created by vue. That is expected I guess though.

In the vuetest folder I have also now the dist folder which wasn’t there after running vue and its empty

Now I ran npm install followed by npm run serve which provided some warnings about indentation which do not appear when running vue create in another folder.

  • Navigating to http://localhost:8080/ gives me the landing page of the vue std app as expected
  • Navigating to vuetest still provides the uibuilder standard landing page. I did NOT run npm run build yet.

Changed some informative entries in manifest.json

I am running node via nvm and the curent node version is 12.8.3

Installing web pack being inside the vuetest folder with : npm i -g webpack

This ends with the error already mentioned before

I found this : Upgrade watchpack for node 14 compatibility · Issue #10924 · webpack/webpack · GitHub on the topic

In the .node-red folder i executed npm install -D -g chokidar which then forces the install of v3

Followed by npm install -g webpack which still produces the following warnings but as I understood can be ignored as finally the installation succeeded.

npm WARN deprecated resolve-url@0.2.1:

npm WARN deprecated urix@0.1.0:

npm WARN deprecated chokidar@2.1.8: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.

npm WARN deprecated fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.

/Users/gregor/.nvm/versions/node/v12.18.3/bin/webpack -> /Users/gregor/.nvm/versions/node/v12.18.3/lib/node_modules/webpack/bin/webpack.js

npm WARN notsup Unsupported engine for watchpack-chokidar2@2.0.0: wanted: {"node":"<8.10.0"} (current: {"node":"12.18.3","npm":"6.14.6"})

npm WARN notsup Not compatible with your version of node/npm: watchpack-chokidar2@2.0.0

  • webpack@4.44.0

updated 1 package in 1.615s

Followed by

npm install style-loader css-loader html-webpack-plugin copy-webpack-plugin uglifyjs-webpack-plugin imports-loader --save-dev

So I should be set installation wise new step will be the configuration of web pack.

Thats how far i got so far. I have to play with the index.html file location as outlined above and figure out how that should work. Any suggestions ?
Have a nice day

Untick the option in uibuilder's advanced settings to stop it copying the index files to src.

You can do one of several things.

  • Tell Vue to create things where you want them (is this possible with vue-cli? Not sure).
  • Move the files once vue has created them - webpack can do this for you.
  • Create a filing system link. So delete the src & dest folders that uibuilder created for you and replace them with links to the folders that vue-cli creates.

Remember that although you do need both a src and dist folder, other than that you can create anything else in that master folder. The only other requirement is that you need an index.html file either in the src or dist folders. It is that file that tells uibuilder which folder to serve up. The other folder is then ignored from a web server perspective.

You don't want to do that unless it is running Node-RED for you - uibuilder and Node-RED between them provide the server. If you make changes to your front-end code, you only need to reload the browser window that is showing your UI.

It doesn't matter which folder you are in if you use the -g option since that installs it globally.

That is still a problem.

I think these need to go into your uibuilder instances folder. So if you have called your uibuilder instance "vuetest", the folder is ~/.node-red/uibuilder/vuetest. Run npm init -y in that folder then run npm install style-loader css-loader html-webpack-plugin copy-webpack-plugin uglifyjs-webpack-plugin imports-loader --save-dev in that folder. Edit the package.json file in that folder and add scripts to run webpack build and add an appropriate webpack configuration file to the same folder.

In other words, ~/.node-red/uibuilder/vuetest is the master folder for all of your web UI development and build activities. ~/.node-red is the master folder for everything that you want Node-RED (the back-end/web server) to do. Node-RED is the web server.

uibuilder will serve up your front-end resources from ONE of either:

  • ~/.node-red/uibuilder/vuetest/dist
  • ~/.node-red/uibuilder/vuetest/src (ignored if index.html exists in the other folder)

uibuilder will not serve up your resources from any other folder (except the common folder which is provided for convenience).

Thanks a lot for your extensive reply! webpack actually executes but the webpack.config.js throws many errors. Actually the syntax used on the wiki page seems to have extensivley evolved. In the beginning webpack didnt want to even start run and i changed the config fila as follows:

const url = "vuetest"    // change this to the url specified in the uibuilder node
var webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const path = require('path');

var HTMLWebpackPluginConf = new HtmlWebpackPlugin({
  template: __dirname + '/uibuilder/' + url + '/src/index_template.html',
  filename: 'index.html'
});

var CopyWebpackPluginConf = new CopyWebpackPlugin({
	patterns:[ 
  	{ from: './uibuilder/' + url + '/src/manifest.json' }
	]
});

var UglifyJSPluginConf = new UglifyJSPlugin({
  uglifyOptions: {
    compress: {
      warnings: false,
    },
    output: {
      comments: false
    },
  }
});

module.exports = {
  entry: [
    './uibuilder/' + url + '/src/index.js',
    'normalize.css'
  ],
  output: {
      path: path.resolve(__dirname, 'uibuilder/' + url + '/dist'),
      filename: 'bundle.min.js'
  },
  plugins: [
    UglifyJSPluginConf,
    HTMLWebpackPluginConf,
    CopyWebpackPluginConf
  ],
  module: {
    rules: [
      {
        use: [
          'imports-loader',
          {
            loader: 'imports-loader',
            options: {
              test: /uibuilderfe.js$/,
              use: "this=>window"
            }
          },
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              test: /\.css$/,
              exclude: /node_modules/
            }
          },
          'style-loader',
          {
            loader: 'css-loader',
            options: {
            test: /normalize.css$/,
            }
          }
        ]
      }
    ]
  }
};

but thats not yet it as i get the following error

Hash: **7f1dd5b3d858e55a6a12**
Version: webpack **4.44.0**
Time: **267** ms

Built at: 2020-07-30 **15:43:02**
 2 assets

Entrypoint **main** = **bundle.min.js**

[0] **multi ./uibuilder/vuetest/src/index.js normalize.css** 40 bytes { **0** } **[built]**
[1] **./uibuilder/vuetest/src/index.js** 1.08 KiB { **0** } **[built]** **[failed] [1 error]**

**WARNING in configuration**

**The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.**

**You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/**

**ERROR in ./uibuilder/vuetest/src/index.js**
**Module build failed (from ./node_modules/imports-loader/dist/cjs.js):**

**ValidationError: Invalid options object. Imports Loader has been initialized using an options object that does not match the API schema.**

 **- options should be one of these:**
**object { imports, … } | object { wrapper, … } | object { additionalCode, … }**

**Details:**
*** options has an unknown property 'test'. These properties are valid:**
**object { imports, … } | object { wrapper, … } | object { additionalCode, … }**
*** options has an unknown property 'use'. These properties are valid:**
**object { imports, … } | object { wrapper, … } | object { additionalCode, … }**
*** options misses the property 'imports' | should be any non-object.**
*** options misses the property 'wrapper' | should be any non-object.**
*** options misses the property 'additionalCode' | should be any non-object.**
**at validate (/Users/gregor/.node-red/node_modules/schema-utils/dist/validate.js:96:11)**
**at Object.loader (/Users/gregor/.node-red/node_modules/imports-loader/dist/index.js:28:28)**
 **@ multi ./uibuilder/vuetest/src/index.js normalize.css main[0]**

**ERROR in multi ./uibuilder/vuetest/src/index.js normalize.css**
**Module not found: Error: Can't resolve 'normalize.css' in '/Users/gregor/.node-red'**
 **@ multi ./uibuilder/vuetest/src/index.js normalize.css main[1]**

**ERROR in bundle.min.js from UglifyJs**

**DefaultsError: `warnings` is not a supported option**
**at DefaultsError.get (eval at <anonymous> (/Users/gregor/.node-red/node_modules/uglify-js/tools/node.js:18:1), <anonymous>:69:23)**
**at Function.buildError (/Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/index.js:105:54)**
**at /Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/index.js:258:52**
**at Array.forEach (<anonymous>)**
**at /Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/index.js:233:17**
**at step (/Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/TaskRunner.js:85:9)**
**at done (/Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/TaskRunner.js:96:30)**
**at /Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/TaskRunner.js:101:13**
**at TaskRunner.boundWorkers (/Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/TaskRunner.js:70:11)**
**at enqueue (/Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/TaskRunner.js:91:14)**
**at /Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/TaskRunner.js:111:9**
**at Array.forEach (<anonymous>)**
**at TaskRunner.run (/Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/TaskRunner.js:89:11)**
**at UglifyJsPlugin.optimizeFn (/Users/gregor/.node-red/node_modules/uglifyjs-webpack-plugin/dist/index.js:227:18)**

**at AsyncSeriesHook.eval [as callAsync] (eval at create (/Users/gregor/.node-red/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:15:1)**
**at AsyncSeriesHook.lazyCompileHook (/Users/gregor/.node-red/node_modules/tapable/lib/Hook.js:154:20)**
**at /Users/gregor/.node-red/node_modules/webpack/lib/Compilation.js:1409:36**
**at eval (eval at create (/Users/gregor/.node-red/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:10:1)**
**at /Users/gregor/.node-red/node_modules/html-webpack-plugin/lib/cached-child-compiler.js:267:53**

Child **HtmlWebpackCompiler** :
**ERROR in Entry module not found: Error: Can't resolve '/Users/gregor/.node-red/uibuilder/vuetest/src/index_template.html' in '/Users/gregor/.node-red'**

I assume once all the option errors are fixed the rest will go away as well so i have to dig in and understand what the different loaders etc actually do. Conceptually its quite clear basically its a rewrite of the code a sort of compiler to produce 'production ready' code.

btw in the section where it s stated to change template_index.html ( as copied from the original index.html) to remove the following lines

    <link rel="stylesheet" href="index.css">
    <script src="vendor/jquery/dist/jquery.min.js"></script>
    <script src="uibuilderfe.min.js"></script>
    <script src="index.js"></script>
    <link rel="stylesheet" href="vendor/normalize.css/normalize.css">

only three of the actualy exist for removal. The two lines below don't exist in that file:

<link rel="stylesheet" href="vendor/normalize.css/normalize.css">
<script src="vendor/jquery/dist/jquery.min.js"></script>

As i understood the normalize.css will be created during the webpack processing. Maybe the file this ws taken from originally was already a processed index.html file i.e. state after webpack has run.
// have a nice evening

1 Like

Thanks for your extensive reply! I just posted a reply but the antispam system actually captured it and hid it for review so hopefully it will apear soon ... ha it just appeared :slight_smile:

Some update on my tests so far. I have been able to get webpack working just on a barebone uibuilder node. I am working from within the folder of the node and the follwoing webpack.config.js worked for me:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');

var HTMLWebpackPluginConf = new HtmlWebpackPlugin({
  template: './src/index-template.html',
  filename: 'index.html'
});

var CopyWebpackPluginConf = new CopyWebpackPlugin({
	patterns:[ 
    { from: './src/manifest.json' }
	]
});

module.exports = {
  entry: [
    './src/index.js',
    'normalize.css'
  ],

  output: {
      path: path.resolve(__dirname, './dist'),
      filename: 'bundle.min.js'
  },

  plugins: [
    HTMLWebpackPluginConf,
    CopyWebpackPluginConf
  ],

  module: {
    rules: [
      {
        test: /uibuilderfe.js$/,
        use: "imports-loader?this=>window"
      },
      {
          test: /\.css$/,
          exclude: /node_modules/,
          use: ["style-loader", "css-loader"]
      },
      {
        test: /normalize.css$/,
        use: ["style-loader", "css-loader"]
      },
    ]
  }
};

Preparation steps once the uibuilder node has been iniatlized:

npm init -y
npm i webpack webpack-cli webpack-dev-server --save-dev
npm i html-webpack-plugin copy-webpack-plugin --save-dev
npm i css-loader style-loader imports-loader --save-dev

for convienience i added

    "dev": "webpack --mode development",
    "prod": "webpack --mode production"

in the scripts section of package.json

Next step is to repaet this from vue-cli generated project within a uibuilder node folder. I keep you updated. //

1 Like

That's great, thanks for sharing.

If you were able to write up a summary with example configuration files when you have everything working, it would be great to be able to load that to the WIKI. :slight_smile:

Yes i will write it up and proivde it. I try to document every step i do as there is a lot of trial and error involved as I am not an expert at all FE dev ( at least not yet :slight_smile: )

One test i did is to use the 'vue-cli-service build' from a scaffolded project within a folder under uibuilder and linked that to a uibuilder node. Unfortunaltley it just shows a blank page when navigating to it. It compiles fine and i have a dist folder with the following structure:

.
├── css
│   └── app.fb0c6e1c.css
├── favicon.ico
├── img
│   └── logo.82b9c7a5.png
├── index.html
└── js
    ├── app.9a43cbb9.js
    ├── app.9a43cbb9.js.map
    ├── chunk-vendors.b0f460c7.js
    └── chunk-vendors.b0f460c7.js.map

are all the preconditions for uibuilder met here ? Or is some more config required ?

*** EDIT ***
I just found it. The trick is that you have to tell Vue where the application is actually located. Vue assumes that e.g an App called my-app is served from http://www.my-app.com/ so therefore nothing has been shown.

I had to simply add a vue.config.js file like below in the root folder of the uibuilder node.

module.exports = {
    publicPath: './'  
  }

and the magic happened. Now i have to link that app with uibuilder and node red to get the messages flowing...

2 Likes

And that is exactly the kind of person we need to help write documents because you aren't taking background knowledge for granted as those of us who are used to working with FE code and uibuilder do. :slight_smile:

1 Like

Some more news.

Actually you don't need the webpack.config.js when cretating a project with vue create <project name>. The only thing you need is the vue.config.js file as stated above and then use the vue-cli-service scripts for building as of the package.json file.

I tested this by creating a new project from scratch this way and then accessed the url via the uibuilder node.

Nevertheless one question: I created again a new project but this time with the router selected at the inital project setup added the vue.config.js and build the app, which went fine. The result though I got was that the uibuilder fallback page showed up instead of the vue base application (the only thing which changed is the added router).

I was trying to figure out what was going on to no avail. The dist directory was populated with all the right files etc.. no difference in the structure from the app without the router. BUT when I restared my docker container wich serves node-red, bingo it worked. I got the app with the router working accessible directly from the uibuilder node.

So my question is, what are the conditions when you have to restart node-red using uibuilder ?

The next step is now to add a route with the uibuilder standard page to get the message flow going.

Thanks

Normally you should never have to do that. Changes to the front-end code, even rebuilding the dist folder or swapping from src to dist should all be dynamic.

Adding new front-end libraries (REACT, ...) libraries should also be dynamic if you do it from the uibuilder config.

I wonder if you stumbled on a race condition. I'd need to understand the order in which things happen. I think that the sensible order is:

  1. Add a uibuilder node instance, change the url and re-deploy. That will create the folders that uibuilder requires.
  2. Install any front-end libraries if you need to.
  3. Add your Vue cli config and generate any scaffold & src files/folders within the instance top-level folder.
  4. Change the src/dist folders to be links if you need to.
  5. Run your build.
  6. Open or reload the end point.

From there, you should be able to make changes to your src, run the build and reload the page without any need to do anything with Node-RED.

I will repeat the steps as you outlined and see what is happening.

Where is the uibuilderfe.js actually located ?

I started adding the scripts from the original index.html file to the App.vue

starting with those two

<script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
<script src="./uibuilderfe.js"></script>   <!--   //dev version -->

the build process takes into account the socket.io one but complains that it can't find the uibuilderfe.js one ...
As in the vue app an index.html doesn't exist ( it gets generated by the build process ) i have to put somewhere the scripts needed for uibuilder to work properly. My first guess is that the root component would be the best place to start out.

If is currently in the nodes folder of the module - I think in a sub-folder called src. It will be moving to a more sensible place in the next release of uibuilder. There is a minimised version as well. That folder is mounted as a web resource into the root of the instance.

That is also true of any other resources that are loaded. In that case, they are all attached a sub-paths to the ../uibuilder/vendor path. The leading dot and double dot mean the same things as they would for a filing system path by the way. So . is your current root folder and .. is 1 level up and is, therefore your Node-RED root (taking into account things like the httpRoot setting). Using those relative references means that it is a lot easier to build html files that don't need to be changed if you change uibuilder url, httpRoot or even the whole server platform.

Well webpack can do all of this for you so you may still need to do some work on the config. The WIKI example I think has all of that built in.

But to start with, you could simply exclude those from the build process and leave the links as-is.

i finaly got it working :slight_smile:

and here the corresponding node-red part:

As you can see at the top of the first image I added a vue view to the scaffolded application and created UiBuilder.vue as a single file vue component.

the url of the view is this one where you can see the # concerning the route added to the router: http://localhost:1880/vuelms/?#/uibuilder

The only thing is that when jumping between the different routes, which works, the UiBuilder view looses context when comming back i.e. the button count drops to 0 every time. Having multipe browser windows open on that route the button count is relative to the browser window there are no side effects here.

Now I have to redo everything again to be sure to write it up properly what i did in the end. There was some trial and error involved which i didn't always document 100% ....

and btw it just uses the stock vue-cli-service build from package.json nothing else no other changes required.

1 Like