tools

Code-splitting and Lazy-loading Alpine Components using Async Alpine

June 2, 2023

//

3 min read

Code-splitting and Lazy-loading Alpine Components using Async Alpine

With AlpineJs, you can create components in two ways: using inline HTML markup or the Alpine.data() function to define a component globally and use it throughout the entire project.

Each approach has its pros and cons. For example, It is not possible to cache the inline components. And are only reusable if you use your template engine to create reusable components. The benefit of this approach is that you don't need a build step; you can add the AlpineJS to your HTML markup using CDN and start building your application.

On the other hand, using Alpine.data() to declare the components, you can reuse them and cache them like other static files. This option has some downsides too. You must set up a bundler like Vite or Webpack to bundle the codes. And if you have a big project, your bundled file size can grow over time.

To fix these issues, you can use Async Alpine to split your components into individual files and load them only when needed.

Installation

You can use Async Alpine using CDN or npm:

1<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/async-alpine.script.js"></script>
2<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>

When you want to use CDN, you must load Async Alpine before loading the Alpine itself.

Install from npm with:

1npm install async-alpine

Import it into your bundle alongside Alpine and run AsyncAlpine.init(Alpine) and AsyncAlpine.start() before Alpine.start():

1import AsyncAlpine from 'async-alpine';
2import Alpine from 'alpinejs';
3AsyncAlpine.init(Alpine);
4AsyncAlpine.start();
5Alpine.start();

Usage

To use Async Alpine, you must create your components in individual files using ES Module format like the code below:

1// publicly available at `/assets/path-to-component.js`
2export default function myComponent() {
3 return {
4 message: '',
5 init() {
6 this.message = 'my component has initialised!'
7 }
8 }
9}

After creating components in separate files, you can load them using one of these four options depending on your environment: URL Components — most straightforward implementation declaring component URLs in JS; Data Components — for building tools with dynamic import/code-splitting like Vite, WebPack, Rollup, and Parcel; Alias Loading — if your components are in a very consistent file-system structure; Inline Components — you need to declare components in HTML to get the asset URL, for example, using an asset CDN or for Shopify sites.

After loading components, you can use them asynchronously by adding x-ignore and ax-load with your x-data attribute like the code below:

1<div
2 x-ignore
3 ax-load="visible"
4 x-data="myComponent"
5>
6 <div x-text="message"></div>
7</div>

Loading Strategies

In the code above, you can see that we used a value in the ax-load attribute. You can change the loading strategy by setting these values in the ax-load

Eager

It's the default behavior; if you do not provide any value, the component will load in this mode. In eager mode, the component will start loading immediately on page load. It will still load asynchronously in the background but with the highest priority possible.

This mode is useful when you want a component to be interactive as soon as possible.

Idle

It uses requestIdleCallback, where it's supported to load when the main thread is less busy. Components not critical to the initial load are bests for this scenario.

Visible

It uses IntersectionObserver only to load when the component is in view, similar to lazy-loading images.

Media

The component will be loaded when the provided media query is true.

Event

The component will be loaded once it receives the async-alpine:load event on the window. Provide the id of the component in detail.id.

1<!-- on a button click using Alpine's $dispatch -->
2<button x-data @click="$dispatch('async-alpine:load', { id: 'my-component-1' })">Load component</button>
3<div
4 id="my-component-1"
5 x-ignore
6 ax-load="event"
7 x-data="componentName"
8></div>
9 
10<!-- load our component after `another-library-init` has loaded -->
11<script>
12window.addEventListener('another-library-init', () => {
13 window.dispatchEvent(new CustomEvent('async-alpine:load', {
14 detail: {
15 id: 'my-component-2'
16 }
17 }))
18})
19</script>
20<div
21 id="my-component-2"
22 x-ignore
23 ax-load="media (prefers-reduced-motion: no-preference)"
24 x-data="componentName"
25></div>

For more information and more detailed examples, you can check out the official document of Async Alpine here.