DEV Community

Cover image for Integrate Vue.js and Adonis.js v5 using Laravel Mix
Amir Hosein Salimi
Amir Hosein Salimi

Posted on

Integrate Vue.js and Adonis.js v5 using Laravel Mix

In this post, we're going to setup Vue.js in a newly created Adonis.js v5 project. Also we're going to write Single File Components (SFC) for the Vue part and SCSS for the styling part! You can see the final source code of this tutorial on my GitHub:

Create a new project

We start with a fresh project, so let's create a new Adonis.js v5 project, called adonis-vue-app:

npm init adonis-ts-app adonis-vue-app
Enter fullscreen mode Exit fullscreen mode

Choose Web Application when prompted, so we have @adonis/view, @adonis/session providers configured for us automatically.

I preferably choose to install ESLint and Prettier as well, so my code always looks perfect. After your project is created cd to it.

Setup a static file server

For the rest of the article, we need a static file server because later on, we want to access generated JS and CSS files directly from the browser. If you already chose to have an API boilerplate, then you may configure a static file server by creating config/static.ts with the following code:

// config/static.ts

import { AssetsConfig } from '@ioc:Adonis/Core/Static'

const staticConfig: AssetsConfig = {
  enabled: true,

  dotFiles: 'ignore',

  etag: true,

  lastModified: true,
}

export default staticConfig
Enter fullscreen mode Exit fullscreen mode

In order to tell Adonis.js file to serve which files to serve, open .adonisrc.json file and add this to the corresponding field:

//...

"metaFiles": [
    ".env",
    ".adonisrc.json",
    {
      "pattern": "resources/views/**/*.edge",
      "reloadServer": true
    },
    {
      "pattern": "public/**/css/*.css",
      "reloadServer": false
    },
    {
      "pattern": "public/**/js/*.js",
      "reloadServer": false
    }
  ],

//...
Enter fullscreen mode Exit fullscreen mode

Configure Laravel Mix

Now it's time to install the beloved laravel-mix, but how? Thankfully there's a provider for that, specifically implemented for Adonis.js v5, by Wahyu Budi Saputra
. Let's install the package and its dependencies:

npm i adonis-mix-asset && npm i --save-dev laravel-mix
Enter fullscreen mode Exit fullscreen mode

After that, invoke the corresponding ace command to configure the provider for us.

node ace invoke adonis-mix-asset
Enter fullscreen mode Exit fullscreen mode

Done! A webpack.mix.js file has been created at the root of your project. Open it and see all the default configuration. It's a common laravel-mix file, ha? Replace the current configuration with the following code:

const mix = require('laravel-mix')
const path = require('path')

// NOTE: Don't remove this, Because it's the default public folder path on AdonisJs
mix.setPublicPath('public')

mix
  .js('resources/vue/main.js', path.resolve(__dirname, 'public/js'))
  .webpackConfig({
    context: __dirname,
    node: {
      __filename: true,
      __dirname: true,
    },
    resolve: {
      alias: {
        '@': path.resolve(__dirname, 'resources/vue'),
        '~': path.resolve(__dirname, 'resources/vue'),
        '@sass': path.resolve(__dirname, 'resources/assets/sass'),
      },
    },
  })
  .sass('resources/assets/scss/app.scss', path.resolve(__dirname, 'public/css'))
  .options({
    processCssUrls: false,
  })
  .vue() // Magic here!!
Enter fullscreen mode Exit fullscreen mode

What we're doing is simple. We want to load our entry Vue.js file from resources/vue/main.js and expose it to the public directory. We do the same for our SCSS files which reside under resources/assets/scss/. We also created aliases for Webpack, so we'll able to use @/components/HelloWorld.vue later in our SFCs. Feel free to have a look at the package documentation or Laravel Mix if you're new to it. The last line of code specifies that we want to use Vue.js Single File Components so it'll install required dependencies as we run laravel-mix. You also don't want to version control those dirty files created by laravel-mix so adding them to your .gitignore would be a wise move:

