Image created with resources from Freepik (image, image, image)

Our journey from Symfony to Vue — Part 3

Mario Sanz
Jeff Tech
Published in
6 min readJun 26, 2019

--

In part 1 of this series, we detailed why we decided to move all the Mr Jeff web apps from Symfony to a Javascript frontend stack, why we chose Vue, and how we planned to tackle the migration using Encore. In part 2, we went through the step-by-step configuration, then created a simple test app, and finally migrated one of our real views from its old Twig form to a shiny new Vue app running inside our Symfony project.

However, we ended part 2 by raising a few questions about the scalability and maintainability of that approach. Do we really need to have a separate root app for every new view we create? Can our apps communicate between them? How can we take advantage of hot reloading and Babel plugins? Can we work with our existing env variables and Docker setup?

In this chapter, we’ll address the first of those concerns (spoiler alert, the answer is no!). And then, in the next and final part of this story, we’ll try to answer most of those questions in order to improve the structure and tooling of our project.

All these giant tentacles around our boat are the only thing standing between us and the treasure island. So, get your kraken-sized fishnets ready, and remember — the X marks the spot!

The ocean is dark and full of krakens: Symfony router vs Vue router

So, first things first. We already migrated some of our pages, and we now have a bunch of Vue views (hehe) scattered across our Symfony project. But for each of those apps, we currently need the following:

  • A Twig view to mount the app
  • A Symfony controller to catch a route, render the Twig view, and pass data if needed
  • An entry point declaration in our Encore config
  • An actual entry point (js file) to create the app and mount it to the view
  • A root component for the app

That’s four new files for each Vue app we want to write — It’s easy to see this won’t be very scalable once the number of apps starts to grow. Let’s see how vue-router, which is the official routing library for Vue, can help us.

But, hey! Why another router? I already have the Symfony router!, you might be thinking. The main difference is the Symfony router operates in the server, while the Vue router runs in the browser. The easiest way to explain is to understand what’s currently happening every time one of our Vue app loads, step by step:

  1. The user enters a url (or clicks on a link that goes to one).
  2. On the server, Symfony resolves the route, and executes its associated controller.
  3. The controller renders a view and sends the HTML to the browser. This HTML doesn’t contain our app yet, only its root element and a link to the JS needed to create the app.
  4. The browser parses the HTML and downloads all the needed CSS and JS files.
  5. When the browser has all the resources, it executes our app’s JS, which uses Vue to render the app and mount it into the root element, making it visible on the screen.
  6. If we change the URL, we’ll start again from step 1.

This would be fine if we only had to do this once, but currently we’re doing this every time we change routes. If we want to go from /users to /users/123, we need to do all this process again, even if we have Vue apps for both views. We’ll go through very similar, almost empty controllers and Twig views, we’ll download Vue and other dependencies again (since it’s included in each app’s JS), and of course we’ll have a page reload.

By using vue-router, what we will be doing is adding a second router that runs in the browser. In order for that to work, we’ll have to make some changes. Before jumping into the code, here’s what we’ll end up with:

  • Only one controller, which will capture all the routes that contain Vue apps.
  • Only one Twig view, which will create and mount a “shell app”.
  • Only one Encore entry point
  • Only one bundled JS file, containing the logic of all our apps.

Sounds promising! Let’s see how to do that.

Taming the kraken: Combining all apps into one with vue-router

With that in place, let’s write some code. First, instead of having a different controller for every route, we will write a VueController.php that catches all the routes where we want to have Vue apps. It should look something like this:

Easy, we’ll catch here all the routes we need, and always render the same template. If any view needs some initial data (the user, in our case), we’ll pass it all from here. Now, whenever we create a new Vue app, we just need to add a @Routeannotation here.

Next, that vue.html.twig should be very similar to the templates we wrote in part 2. Similarly to the controller, the only difference is that this time we need to write to the browser any initial data that any of our apps may need, since all of them will be using this template. Here we’re illustrating two methods for passing that data (browser storage and data attributes), but you don’t need to use both.

Also, we’ll remove all but one of the entry points we have in our webpack.config.js, going back to its initial state, when we only had our test app:

Now, before dealing with that main.js, let’s add vue-router to the project:

$ yarn add vue-router

Finally, let’s take a look at our new main.js file. Again, it’s basically the same we had when we were writing our test component, but here we will also be importing the vue-router we just installed, and a router config object that we’ll see in detail later. The whole file should look like this:

Up until now, we haven’t really specified any routing anywhere. So, where does all the magic happen? Of course, it’s in that router object we’re now passing to the app. Let’s create a src/router/index.js file and add something like this:

There it is! All those PageSomething components are all the individual apps that we were handling via separate apps. Now we’re loading them all in this app, and assigning each one to its route.

From now, every time we want to add a new view to our project, all the Symfony code we have to touch is adding one line to VueController.php, and maybe passing some initial data in vue.html.twig if we need that. Apart from that, it’s all about creating new PageSomething components, and of course adding them to the Vue router. Hooray!

Oh, just one small detail: please note that the routes declared in both routers must match. If in the Symfony router we declare /orders/{id}, in the Vue router you need to declare /orders/:orderId. You can use any name you want for the path parameters, as we did, but the rest of the path should be the same in both.

The kraken is our pet now: Just minor issues ahead

Phew, that took long to explain, but I hope it was worth it! In this chapter, we saw how to manage all the separate Vue apps we had from one same place (the Vue router), and not only that — the change from one Vue app to another is now much faster because most of our JS is already downloaded, and also our UX is better since we’re avoiding the page refresh.

From here, the migration of our Symfony views will be easier and faster, as we’ll be avoiding a lot of boilerplate and focusing on Vue code most of the time. You see that through your spyglass? Land ahoy!

In the next — and last, we promise! — part of the story, we will fix some of the flaws our current setup has, add a few new tools, and make some improvements to the developer experience. None of those changes are strictly necessary, but most of them are highly recommended for a better and more maintainable project, so stay with us!

[Update: Sorry, but I was moved to another project before finishing this one, so the promised last part might never come. I hope you enjoyed these 3 though!]

--

--