Add Vue.js + Vite to an AdonisJS Application
Tags: webdev, adonisjs, vue, typescript
Published: 2023-02-13 08:59:25

AdonisJS is a Node.js backend framework. There's no frontend framework coming with it, everything is rendered on the server side.

This article will describe how to integrate Vue.js (and Vite) to an AdonisJS application.

You should first [create a new project](https://docs.adonisjs.com/guides/installation#creating-a-new-project) with a "web" structure and no Webpack Encore integration:

```
CUSTOMIZE PROJECT
❯ Select the project structure · web
❯ Enter the project name · hello-world
❯ Setup eslint? (y/N) · false
❯ Configure webpack encore for compiling frontend assets? (y/N) · false
```

## Add Vue.js & Vite dependencies & Configure Vite

```sh
# if using npm
npm install vue && npm install --dev vite @vitejs/plugin-vue

# if using yarn
yarn add vue && yarn add -D vite @vitejs/plugin-vue

# if using pnpm
pnpm install vue && pnpm install -D vite @vitejs/plugin-vue
```

Create Vite configuration at the root of your project (`vite.config.ts`):

```typescript
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';

// https://vitejs.dev/config/
export default defineConfig(({ command }) => {
  return {
    base: command === 'serve' ? '' : '/build/',
    publicDir: 'fake_dir_so_nothing_gets_copied',
    build: {
      manifest: true,
      outDir: 'public/build',
      rollupOptions: {
        input: 'resources/js/app.ts',
      },
    },
    plugins: [vue()],
  };
});
```

## Reconfigure app startup scripts

You'll need a parallel script runner like `npm-run-all`:

```sh
# if using npm
npm install --dev npm-run-all

# if using yarn
yarn add -D npm-run-all

# if using pnpm
pnpm install -D npm-run-all
```

Then reconfigure your `npm dev` script this way (`package.json` excerpt):

```json
{
  "scripts": {
    "dev": "run-p ace:serve vite:serve",
    "ace:serve": "node ace serve --watch",
    "vite:serve": "vite --host --port 3000",
  }
}
```

At this point, the `npm dev` command starts both AdonisJS & Vite.
But we lack the integration of both: how AdonisJS will instruct your browser that it should load assets generated by Vite?

## Integrate Vite into AdonisJS

We'll create a `ViteProvider` class that will generate the contents to be included into AdonisJS pages:

Paste this into `providers/ViteProvider.ts`:

```typescript
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
import Env from '@ioc:Adonis/Core/Env';
import { readFileSync } from 'fs';

export default class ViteProvider {
  public static needsApplication = true;

  constructor(protected app: ApplicationContract) {}

  public async boot() {
    const View = this.app.container.resolveBinding('Adonis/Core/View');

    const served = () => {
      const port = Env.get('VITE_PORT', 3000);
      return `
      <script type="module" src="http://localhost:${port}/@vite/client"></script>
      <script type="module" src="http://localhost:${port}/resources/vue/app.ts" ></script>
    `;
    };

    const built = () => {
      const data = readFileSync('./public/build/manifest.json').toString();
      const manifest = JSON.parse(data);
      return `<script type="module" src="/build/${manifest['resources/vue/app.ts']['file']}"></script>`;
    };
    View.registerTag({
      tagName: 'vite',
      seekable: false,
      block: false,
      compile(_, buffer) {
        buffer.outputRaw(
          Env.get('NODE_ENV') === 'development' ? served() : built(),
        );
      },
    });
  }
}
```

Register the provider into `.adonisrc.json` file (excerpt):

```json
{
  "providers": [
    "./providers/ViteProvider",
  ],
}
```

Replace default view contents (`resources/views/welcome.edge`):

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="icon" type="image/png" href="/favicon.ico" />

    @vite

``    <title>AdonisJS + Vue.js + Vite</title>
  </head>

  <body>
    <div id="app"></div>
  </body>
</html>
```

## Create basic Vue.js application

Create Vue.js application main entry point in `resources/vue/app.ts`:

```typescript
import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');
```

Create main component in `resources/vue/App.vue`:

```vue
<template>Welcome to AdonisJS + Vue.js + Vite</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  setup() {},
});
</script>
```

## Adjust for production build

We need to adjust build scripts as well (`package.json` excerpt):

```json
{
  "scripts": {
    "build": "run-s vite:build ace:build",
    "ace:build": "node ace build --production",
    "vite:build": "vite build"
  },
}
```

Our application will be build through the `npm run build` command.

This will fail because of the Vue.js code being processed by the TypeScript compiler.

To circumvent this, we add the Vue.js app folder to the TypeScript exclude paths (`tsconfig.json` excerpt):

```json
{
  "exclude": ["resources/vue/", "node_modules", "build"],
}
```

## Final thoughts

This article gives the steps for a basic Vue.js integration into AdonisJS.

The developer experience can be enhanced by integrating a few more libraries:

- [Inertia](https://inertiajs.com/)
- [Adonis Stardust](https://github.com/eidellev/adonis-stardust)
- [Tailwind CSS](https://tailwindcss.com/)