mix-manifest.json
hot
public/js/*
public/css/*
Enter fullscreen mode Exit fullscreen mode

Bring Vue.js to the game

For a clean Vue.js app we need an SFC compiler and a few extra packages like sass, sass-loader, vue-loader, etc. Although all these packages will be installed for you automatically by laravel-mix, I just list them here if you want to take a look at them. Let's install them all in one go:

npm i vue vue-router && npm i -D sass sass-loader vue-loader vue-template-compiler autoprefixer postcss
Enter fullscreen mode Exit fullscreen mode

Hmmm... good! Now go and delete all files inside resources/views directory and instead create a new file called index.edge there, and fill it with this content:

<!-- resources/views/index.edge -->

<!DOCTYPE html>
<html lang="en">
<head>
  <link rel="stylesheet" href="{{ mix('/css/app.css') }}">
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
</head>

<body>
  <h1 class="center">
    This is index.edge file
  </h1>

  <div id="app"></div>
  <script src="{{ mix('/js/main.js') }}"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Look how we're referring to the generated files by Laravel Mix using mix() helper. Also, we created an #app container in which our Vue.js app will be mounted. We also want to put the Vue.js app in a separate directory to be as neat as possible, so:

mkdir -p ./resources/vue/
Enter fullscreen mode Exit fullscreen mode

In the vue directory, create the following structure:
Alt Text

Now it's time to fill these files with some boilerplate. I go get some coffee, and you just place the codes below in their corresponding files:

<!-- resources/vue/App.vue -->

<template>
  <router-view></router-view>
</template>

<script>
export default {
  name: 'App',

  mounted() {
    console.log('App has been mounted!!!')
  },
}
</script>

<style lang="scss">
a {
  border: 1px solid black;
  width: 100px;
  background: gray;
  padding: 5px 10px;
  text-align: center;

  &.active {
    background: tomato;
  }
}
</style>
Enter fullscreen mode Exit fullscreen mode
// resources/vue/main.js

import Vue from 'vue'
import router from './router/index'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
})
Enter fullscreen mode Exit fullscreen mode
// resources/vue/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home,
    },
    {
      path: '/about',
      name: 'About',
      component: () => import('@/views/About.vue'),
    },
    // Should be the last route to handle 404
    {
      path: '*',
      name: 'NotFound',
      component: () => import('@/views/NotFound.vue'),
    },
  ],
})
Enter fullscreen mode Exit fullscreen mode
<!-- resources/vue/components/HelloWorld.vue -->

<template>
  <div class="hello-world-component">
    <h2>
      {{ message }}
    </h2>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',

  data() {
    return {
      message: 'This is a message from Hello World component',
    }
  },
}
</script>

<style scoped lang="scss">
.hello-world-component {
  width: 70%;

  h2 {
    border: 1px dashed coral;
    background: brown;
    color: white;
    text-align: center;
  }
}
</style>
Enter fullscreen mode Exit fullscreen mode
<!-- resources/vue/views/Home.vue -->

<template>
  <div>
    <h2>{{ homePageMessage }}</h2>

    <hello-world />

    <router-link to="/about">Go to About page</router-link>
  </div>
</template>

<script>
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'Home',

  components: { HelloWorld },

  data() {
    return {
      homePageMessage: 'This is the Home page'
    }
  },
}
</script>
Enter fullscreen mode Exit fullscreen mode
<!-- resources/vue/views/About.vue -->

<template>
  <div>
    <h2>This is the About page</h2>
    <router-link to="/">back To Home page</router-link>
  </div>
</template>

<script>
export default {
  name: 'About',
}
</script>
Enter fullscreen mode Exit fullscreen mode
<!-- resources/vue/views/NotFound.vue -->

<template>
  <div class="not-found-page">
    This is the 404 page. Are you lost?

    <router-link class="go-back-btn" to="/">
      Go Back Home
    </router-link>
  </div>
</template>

<script>
export default {
  name: 'Register',
}
</script>

<style scoped lang="scss">
.not-found-page {
  color: red;
  text-align: center;

  .go-back-btn {
    display: block;
    margin: 10px auto;
    width: 400px;
  }
}
</style>
Enter fullscreen mode Exit fullscreen mode

Finished it? Good! As you may already be noticed we created a typical Vue.js app structure within ./resources/vue/. Now let's talk about routing.

Setup server-side routes

We configured vue-router for client-side routing but we're yet to register server-side routes. We need only 2 of them, cause most of the routing will be handled by vue-router. Open start/routes.ts and add the following:

# ./start/routes.ts

import Route from '@ioc:Adonis/Core/Route'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

// A typical route handler
Route.get('/', async ({ view }: HttpContextContract) => {
  return view.render('index')
}).as('index')

/* A catch-all route handler. If a user hits the address http://example.com/a-route-that-does-not-exist directly in the browser, then our Vue.js app will mount, and routing will be delegated to vue-router.
 */
Route.get('*', async ({ view }: HttpContextContract) => {
  return view.render('index')
}).as('not_found')
Enter fullscreen mode Exit fullscreen mode

The code above is the exact code we're told to do, when using vue-router (but for Adonis.js). The catch-all route will pass the routing to the Vue.js app if a user wants to go to a non-existing route.

What about styling?

Remember the webpack.mix.js file we created earlier? We told Webpack to go compile app.scss file but we haven't created it yet. So, create it under resources/assets/scss/ and copy these lines of code:

// resources/assets/scss/app.scss

@import url('https://fonts.googleapis.com/css2?family=Goldman&display=swap');

* {
  font-family: 'Goldman', cursive;
}
Enter fullscreen mode Exit fullscreen mode

You may want to add more .scss files and import them inside this file to be applied.

Add TypeScript to the cake

For the sake of simplicity, I make another post on how to setup TypeScript with Vue.js. That'll be fun cause having TypeScript both on the front-end and back-end gives you more confidence.

Wiring things up

It's time to see what we just built. Open a terminal hit node ace serve --watch and in another session enter node ace mix:watch. The latter has been added by "adonis-mix-asset" when we invoked its provider. If you want to watch your assets and re-bundle them on the fly, you may use --hot switch. For a production build you can issue this command: node ace mix:build --production.

If you want to look into the source code directly, you can check it out here:
GitHub

Conclusion

We just finished setting up an Adonis.js project with Vue.js front-end, we have used SFCs and SCSS for the good. Also, we separated back-end and front-end to have an opinionated code structure, which all Vue.js developer used to.

And the last sentence, Adonis.js is one of the strongest Node.js frameworks I've worked with. I can surely say, in 2021 we'll hear many good news about it; Enjoy using it.

Top comments (1)

Collapse
 
badasscoder profile image
Badasscoder

Awesome article. It is best in the case when we are using adonis api framework. But when it is web framework then how to use it.

with edge:
@each(post in posts)

with vue js:
can I use [ posts ] vue file.