Vite + Vue

Vite is a high-speed build tool for frontend development, created by the author of Vue.js. Since Vite itself is just a build tool, it is typically used in combination with frontend frameworks such as Vue.js or React.

When using a frontend framework, web applications are generally structured in either an SPA (Single Page Application) or MPA (Multi Page Application) style.

TreeFrog supports generating scaffolding for Vite+Vue in an MPA style. In this case, the responsibilities of the frontend and backend are divided as follows.

Side Role
Frontend UI、HTML/CSS-based page layout and animations
Backend Request handling, page transitions, database access, and business logic


The MPA style, in which each page has an independent structure, is relatively simple and thus well-suited for small to medium-sized websites.

Generate an application skeleton for Vite + Vue

First, read through the tutorial to get a general understanding of the workflow. In the following steps, we will create a Vite+Vue skeleton named blogapp, just like in the tutorial. Since the steps below use the Yarn command, please make sure Yarn is installed in advance.

When generating the application, specify the --template option. The frontend source code will be placed in the frontend directory.

 $ tspawn new blogapp --template vite+vue
  created   blogapp
  created   blogapp/controllers
  created   blogapp/models
  created   blogapp/models/sqlobjects
  created   blogapp/views
  created   blogapp/views/layouts
  created   blogapp/views/mailer
  created   blogapp/views/partial
   :

 $ cd blogapp
 $ yarn create vite frontend --template vue
    :
    :  (Vite and Vue source code will be placed in the _frontend_ directory)
    :
  
 $ cd frontend
 $ yarn
    :
    :  (Required modules will be installed)
    :

The files generated by the yarn create vite command may contain unnecessary code, so feel free to modify or remove them as needed.

Before proceeding to scaffold generation, please ensure that the database has been properly configured (as specified in config/database.ini ) and that all necessary tables have been created in accordance with the tutorial instructions.

 $ cd ..
 $ tspawn scaffold blog
  driverType: QSQLITE
  databaseName: blogdb
  hostName:
  Database open successfully
  created   models/sqlobjects/blogobject.h
  created   models/objects/blog.h
  created   models/objects/blog.cpp
  created   models/models.pro
  created   controllers/blogcontroller.h
  created   controllers/blogcontroller.cpp
  created   controllers/controllers.pro
  created   models/blogservice.h
  created   models/blogservice.cpp
  created   models/models.pro
  created   views/blog/index.erb
  created   views/blog/show.erb
  created   views/blog/create.erb
  created   views/blog/save.erb
  created   frontend/src/components/BlogIndex.vue
  created   frontend/src/components/BlogShow.vue
  created   frontend/src/components/BlogCreate.vue
  created   frontend/src/components/BlogSave.vue

Along with the ERB files, Vue files have also been generated. Let us take a look at index.erb.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Blog: index</title>
<% if (databaseEnvironment() == "dev") { %>
  <script type="module" src="http://localhost:5173/src/main.js"></script>
<% } else { %>
  <%== viteScriptTag("src/main.js", a("type", "module")) %>
<% } %>
  <meta name="authenticity_token" content="<%= authenticityToken() %>">
</head>
<body>
<div data-vue-component="BlogIndex"></div>
<script id="BlogIndex-props" type="application/json"><%==$ props %></script>
</body>
</html>

In this example, the entry point is main.js, and the frontend application is injected into the <div data-vue-component="Foo"></div> section. The data is written in JSON format within the <script id="Foo-props" type="application/json"> ... </script> tag.

To pass the JSON data and launch the frontend application, modify the src/main.js file as shown below. Please edit it manually.

import { createApp, defineAsyncComponent } from 'vue'

// Import components
const modules = import.meta.glob('./components/*.vue')

// Execute createApp()
document.addEventListener('DOMContentLoaded', () => {
  document.querySelectorAll('[data-vue-component]').forEach(element => {
    const name = element.dataset.vueComponent
    const mod = modules[`./components/${name}.vue`]
    if (mod) {
      const rawProps = document.getElementById(name + '-props')?.textContent?.trim()
      const props = JSON.parse(rawProps || '{}')
      createApp(defineAsyncComponent(mod), props).mount(element)
    } else {
      console.error(`Component not found: ${name}.vue`)
    }
  })
})

