Skip to content

Commit

Permalink
Merge pull request #17 from Accudio/feat/parents-strategy
Browse files Browse the repository at this point in the history
`parent` strategy
  • Loading branch information
Accudio authored May 25, 2022
2 parents def78a7 + 12a59ee commit 13a8ea9
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 19 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Load Alpine Components asyncronously. Allows for code splitting and lazy loading
- [visible](#visible)
- [media](#media)
- [event](#event)
- [parent](#parent)
- [Combine strategies](#combine-strategies)
- [Advanced Options](#advanced-options)
- [Current Limitations](#current-limitations)
Expand Down Expand Up @@ -225,6 +226,32 @@ window.addEventListener('another-library-init', () => {
></div>
```

### `parent`

The component won't be loaded until all ancestor components with `ax-load` have been loaded.

This would be used for nested components, where the order of initialisation matters. With `parent`, all `ax-load` components in the ancestor tree must be loaded before this component will load. This matches Alpine's expected inheritance and execution order so nested components should work as expected.

Consider whether components would be best combined rather than relying on nesting. Nesting dynamically loaded components can be fragile and can produce download waterfalls negative for performance.

Usage examples:

```html
<div
ax-load="visible"
ax-load-src="/assets/parent-component.js"
x-data="parentComponent"
>
<div
ax-load="parent"
ax-load-src="/assets/child-component.js"
x-data="childComponent"
>
<span x-show="parentData">I have access to the data from parentComponent!</span>
</div>
</div>
```

### Combine strategies

Strategies can also be combined by separating with pipe `|`, allowing for advanced and complex code splitting:
Expand Down
51 changes: 46 additions & 5 deletions dist/async-alpine.cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,20 @@ var Component = class {
node: root,
attributes: getAlpineAttrs(root, this.instance.config)
};
root.setAttribute(this.instance.config.id + this.instance.config.id, this.id);
root.setAttribute(this.instance.config.prefix + this.instance.config.id, this.id);
this.children = [...root.querySelectorAll("*")].filter((el) => getAlpineAttrs(el, this.instance.config).length).filter((el) => !el.hasAttribute(this.instance.config.prefix + this.instance.config.root)).filter((el) => el.closest(`[${this.instance.config.prefix}${this.instance.config.root}]`) === root).map((node) => ({
node,
attributes: getAlpineAttrs(node, this.instance.config)
}));
this.parents = [];
let cursor = root;
do {
cursor = cursor.parentNode.closest(`[${this.instance.config.prefix}${this.instance.config.root}]`);
if (!cursor)
break;
const parent2 = instance.components.find((component) => component.root.node === cursor);
this.parents.push(parent2.id);
} while (cursor);
}
deactivate() {
disableAttributes(this.root, this.instance.config);
Expand All @@ -73,15 +82,15 @@ var Component = class {
this.activate();
}
async getModule() {
if (this.instance.cache[this.src]) {
return this.instance.cache[this.src];
if (this.instance.moduleCache[this.src]) {
return this.instance.moduleCache[this.src];
}
const module2 = await import(
/* webpackIgnore: true */
this.src
);
let whichExport = module2[this.name] || module2.default || Object.values(module2)[0] || false;
this.instance.cache[this.src] = whichExport;
this.instance.moduleCache[this.src] = whichExport;
return whichExport;
}
activate() {
Expand All @@ -94,6 +103,11 @@ var Component = class {
child.node.removeAttribute(`${this.instance.config.alpine.prefix}cloak`);
}
this.status = "loaded";
window.dispatchEvent(new CustomEvent("async-alpine:loaded", {
detail: {
id: this.id
}
}));
}
};

Expand Down Expand Up @@ -138,6 +152,23 @@ var media = (component, requirement) => {
};
var media_default = media;

// src/core/strategies/parent.js
var parent = (component, parentId, parentStatus) => {
return new Promise((resolve) => {
if (parentStatus !== "unloaded") {
return resolve();
}
window.addEventListener("async-alpine:loaded", (e) => {
if (e.detail.id !== parentId)
return;
if (component.status !== "unloaded")
return;
resolve();
});
});
};
var parent_default = parent;

// src/core/strategies/visible.js
var visible = (component, requirement) => {
return new Promise((resolve) => {
Expand Down Expand Up @@ -176,7 +207,8 @@ var idIndex = 1;
var AsyncAlpine = (Alpine, opts = {}) => {
const instance = {
config: config_default,
cache: {}
components: [],
moduleCache: {}
};
if (opts.prefix)
instance.config.prefix = opts.prefix;
Expand All @@ -189,6 +221,9 @@ var AsyncAlpine = (Alpine, opts = {}) => {
return;
for (let root of roots) {
const component = new Component(root, instance, idIndex++);
instance.components.push(component);
}
for (let component of instance.components) {
component.deactivate();
const requirements = component.strategy.split("|").map((requirement) => requirement.trim()).filter((requirement) => requirement !== "immediate").filter((requirement) => requirement !== "eager");
if (!requirements.length) {
Expand All @@ -212,6 +247,12 @@ var AsyncAlpine = (Alpine, opts = {}) => {
if (requirement === "event") {
promises.push(event_default(component));
}
if (requirement === "parent" || requirement === "parents") {
for (let parentId of component.parents) {
let parent2 = instance.components.find((component2) => component2.id === parentId);
promises.push(parent_default(component, parent2.id, parent2.status));
}
}
}
Promise.all(promises).then(() => {
component.download(Alpine);
Expand Down
51 changes: 46 additions & 5 deletions dist/async-alpine.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,20 @@ var Component = class {
node: root,
attributes: getAlpineAttrs(root, this.instance.config)
};
root.setAttribute(this.instance.config.id + this.instance.config.id, this.id);
root.setAttribute(this.instance.config.prefix + this.instance.config.id, this.id);
this.children = [...root.querySelectorAll("*")].filter((el) => getAlpineAttrs(el, this.instance.config).length).filter((el) => !el.hasAttribute(this.instance.config.prefix + this.instance.config.root)).filter((el) => el.closest(`[${this.instance.config.prefix}${this.instance.config.root}]`) === root).map((node) => ({
node,
attributes: getAlpineAttrs(node, this.instance.config)
}));
this.parents = [];
let cursor = root;
do {
cursor = cursor.parentNode.closest(`[${this.instance.config.prefix}${this.instance.config.root}]`);
if (!cursor)
break;
const parent2 = instance.components.find((component) => component.root.node === cursor);
this.parents.push(parent2.id);
} while (cursor);
}
deactivate() {
disableAttributes(this.root, this.instance.config);
Expand All @@ -60,15 +69,15 @@ var Component = class {
this.activate();
}
async getModule() {
if (this.instance.cache[this.src]) {
return this.instance.cache[this.src];
if (this.instance.moduleCache[this.src]) {
return this.instance.moduleCache[this.src];
}
const module = await import(
/* webpackIgnore: true */
this.src
);
let whichExport = module[this.name] || module.default || Object.values(module)[0] || false;
this.instance.cache[this.src] = whichExport;
this.instance.moduleCache[this.src] = whichExport;
return whichExport;
}
activate() {
Expand All @@ -81,6 +90,11 @@ var Component = class {
child.node.removeAttribute(`${this.instance.config.alpine.prefix}cloak`);
}
this.status = "loaded";
window.dispatchEvent(new CustomEvent("async-alpine:loaded", {
detail: {
id: this.id
}
}));
}
};

Expand Down Expand Up @@ -125,6 +139,23 @@ var media = (component, requirement) => {
};
var media_default = media;

// src/core/strategies/parent.js
var parent = (component, parentId, parentStatus) => {
return new Promise((resolve) => {
if (parentStatus !== "unloaded") {
return resolve();
}
window.addEventListener("async-alpine:loaded", (e) => {
if (e.detail.id !== parentId)
return;
if (component.status !== "unloaded")
return;
resolve();
});
});
};
var parent_default = parent;

// src/core/strategies/visible.js
var visible = (component, requirement) => {
return new Promise((resolve) => {
Expand Down Expand Up @@ -163,7 +194,8 @@ var idIndex = 1;
var AsyncAlpine = (Alpine, opts = {}) => {
const instance = {
config: config_default,
cache: {}
components: [],
moduleCache: {}
};
if (opts.prefix)
instance.config.prefix = opts.prefix;
Expand All @@ -176,6 +208,9 @@ var AsyncAlpine = (Alpine, opts = {}) => {
return;
for (let root of roots) {
const component = new Component(root, instance, idIndex++);
instance.components.push(component);
}
for (let component of instance.components) {
component.deactivate();
const requirements = component.strategy.split("|").map((requirement) => requirement.trim()).filter((requirement) => requirement !== "immediate").filter((requirement) => requirement !== "eager");
if (!requirements.length) {
Expand All @@ -199,6 +234,12 @@ var AsyncAlpine = (Alpine, opts = {}) => {
if (requirement === "event") {
promises.push(event_default(component));
}
if (requirement === "parent" || requirement === "parents") {
for (let parentId of component.parents) {
let parent2 = instance.components.find((component2) => component2.id === parentId);
promises.push(parent_default(component, parent2.id, parent2.status));
}
}
}
Promise.all(promises).then(() => {
component.download(Alpine);
Expand Down
2 changes: 1 addition & 1 deletion dist/async-alpine.script.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 21 additions & 3 deletions src/core/async-alpine.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const AsyncAlpine = (Alpine, opts = {}) => {
// instance of AsyncAlpine
const instance = {
config,
cache: {},
components: [],
moduleCache: {},
};

// if a prefix or AlpinePrefix has been passed in from `opt`, update config
Expand All @@ -19,18 +20,21 @@ const AsyncAlpine = (Alpine, opts = {}) => {
instance.config.alpine.attributes.push(opts.alpinePrefix);
}

// get ell elements in the DOM with `x-load` attribute
// get ell elements in the DOM with `ax-load` attribute
const roots = document.querySelectorAll(`[${config.prefix}${config.root}]`);
if (!roots) return;

// for each root, get the loading strategy and any alpine elements controlled by this component
for (let root of roots) {
const component = new Component(root, instance, idIndex++);
instance.components.push(component);
}

// for each component, get the loading strategy and any alpine elements controlled by this component
for (let component of instance.components) {
// disable this component
component.deactivate();


// split strategy into parts
const requirements = component.strategy
.split('|')
Expand Down Expand Up @@ -79,6 +83,20 @@ const AsyncAlpine = (Alpine, opts = {}) => {
);
}

// parents
if (requirement === 'parent' || requirement === 'parents') {
for (let parentId of component.parents) {
let parent = instance.components.find(component => component.id === parentId);
promises.push(
strategies.parent(
component,
parent.id,
parent.status
)
);
}
}

}

// wait for all promises (requirements) to resolve and then download component
Expand Down
Loading

0 comments on commit 13a8ea9

Please sign in to comment.