5

Micro KISSing PHP with Vue

 1 year ago
source link: https://medium.com/scoro-engineering/micro-kissing-php-with-vue-2ed4b6f91a06
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Paper figures of PHP and Vue high-fiving
Paper people photo created by freepik — www.freepik.com

Micro KISSing PHP with Vue

Or, transitioning from PHP-rendered pages to Vue with KISS micro-frontends

The codebase of Scoro is more than 10 years old, in other words, we have amassed quite a bit of “senior” (not legacy, per se) code under our belt. The vast majority of the application is written in PHP with our own bespoke framework. All pages are rendered fully on the server with PHP, and interactivity is added onto the page with jQuery. Such an old-school tech stack brings with it quite a few problems though, and it’s getting more and more difficult to contribute to the monolithic application, especially as a front-end developer. Nowadays, there are many modern libraries and tools which make working on the front-end a breeze.

There are countless blog posts and tutorials on how to get started with a new, greenfield project using fancy frameworks, libraries, and tools, but there isn’t much out there about integrating these tools into an existing, older application. Since this is exactly the type of thing we’re doing in Scoro, it seems like the perfect topic for us to write about. Hopefully, sharing our journey will help a few other older codebases become more modern, easier to maintain, and more fun to work in.

In this blog post, we’ll describe how our older front-end set-up works, what our goals are, and what steps we are taking in order to reach them.

Our senior front-end

As mentioned above, the application is written mostly in PHP and jQuery. We have our own custom component framework which allows us to structure our application into components. Each component usually consists of multiple parts: PHP code that renders the HTML, PHP code that handles the component data (for example, connecting to the database), a JavaScript ES6 class that handles the interactivity, and some SCSS which declares the styles for the component. Internally, we call this older project “the monolith”, so I will use that term to refer to it in this article.

This approach isn’t inherently bad, but it requires a lot of discipline to ensure low coupling of the various concerns. In our case, we don’t use a separate templating engine, so our HTML rendering logic is often coupled to the business logic. This high coupling between the back-end and the front-end requires all our front-end developers to be familiar with (and willing to use) PHP for changing the rendering logic. The coupling also causes a few other problems for us: it is difficult to split work between the front-end and the back-end, there is no easy way for us to work on the front-end without having the back-end completed first, deployments of the front-end are coupled to the back-end, and rendering logic is duplicated between PHP and JavaScript.

It also needs to be mentioned that working with a modern front-end project is extremely convenient and fun. State-of-the-art tools like development servers, hot module reloading, type checking, front-end tests, scoped styles, and so on, are quite difficult, if not impossible, to use in our project. Additionally, vanilla JavaScript makes it quite tough to write structured code. Frameworks like Vue make it much easier to build interactive user interfaces than jQuery. Consequently, all of this also makes it more challenging to attract new talent to Scoro.

Separating the front-end from the back-end

Most of the problems highlighted above can be fixed by having a clear separation between the front-end and the back-end. A single-page application (SPA) approach would work great for us, it would allow us to use Vue for the front-end and have a GraphQL API for the back-end (stay tuned for a future post on why we chose this technology stack). Such a set-up would solve the problems of high coupling and would also make our technical stack much more appealing for new joiners. In order to get to the SPA dream, we could convert everything to the new approach one page at a time.

However, one thing about Scoro is that the application is extremely complex, with a lot of business logic connecting different domains of the application. Each page in the application includes a lot of functionality and provides users many shortcuts to get to the data they need quickly. This means that converting whole pages to a SPA approach would take a very long time and would block us from developing new features for many months. We needed to find a way to move towards the modern front-end goal in an incremental way, without sacrificing feature delivery, and avoiding big bang releases.

Scoro “micro-frontends”

The great thing about front-end frameworks like Vue is that they do not require an “all-or-nothing” approach. Vue components can be easily rendered onto the page like so:

import { createApp } from 'vue'createApp(MyComponent).mount('#app')

Conventionally, for new applications, there is only a single root component rendered, which handles displaying the whole user interface. However, Vue doesn’t limit the number of components that can be rendered onto the page like this.

With this in mind, we can actually convert small parts of our pages to Vue instead of converting the whole page in one go. For example, we can have most of the page be rendered by the monolith PHP, but have the navigation bar be rendered as a Vue component.

We call these Vue components that we embed onto the PHP-rendered pages “micro-frontends” (MFEs) (even though, technically, they might not be considered that by all definitions).

Chess pieces assigned in a micro-frontendly fashion
Diversity and inclusion photo created by freepik — www.freepik.com

How it works