As shown above, data passed from the backend to the frontend is embedded directly into the HTML in JSON format and is provided as an argument when launching the frontend application using the createApp function. Conversely, passing data from the frontend to the backend follows the standard HTML approach - by submitting form data via a POST request. In an MPA architecture, data can be exchanged across many pages without the need for Web APIs.

Run in development mode

Build the C++ code by following the steps provided in the tutorial. Once the build is complete, launch the backend server in development mode.

In the following example, the -r option enables automatic reloading - when the C++ code is modified and rebuilt, the backend application is reloaded automatically without requiring a server restart. Additionally, specifying the -e dev option applies the settings defined under the dev section in database.ini.

 $ treefrog -e dev -r
  (To stop the server, press Ctrl + C)

In a separate terminal, start the Vite development server. This allows frontend changes to be reflected immediately and automatically through Hot Module Replacement (HMR), eliminating the need to manually reload the browser - an extremely convenient feature. However, note that the Vite development server is intended for use during development only and is not required in the production version.

 $ cd frontend
 $ yarn dev

When you access http://localhost:8800/blog, the basic CRUD operations should be functioning properly. Thanks to Vite’s capabilities, any changes made to the frontend are reflected immediately upon saving, without the need to manually reload the browser, making development smooth and efficient.

The backend server, when launched with the -r option, is automatically reloaded upon completion of a rebuild. However, the updated page will not be visible in the browser unless it is manually reloaded, as HMR (Hot Module Replacement) is a feature designed for the frontend.

To address this inconvenience and enable instant reflection of backend changes as well, add the following entry to the plugins section of vite.config.ts. In this example, the plugin watches for changes to .so files and triggers a full reload when a change is detected. If you are using macOS, change it to .dylib, around line 10.

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { watch } from 'fs'   // <-- Add the code

export default defineConfig({
  plugins: [
    vue(),
    // -------------- From here
    {
      name: 'treefrog-watcher',
      configureServer(server) {
        let timer = null;
        const watcher = watch('../lib', (e, f) => {
          if (f && f.endsWith('.so') && !timer) {
            timer = setTimeout(() => {
              console.log(`${new Date().toTimeString().slice(0, 8)} hmr full reload: ${f}`);
              server.ws.send({type: 'full-reload', path: '*'});
              timer = null;
            }, 1000);  // trigger after 1000s
          }
        });
      }
    },
    // -------------- To here
  ],
  :
  :

As a result, once the backend is successfully built using the make command, the changes are reflected in the browser approximately one second later. Since the server takes a short amount of time to complete loading the backend application, this example triggers a full page reload one second after the build finishes.

Start the production version

In the production version, all Vue files must be converted into JavaScript files and placed in the public directory. Add the following build entry to vite.config.ts. Generating a manifest file is required, as the backend server needs to identify the JavaScript filenames.

export default defineConfig({
    :
    :
  // -------------- From here
  build: {
    manifest: true,
    outDir: '../public',
    rollupOptions: {
      input: 'src/main.js'
    }
  },
  // -------------- To here
})

Build the frontend code and convert it into JavaScript.

 $ cd frontend
 $ yarn build

Configure the database settings under the product entry in database.ini. After that, verify that it works as expected. By specifying the -d option, the backend server can be launched in the background. Since the Vite development server is not required in the production environment, it should be stopped, using Ctrl + c.

 $ cd blogapp
 $ treefrog -p 80 -e product -d

 (To stop the application)
 $ treefrog -k stop

On some operating systems, root privileges are required to start the application on port 80. In such cases, run the command with sudo. Similarly, stopping the application may also require sudo.

When you access http://localhost/blog, the JavaScript files located in the public/assets directory are used, and you can confirm that the application behaves the same as in development mode.

Note that HMR is not active in the production version.