Skip to content

Commit

Permalink
feature: lazy loading
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinBLT committed Jan 9, 2024
1 parent db1de48 commit 2b60a95
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 28 deletions.
1 change: 1 addition & 0 deletions packages/@hec.js/ui/example/lazy/include.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include {{ name }}
39 changes: 39 additions & 0 deletions packages/@hec.js/ui/example/lazy/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>hec.js :: Plugins</title>
<script type="module" defer src="./lazy.js"></script>

</head>
<body hidden>

<div>The parts will lazy load...</div>

<div data-lazy>
<div>Inline 1 {{ name }}</div>
</div>

<div hidden data-lazy>
<div>Inline 2 {{ name }}</div>

<div hidden>
<div>Inline 3 {{ name }}</div>

<div hidden>
Inline 4 {{ name }}
</div>

<my-test data-lazy></my-test>

<div data-lazy data-include="include.html"></div>
</div>

<div data-lazy data-include="include.html"></div>
</div>

<div hidden data-lazy data-include="include.html"></div>
<my-test hidden data-lazy></my-test>
</body>
</html>
24 changes: 24 additions & 0 deletions packages/@hec.js/ui/example/lazy/lazy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { component, signal, templateByNode, templateByString } from '../../lib/index.js';

const p = {
name: 'Lazy...'
}

const lazy = async () => {
let i = 1;

for (const lazy of document.querySelectorAll('[hidden]')) {
await new Promise(r => setTimeout(r, 1000));

p.name += ` -> ${i++}`;

lazy.removeAttribute('hidden');
}

}

lazy();

templateByNode(document.body, p);

component('my-test', {}, () => templateByString(new Date().toJSON()));
11 changes: 9 additions & 2 deletions packages/@hec.js/ui/lib/src/component.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { notifyVisible } from "./notify/visible.js";
import { f, nodeProps, prop, propsOf } from "./props.js";
import { isSignal, signal } from "./signal.js";

Expand Down Expand Up @@ -26,7 +27,13 @@ export function component(name, props, fn) {
/** @type {{ [key: string]: AbortController }} */
#aborts = {};

connectedCallback() {
async connectedCallback() {
const hidden = this.closest('[hidden]');

if (this.hasAttribute('data-lazy') && hidden) {
await notifyVisible(this, hidden);
}

const shadow = this.attachShadow({ mode: 'open' }),
node = fn(this.#signals);

Expand All @@ -53,7 +60,7 @@ export function component(name, props, fn) {
this.dispatchEvent(new CustomEvent('::mount'));
}

node instanceof Promise ? node.then(append) : append(node);
append(node instanceof Promise ? await node : node);
}

disconnectedCallback() {
Expand Down
7 changes: 5 additions & 2 deletions packages/@hec.js/ui/lib/src/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import { dataBindPlugin } from "./plugins/data-bind.js";
import { dataForPlugin } from "./plugins/data-for.js";
import { dataIfPlugin } from "./plugins/data-if.js";
import { dataIncludePlugin } from "./plugins/data-include.js";
import { dataLazyPlugin } from "./plugins/data-lazy.js";
import { dataOnPlugin } from "./plugins/data-on.js";
import { dataPreloadPlugin } from "./plugins/data-preload.js";
import { dataRoutePlugin } from "./plugins/data-route.js";

/**
* @typedef {{
* select: string,
* run: (arg0: HTMLElement, arg1: {[key: string]: any}) => void
* run: (node: HTMLElement, props: {[key: string]: any}, stopTemplate: () => void) => void
* }} Plugin
*
*
/** @type { Plugin[] } */
export const plugins = [
dataForPlugin,
Expand All @@ -20,5 +22,6 @@ export const plugins = [
dataIncludePlugin,
dataPreloadPlugin,
dataOnPlugin,
dataBindPlugin
dataBindPlugin,
dataLazyPlugin,
];
4 changes: 3 additions & 1 deletion packages/@hec.js/ui/lib/src/plugins/data-for.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const done = new WeakSet();
export const dataForPlugin = {
select: '[data-for]',

run: (node, props) => {
run: (node, props, stopTemplate) => {

if (done.has(node)) {
return;
Expand Down Expand Up @@ -62,5 +62,7 @@ export const dataForPlugin = {
if (isSignal(list)) {
list.subscribe({next: update});
}

stopTemplate();
}
}
2 changes: 1 addition & 1 deletion packages/@hec.js/ui/lib/src/plugins/data-include.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const dataIncludePlugin = {

loaded.add(node);

const hidden = node.closest('[hidden]');
const hidden = node.hasAttribute('data-lazy') && node.closest('[hidden]');

const execute = async () => {
const response = await fetch(node.dataset.include, {
Expand Down
33 changes: 33 additions & 0 deletions packages/@hec.js/ui/lib/src/plugins/data-lazy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { notifyVisible } from '../notify/visible.js';
import { templateByNode } from '../template.js';

const loaded = new WeakSet();

/**
* @type { import("../plugins.js").Plugin }
*/
export const dataLazyPlugin = {
select: '[data-lazy]',

run: (node, props, stopTemplate) => {

if (loaded.has(node) || !node.childNodes.length) {
return;
}

loaded.add(node);

const hidden = node.closest('[hidden]');

const execute = () => {
for (const child of node.childNodes) {
templateByNode(child, props);
}
}

if (hidden) {
notifyVisible(node, hidden).then(execute);
stopTemplate();
}
}
}
2 changes: 1 addition & 1 deletion packages/@hec.js/ui/lib/src/plugins/data-route.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const joinsRoutes = (node) => {
}
}

return route
return route.replaceAll(/\/+/g, '/');
}

/**
Expand Down
42 changes: 21 additions & 21 deletions packages/@hec.js/ui/lib/src/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function templateByName(name, props = {}) {
}

/** @type { HTMLTemplateElement } */
let tmpl = document.querySelector(`template[data-name="${name}"]`);
let tmpl = document.querySelector(`template[id="${name}"]`);

if (!tmpl) {
/** @type { HTMLMetaElement } */
Expand All @@ -28,7 +28,7 @@ export function templateByName(name, props = {}) {
templatesLoading[name] ??= new Promise(async (resolve) => {
tmpl = document.createElement('template');

tmpl.dataset.name = name.toString();
tmpl.setAttribute('id', name.toString());
tmpl.innerHTML = await fetch(
meta?.content?.replaceAll('[name]', name.toString()) ?? name
).then((r) => r.text());
Expand Down Expand Up @@ -139,7 +139,7 @@ export function templateByNode(template, props = {}) {

/** @param { Node } node */
const findExpression = (node) => {
const parentNode = node.parentNode;
let stopFlag = false;

if (node.nodeName == '#document-fragment') {
nodeProps.set(node, props);
Expand All @@ -151,36 +151,36 @@ export function templateByNode(template, props = {}) {

for (const plugin of plugins) {
if (node.matches(plugin.select)) {
plugin.run(node, props);
plugin.run(node, props, () => stopFlag = true);
}
}

if (parentNode === node.parentNode) {
const attributeNames = node.getAttributeNames();
if (stopFlag) {
return;
}

const attributeNames = node.getAttributeNames();

for (const attributeName of attributeNames) {
const attribute = node.getAttribute(attributeName);

if (attribute.includes('{{')) {

bindExpressions(attribute, (text) => {
node.setAttribute(attributeName, text.trim().replace(/ +/, ' '))
});
for (const attributeName of attributeNames) {
const attribute = node.getAttribute(attributeName);

} else if (node.localName.includes('-') && props[attribute]) {
node.setAttribute(attributeName, `@parent.${attribute}`);
}
if (attribute.includes('{{')) {

bindExpressions(attribute, (text) => {
node.setAttribute(attributeName, text.trim().replace(/ +/, ' '))
});

} else if (node.localName.includes('-') && props[attribute]) {
node.setAttribute(attributeName, `@parent.${attribute}`);
}
}

} else if (node instanceof Text && node.textContent.includes('{{')) {
bindExpressions(node.textContent, (text) => (node.textContent = text));
}

if (parentNode === node.parentNode) {
for (const child of node.childNodes) {
findExpression(child);
}
for (const child of node.childNodes) {
findExpression(child);
}
};

Expand Down

0 comments on commit 2b60a95

Please sign in to comment.