Dynamic imports, Code splitting & loading child chunks on Laravel Vapor

Published on Aug 22, 2019

Over the past few days, I've been lucky enough to have been using Laravel Vapor.
So far I've been incredibly impressed with how smooth the development experience has been. The Vapor CLI tool is incredible, everything from pushing and pulling your environment variables, creating an RDS database and triggering a deployment all with just a few keystrokes. It's very clear to me that Taylor put a lot of thought and effort into making sure any developer would enjoy using this tool.

Like any tool, there are things that you need to consider when you decide to build something using it.
I love building single page apps, mostly because I love living in the JavaScript world building my UI.
What can I say? I'm a sucker for hot module replacement.
Building a full JavaScript frontend can get pretty beefy when it comes to your published JavaScript, so I always leverage dynamic imports as much as I can. If you don't know what that is it basically allows you to code split and lazy load your Vue Components to reduce the initial app.js file load. When the this occurs, you end up with what is called a "child chunk". The parent component when mounted, will have a url/path where it can load the child chunk.

So the thing that I ran into? Apps deployed on Vapor can not load their css and js from the public folder like other apps. That is why Vapor uploads your compiled assets to S3 which are then served via CloudFront. If you read the docs (which I didn't at first) you'll see you shouldn't use {{ mix('js/app.js') }} but rather you should rely on the {{ asset('js/app.js) }} helper. Reason for this is that Laravel Vapor will inject an ASSET_URL environment variable which helps the asset helper generate the proper CloudFront path for your compiled assets.

So the problem is, when you compile your js and it gets code split and chunked, there are references to where that "chunked" file is going to be loaded from, and by default that url will be pointing to your Vapor app which will not work. So here's how you fix that.

Since Laravel Vapor injects the ASSET_URL variable into your env, we’ll use this in your JavaScript build to tell webpack where these chunks should load from.

In your webpack.mix.js add the following.

1const mix = require("laravel-mix");
2const ASSET_URL =
3 process.env.NODE_ENV === "production" ? process.env.ASSET_URL + "/" : "/";
6 |--------------------------------------------------------------------------
7 | Mix Asset Management
8 |--------------------------------------------------------------------------
9 |
10 | Mix provides a clean, fluent API for defining some Webpack build steps
11 | for your Laravel application. By default, we are compiling the Sass
12 | file for the application as well as bundling up all the JS files.
13 |
14 */
17 .js("resources/js/app.js", "public/js")
18 .sass("resources/sass/app.scss", "public/css");
20mix.webpackConfig(webpack => {
21 return {
22 output: {
23 publicPath: ASSET_URL
24 },
25 plugins: [
26 new webpack.DefinePlugin({
27 "process.env.ASSET_PATH": JSON.stringify(ASSET_URL)
28 })
29 ]
30 };
We are using a callback which will give us access to webpack itself to get the job done.

These are all the changes you need. Let’s review what we’ve achieved: First, we are setting a ASSET_URL variable that, in production builds aka npm run production will be set to the ASSET_URL path that Vapor will inject when building our assets, otherwise the path will be set to the default path. Next we're configuring the webpack config with a couple things.

  • The output.publicPath which will be set to the ASSET_URL variable we set at the top of our file.
  • We are setting a global constant using the DefinePlugin which will set the ASSET_PATH variable that webpack uses to define where the child chunks will be loaded from.

So now, when we build our production assets with the Vapor CLI, our app will know to load our chunked files from CloudFront rather than our Vapor app!
I hope you find this helpful!