We try to keep our implementation of the micro-frontends as simple as we can. There is only a single project for the micro-frontends, which includes a number of Vue components, and an entry point file, which contains the logic for orchestrating the micro-frontends.

The Vue components are quite standard when it comes to Vue applications. We use TypeScript and the composition API when writing components. Some utilities are available which we can use to communicate between the micro-frontends and the monolith JavaScript. In addition, we have an internal component library, called Scoro UI, which includes reusable building blocks of our user interfaces (expect to hear more about that in a future article).

The entry point file is where the main logic of the micro-frontends is defined.
It configures the used Vue plugins, global utilities like Apollo Client for making GraphQL requests, Pinia for state management, and vue-i18n for translations. Additionally, the available MFEs are defined and a function to render an MFE onto the page is exported.

Any Vue component can be exported as an MFE in a configuration object:

const microFrontends = {
AppHeader: () => import('@/components/AppHeader.vue'),
ProjectPhases: () => import('@/components/ProjectPhases.vue'),
}

Once the MFEs have been defined, a render function allows them to be rendered onto the page by the monolith JavaScript:

window.__MFE_UTILS__.render({
element: '#mfe-header',
component: 'AppHeader',
})

The render function definition is quite simple, it just renders the Vue
component into the given element and provides the various utilities mentioned above to the component:

import pinia from './pinia'
import { i18n } from './i18n'const apollo = createApolloClient(store)window.__MFE_UTILS__.render = async ({ element, component, options = {} }) => {
const comp = microFrontends[component] return new Vue({
el: element,
setup() {
provide(DefaultApolloClient, apollo)
},
render: (h) => h(comp, options),
pinia,
i18n,
})
}

As you can see, there’s nothing too special going on in our micro-frontends setup, we’re just making use of the built-in capabilities of Vue. The simple implementation also means that new joiners can quickly grasp the architecture without having to worry about the deep technical details that are going on under the hood.

Embroidery with the text “Keep It Simple Stupid”
Keep It Simple Stupid by Hey Paul Studios (CC BY 2.0)

Communication

More often than not, there needs to be communication between the micro-frontends and the monolith JavaScript, or vice-versa, or even MFEs themselves. We have two tools at our disposal that we can use for this purpose.

The first tool we can use is something Vue supports right off the bat: events. Using our custom MFE utilities, we can emit a Vue event from inside a micro-frontend and then listen to it in the monolith JavaScript:

// In a Vue component
const { rootEmit } = useMfeUtils();const showEmailModal => (personId, email) => {
rootEmit('show-send-mail-modal', {
personId,
email,
});
};// Rendering the micro-frontend in the monolith JavaScript
window.__MFE_UTILS__.render({
element: 'mfe-header',
component: 'AppHeader',
options: {
on: {
'show-send-mail-modal': (payload) => {
// The `popModal` function is defined in the monolith JavaScript
popModal('sendMail', payload);
},
},
},
});

The second tool we have available is a global state management library. We use Pinia for this. The Pinia instance is available in the __MFE_UTILS__ global object that we can access anywhere in the monolith JavaScript:

window.__MFE_UTILS__.pinia.projectsView.$patch({
myValueToUpdate: true,
});

On the MFE entry point side, we configure the Pinia instance and add it onto the global object:

window.__MFE_UTILS__.pinia = {
projectsView: projectsViewStore,
};

We use Pinia to communicate between the micro-frontends and from the monolith to the MFEs.

Since we don’t have a large number of micro-frontends yet, we haven’t run into issues with the above solutions. It might happen that we will need something more sophisticated in the future, but the current approach seems to work well for now.

What’s next?

So far, we only have a handful of micro-frontends that we are using in production, but once we have converted more of the application to the new approach, we’ll be able to convert whole pages into Vue. At that point, it won’t take much effort to have a hybrid solution which treats some pages as a single-page application and delegates rendering of older pages to the previous PHP approach.

Additionally, we currently have all of the micro-frontends in a single project. This means that they are not necessarily independent from a code perspective, and it’s not possible to deploy a single micro-frontend at a time. Because of this, it could be said that they are not actually micro-frontends. Using Webpack Module Federation would allow us to split each micro-frontend into its own project and deploy them individually. This would be a nice next step to take, and we are deciding when the best moment to take it would be.

Stay posted for the next steps on our journey!

Hopefully this overview of how we’re transitioning to a more modern tech stack has been interesting and has given you ideas on how to approach similar initiatives in the future. If this seems interesting and you’d like to contribute, check out our open positions at https://www.scoro.com/careers/.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK