single-spa-alpinejs
single-spa-alpinejs is a helper library for mounting alpinejs components as single-spa applications or parcels.
Installation
npm install --save single-spa-alpinejs
# or
yarn add single-spa-alpinejs
Alternatively, you can use single-spa-alpinejs from a CDN as a global variable:
<script src="https://cdn.jsdelivr.net/npm/single-spa-alpinejs"></script>
Note that you might want to lock down the package to a specific version. See here for how to do that.
Usage
- There are three ways the you can define AlpineJS components as single-spa applications or parcels.
1 - Template Only
- The simplest way where the template contains all the required data and initialization logic (including
x-data
andx-init
) as part of the dom. The template is provided via the options attributetemplate
2 - Template with externally defined x-data
- You could also provide
x-data
externally and the helper will add it to the component.- The
x-data
can be provided in the following forms (via the options attributexData
)- an object
- a function that returns an object
- a function that returns a promise which resolves with an object.
- The
3 - Template with externally defined x-data
with x-init
-
You can also provide
x-init
externally along with thex-data
and the helper will add it to the component. -
The
x-init
can be provided in the following forms (via the options attributexInit
) and needs to be a function. -
Please note the
xData
attribute must be provided otherwise thexInit
attribute will be ignored. -
The sample below references the example from the Alpine Toolbox - Alpine JS and fetch() and demonstrates how you can use the
xInit
andxData
attributes to create an AlpineJS application .
Usage Examples
1 - Template Only
import singleSpaAlpinejs from "single-spa-alpinejs";
const alpinejslifecycles = singleSpaAlpinejs({
template: `
<div class="rounded overflow-hidden shadow-lg font-sans p-1 m-1"
x-data="{ open: false }">
<div class="font-bold p-1">Example for x-show attribute</div>
<button class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold
hover:text-white py-2 px-4 border border-blue-500
hover:border-transparent rounded"
@click="open = !open">Open/Close</button>
<div x-show="open" class="text-4xl">
Hey, I'm open
</div>
</div>`,
});
export const bootstrap = alpinejslifecycles.bootstrap;
export const mount = alpinejslifecycles.mount;
export const unmount = alpinejslifecycles.unmount;
Via cdn
Example usage when installed via CDN:
- The usage is similar and once the library is loaded it will be available globally and accessed via the
window
object.
const alpinejsApp = window.singleSpaAlpinejs({
template: `
<div class="rounded overflow-hidden shadow-lg font-sans p-1 m-1"
x-data="{ open: false }">
<div class="font-bold p-1">Example for x-show attribute</div>
<button class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold
hover:text-white py-2 px-4 border border-blue-500
hover:border-transparent rounded" @click="open = !open">Open/Close</button>
<div x-show="open" class="text-4xl">
Hey, I'm open
</div>
</div>`,
});
singleSpa.registerApplication({
name: "name",
app: alpinejsApp,
activeWhen: () => true,
});
2 - Template with externally defined x-data
import singleSpaAlpinejs from "single-spa-alpinejs";
const alpinejslifecycles = singleSpaAlpinejs({
template: `
<div class="rounded overflow-hidden shadow-lg font-sans p-1 m-1">
<div class="font-bold p-1">Example for x-show attribute</div>
<button class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold
hover:text-white py-2 px-4 border border-blue-500
hover:border-transparent rounded" @click="open = !open">Open/Close</button>
<div x-show="open" class="text-4xl">
Hey, I'm open
</div>
</div>`,
xData: { open: false },
});
export const bootstrap = alpinejslifecycles.bootstrap;
export const mount = alpinejslifecycles.mount;
export const unmount = alpinejslifecycles.unmount;
3 - Template with externally defined x-data
with x-init
import singleSpaAlpinejs from "single-spa-alpinejs";
const appTemplate = `
<div class="w-full h-full text-gray-800">
<h1 class="mt-0 mb-3 font-light text-3xl" x-text="title"><!-- title text --></h1>
<p class="text-xl text-gray-600 font-light mb-4" x-html="intro"><!-- intro text --></p>
<div class="flex flex-wrap -mx-2 pb-8">
<!-- begin: user card -->
<template x-for="user in users" :key="user.id">
<div class="w-full md:w-1/2 lg:w-1/3 xl:w-1/4 h-auto font-light">
<div class="flex bg-white rounded-lg shadow-md m-2 border-l-4
border-white hover:shadow-2xl hover:border-pink-500
cursor-pointer relative">
<div class="p-4 pr-6 leading-normal">
<div class="font-medium text-xl truncate" x-text="user.name"></div>
<div class="truncate uppercase text-xs text-gray-500 font-semibold
pb-2 tracking-widest" x-text="user.company.name"></div>
<div class="" x-text="user.phone"></div>
<a class="text-blue-600 hover:text-blue-700 mr-4 block"
x-bind:href="'mailto:' + user.email" x-text="user.email"></a>
<a class="text-blue-600 hover:text-blue-700 block"
x-bind:href="'https://' + user.website" x-text="user.website"></a>
</div>
</div>
</div>
</template>
<!-- end: user card -->
</div>
</div>
`;
const appDataFn = ({ title, name }) => ({
title,
intro:
'Implement a simple <code class="text-md text-pink-600">fetch()</code> request to render a list of items using Alpine.js :)',
users: [],
open: false,
name,
});
const appXInitFn = (id) => {
return fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => (document.querySelector(`#${id}`).__x.$data.users = data));
};
const opts = {
template: appTemplate,
xData: (data) => appDataFn(data), // pass props to x-data
xInit: appXInitFn,
};
const alpinejslifecycles = singleSpaAlpinejs(opts);
export const bootstrap = alpinejslifecycles.bootstrap;
export const mount = alpinejslifecycles.mount;
export const unmount = alpinejslifecycles.unmount;
API / Options
single-spa-html is called with an object that has the following properties:
template
(required): An HTML string or a function that returns a string. The function will be called with the single-spa custom props. The returned string is injected into the DOM during the single-spa mount lifecycle.domElementGetter
(optional): A function that returns the dom element container into which the HTML will be injected. If omitted, a default implementation is provided that wraps the template in a<div>
that is appended todocument.body
.xData
(optional): An object or a function or a function that returns a promise.The returned string is injected into the DOM as thex-data
attribute during the single-spa mount lifecycle.xInit
(optional): A function or a function that returns a promise. The function provided is added to the global scope and the function initiation along with the root dom element id as a parameter is injected into the DOM as thex-init
attribute during the single-spa mount lifecycle. Please note thexData
attribute must be provided otherwise thexInit
attribute will be ignored. The function you providexInit
xData and xInit Handling
-
This section covers the details of how
xData
andxInit
option attributes are processed by the single spa helper. -
Consider the example below
const appDataFn = () => { open: false, loading: true }
const appXInitFn = (domId) => {
console.log('Hello from appXInitFn');
// domId provides access to the parent dom element where x-data and x-init are defined
document.querySelector(`#${domId}`).__x.$data.loading = false
}
const opts = {
template: appTemplate, // base template
xData: (data) => appDataFn(data), // pass props to x-data
xInit: appXInitFn, // x-Init function
};
const alpinejsApp = singleSpaAlpinejs(opts);
singleSpa.registerApplication({
name: 'myapp',
app: alpinejsApp,
activeWhen: () => true,
});
-
The helper does the following
- Adds the template to the dom wrapped in
parent dom element
with and id that has a prefix ofalpine
. In this case it will beid='alpine-myapp'
- Attaches a resolved
xData
as a stringx-data="{ "name": "myapp" ,"open": false }"
to theparent dom element
. - It will make the user defined
appXInitFn
available globally as an attribute ofwindow.singleSpaAlpineXInit
and will be accessible via variablewindow.singleSpaAlpineXInit.myapp
- Attaches a resolved
xInit
as a string that calls the globally defined variablex-init="singleSpaAlpineXInit.myapp('alpine-myapp')"
to theparent dom element
. - Note that this also passes
id
of theparent dom element
which can then be used to access the alpine data elements to update the state as required.
Special characters in the application names
- You may have special characters in the application name for example
@my/app
. See the example below
singleSpa.registerApplication({
name: "@my/app",
app: alpinejsApp,
activeWhen: () => true,
});-
The single spa helper converts these to valid
global
function names byreplacing
all the special characters
with underscores (_
). This does not require any special handling from the user as the helper takes care of this internally -
In the above case the
xInit
dom element would look like the followingx-init="singleSpaAlpineXInit._my_app('alpine-@my/app')"
where thexInit
function is available as aglobal
variable_my_app
.
- Adds the template to the dom wrapped in