Laravel Mix Code Splitting

Code splitting is one of webpack's most exciting features. This feature allows you to split your code into various bundles that can then be loaded on demand or in parallel.

Some of you might already be using Mix's vendor extraction with mix.extract().

This article assumes you are using Laravel Mix.

Use Case

Imagine you have a single bundled JS file, typically app.js in Laravel, which is loaded on every page of your website, including code that isn't needed. As the number of pages increases, the bundle can become very large. This is not optimal for web page performance, and we don't want this to happen because it will make your web pages load slowly.

Dynamic import

From the code above, we will always load the variable ex and document id example, even though we might not need that code. So, how can we dynamically load that code when a page has an element with the id "example"?

We need the babel-dynamic-import plugin. Install the plugin:

npm i @babel/plugin-syntax-dynamic-import

Then add the following code to webpack.mix.js:

mix.babelConfig({
  plugins: ['@babel/plugin-syntax-dynamic-import'],
})

The implementation is quite easy. import() only takes one argument, followed by a promise.

import('./example').then((init) => init.default.execute())

The final result will look like this:

import ResponsiveMenu from './ResponsiveMenu'

// Karena setiap halaman responsive kita muat kode ini di setiap halaman
ResponsiveMenu.init()

// Tergantung dari halaman yang dibuka oleh user
// ketika ada halaman dengan element dengan id example jalan kan kode ini
if (document.getElementById('example')) {
  import('./example').then((init) => init.default.execute())
}

Our example.js file will look like this:

const Example = {
  execute() {
    let ex = 'example'
    document.getElementById('example').innerHTML = ex
  },
}

export default Example

Up to this point, we have successfully refactored the code to use dynamic imports! Beyond this, webpack handles everything—yes, everything!

When you run npm run dev, you will see a new file in the output:

Judging by the bundle size, we have successfully split the script as expected.

Unfortunately, 0.js is a somewhat vague filename. Webpack cannot predict chunk names, but we can define them ourselves with magic comments.

import('./example' /* webpackChunkName: "js/example" */).then((init) =>
  init.default.execute(),
)

You don't need to do anything with example.js because webpack will automatically load example.js when the page contains an element with the id "example".

Prefetching Script

Pay close attention to the Network results above. When the user is on page A, the browser doesn't load example.js. Instead, when the user is on page B, webpack automatically instructs the browser to load example.js. When the user navigates to page B, this makes them wait for the browser to download example.js, making page A fast and light but slowing down page B.

To work around this, we can use prefetch, as illustrated below.

"Hey browser, here's a script I might need in the future. If you have some spare time, feel free to fetch it, no rush!"

You might do something like this.

<link rel="prefetch" href="js/example.js" as="script" />

Once again, webpack makes it easier than having to write code like the above; we just need to add the magic comment /* webpackPrefetch: true */.

import(
  /* webpackPrefetch: true */ './example' /* webpackChunkName: "js/example" */
).then((init) => init.default.execute())

Here are the results:

On page A, example.js is not loaded. However, the browser has loaded it in the background. When the user switches to page B, example.js is already prepared for use.

On page B, example.js is loaded from the browser cache. Therefore, page B will feel faster.