How To Progressively Replace Parts Of A Complex Laravel Web Application With VueJS

Todo-style micro-apps are nice for demo purposes. However, these SPA tutorials won’t help you integrate VueJS into your existing, full-fledged, complex Web app.

Here is a cool and very simple trick I am currently using to slowly integrate the VueJS javascript framework into an existing Laravel Web application. Note that I am not interested (yet) in making the whole app into a Single Page Application (SPA). First, I want to try and extract value by moving a few features from the jQuery mindset to the VueJS, reactive mindset. Then I can make an educated decision on whether to go full VueJS.

The requirements for this little refactoring project are as follows:

  • Minimal to no changes required to the existing Laravel backend
  • No change required to the current Blade templates
  • Reasonable amount of changes needed to integrate VueJS within a Blade template
  • Ability to clearly extract specific, partial features into VueJS Components, each of which will bundle their own HTML template, CSS style & JS logic
  • No need to use Webpack or any server-side precompiling step during dev

1. Link the VueJS framework on each view

This is trivial: simply link the VueJS lib inside your main Blade layout file, in the <head> section. For convenience, during development, I’m using the latest version hosted on a CDN. Once you want to refine the application and prepare it for production, you will probably want to migrate to a server-side compiler such as Mix/Webpack.

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

Then, surround your main view code with a <div> and set a unique ID attribute to it. This tag will be the root element for the VueJS framework. Here I’m using #app.

<body>
    <div id="app">
        <header>
            Nav menu
        </header>
        <main>
            @yield('body')
        </main>
        <footer>
            © {{date("Y")}} App - v. {{config('version.commit')}}
        </footer>
    </div>
</body>

Be sure to set the root element to a <div> and not the <body> tag. Otherwise, VueJS will complain that there are <script> tags inside its root element! By setting the root element to a regular <div>, you can still put <script> tags before </body> as needed.

2. Set up hooks for VueJS and regular JS scripts

The library is now being loaded everywhere, however we don’t want to initialize it except on specific views. This will allow us to keep using <script> tags throughout our app while we progressively migrate from jQuery to Vue.

A pretty simple way of doing this is to set two hooks in the main layout file. One for loading regular scripts from our Blade templates, the other for initializing VueJS and passing data to its instance as needed.

<body>
    <div id="app">
        ...
    </div>

    @yield('vue')
    @yield('scripts')
</body>

3. Make your Blade templates VueJS compatible

First, extract all <script> tags from your template‘s main section (I named it body) into the new scripts section we just added. This is critical, otherwise VueJS will complain that we use scripting on the root element.

Then, load VueJS from the vue section we just added. We don’t need much more than the barebone capabilities for now, so you can simply use the following code:

@section('body')
...
@stop

@section('vue')
<script>
var app = new Vue({
    el: '#app'
});
</script>
@stop

Then, convert your template from Blade to VueJS. This is tricky because of the similarity in the conventions for displaying data: {{var}} in Vue, {{$var}} in Blade. Of course, Blade is processing the template first, so it will crash badly if you use {{var}} because there is (hopefully!) no PHP constant named var. Oops. Use @{{var}} to tell Blade not to parse the brackets and make them remain intact for VueJS to handle.

Converting the rest of the template into VueJS logic should be fairly straightforward, and even pretty fun as it often results in less lines and clearer code. For example, you will need to turn this:

@if($elements->count())
<div>
    @foreach($elements as $element)
    <div>
        <a href="/elements/{{$element->id}}">{{$element->name}}</a>
    </div>
    @endforeach
</div>
@else
<div>
    ...
</div>
@endif

into this:

<div v-if="elements.length">
    <div v-for="element in elements">
        <a v-bind:href="'/elements/'+element.id">@{{element.name}}</a>
    </div>
</div>
<div v-else>
    ...
</div>

Then, pass the required data into VueJS so that it be accessible in your template:

@section('vue')
<script>
var app = new Vue({
    el: '#app',
    data: {
        "elements": {!!$elements->toJson()!!}
    }
});
</script>
@stop

4. Extract functionality into VueJS Components

By now, you should have a functional VueJS template that performs exactly the same as the old Blade template. The last step is progressively extracting parts of it into reusable VueJS components.

Create a components directory under /public/js and a new Element.vue inside it. This setup will allow us to bypass the compilation step and use a small library to load and parse our component file on the go. This is very useful during development, and actually manageable in production for many use cases.

If you need your application to be much faster, that’s when you will want to add these components to your assets pipeline so that they are compiled once server-side. However, once the component has been downloaded and cached by the browser, fetching it again is practically instantaneous.

Here is how your first component might look like:

<template>
    <div>
        <a v-bind:href="'/elements/'+element.id">{{element.name}}</a>
    </div>
</template>
<script>
module.exports = {
    "props": [
        "element"
    ]
}
</script>

Note that we don’t need to escape the brackets anymore, because this is a regular static file that won’t ever be parsed by Laravel’s Blade template engine. Now you can load the component file directly from the Blade template, where you initialize VueJS:

<script>
var app = new Vue({
    el: '#app',
    "components": {
        "element": httpVueLoader('js/components/Element.vue')
    },
    data: {
        "elements": {!!$elements->toJson()!!}
    }
});
</script>
@stop

Magic, right? Well, almost. For this to work, you still need to link the http-vue-loader library that provides this much needed httpVueLoader function:

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/http-vue-loader"></script>

Leave a Reply