Cross Platform Builds & its merrits

We've recently been developing a cross platform application. In its core, it is not cross-platform as in mobile versus desktop, but moreso one language intertwined with another. Let's walk through our challenges and discoveries when building this application.

Around a year ago, we decided to upgrade our stack to be more maintainable while being effective with development. One of our engineering teams decided it would be smart to use React in a new project we were working on, so it meant that we would need to come up with a way to bridge our old stack to a newer one (with React). The "old" stack was written in PHP, which is using Laravel. This made it more convenient for us to hook up React to PHP.

Traditionally with React, you will see tutorials on how to rehydrate a hybrid application using a notation similar to ReactDOM.toString(myHTML), which happens on the server side. In our case, we had a PHP application already being SSR (Server Side Rendered), so it made us force our hand. Our hand had a high chance of winning, if to put it that way.

Before getting to the details, I want to elevate the fact that we are using webpack to bundle our React application. Another point worth mentioning is that Laravel also uses React to generate scripts. We were lucky. This meant that there was a way to bind the two respective platforms or languages, PHP and JavaScript together.

Our best shot at writing maintainable code at scale and efficiently would be to make webpack output scripts the same way as Laravel did, so that we wouldn't break any existing modules while binding the old stack to a new one. Doing this, made it possible for us to continue on features and projects, while maintaining the application.

Challenges

As we began to upgrade our stack, we hit several problems with asset generation, chunk splitting and long-term caching. With asset generation, we are using the newest version of webpack(v4), which uses mini-css-extract-plugin. To make sure that we get an unique hash to bust browser cache when deploying, we had to make sure that a manifest file picked up the base filename and transformed it to a unique filename. This scenario occured for generating JavaScript too, and at first glance it seemed like it was hard to use webpack-manifest-plugin to generate entry points in PHP.

Why? Laravel uses their higher order API to provide a translation from a file, to a file with a hash. Our team tried to configure webpack manually with webpack-manifest-plugin in order to have complete control over the bundling/script generation stage. As for webpack, we could not use html-webpack-plugin to inject our scripts to PHP, because.. it's PHP.

A solution to this was to trick webpack-manifest-plugin (remember we have full control over webpack at this point), to generate a manifest file with the laravel-mix convention. This way, we could use the built-in API from Laravel to get access of a manifest translation. This would look something like:

filename: '/entry.js' -> manifestFunction('/entry.js')

generatedFileName: 'entry.12912929hash.js'

So that is what we did. We made webpack compile a fake Laravel generated manifest file and used laravel-mix to transform filenames to unique filenames without actually using laravel mix to bundle files. The API to generate these files we're only exposed through laravel-mix and we could not tap into that using traditional JavaScript as this API was platform specific.

In a PHP file, the end result would look like this:

<script src="{{mix('entry.js')}}"></script>

which would transform to:

<script src="entry.[hash].js"></script>

The way we did this in webpack was as follows:

new ManifestPlugin({
			fileName: 'mix-manifest.json',
			map: chunk => {
				chunk.name = '/' + chunk.name;
				return chunk
			},
			writeToFileEmit: true,
			filter: chunk => {
				const NotPHP = chunk.name.indexOf('php') < 0 ? true : false;
				const NotSASS = chunk.name.indexOf('scss') < 0 ? true : false;
				return NotPHP && NotSASS;
			}
		}),

With PHP, chunk splitting is a more advanced problem space and we are hoping to tackle that later by using a plugin that would look much alike the html-webpack-plugin.

Advantages

With complete access to the compilation stage we have more burden of maintaining a configuration file and it might be harder to read than a simplified API for webpack. However, we would also get a more fine grained control over what we should emit to the web and what we shouldn't. This way, we are able to tweak specific options in webpack without having to rely on an API, feature being upgraded in Laravel-Mix or worry about outdated dependencies in our build step. This makes it possible for the engineering team to write efficient code that scales well and we are making sure that we have the latest versions of our transpilation step, which again makes it possible to gain performance wins and generate a clean site.

The engineering team also maintains a Vue stack. Keeping track of different stacks with different build steps are hard, and we managed to link that together with the webpack configuration. The effect of this was that we now have full autonomy to generate whichever front-end stack we want in future use. We are able to remove a stack easily by just removing the entry point in our webpack configuration and we are able to scale better once, and if, we decide to use a new stack or start on a new project.

Conclusion

After bridging PHP and (modern) JavaScript together, we were able to develop code faster while maintaining source code that is not project specific. The engineering team was able to get the latest dependencies, which guuaranteed that we would get the latest compilation optimizations provided by webpack.


Infrastructure