Skip to content

Commit

Permalink
Build lib files in two passes, so we can have a module that can be im…
Browse files Browse the repository at this point in the history
…ported via npm as well as a normal js script
  • Loading branch information
croxton committed Dec 5, 2023
1 parent 5f89a33 commit 48e73b2
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 4 deletions.
207 changes: 207 additions & 0 deletions dist/booster-pack.min.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value;
var __publicField = (obj, key, value) => (__defNormalProp(obj, typeof key != "symbol" ? key + "" : key, value), value);
class Booster {
constructor(element = "", options = {}) {
__publicField(this, "mounted", !1);
__publicField(this, "elm", null);
__publicField(this, "target", null);
__publicField(this, "_state", {});
this._options = options || {}, element && (this.elm = element);
}
get options() {
return this._options;
}
set options(defaults) {
let options = {};
if (this.elm) {
let mount = document.querySelector(this.elm);
if (mount) {
let optionsFromAttribute = mount.dataset.options;
optionsFromAttribute && (options = JSON.parse(optionsFromAttribute)), mount = null;
}
}
this._options = {
...this._options,
...defaults,
...options
};
}
mount() {
}
unmount() {
}
refresh() {
this.unmount(), this.mount();
}
get state() {
return console.warn("You should not get state manually. Use getState() instead."), this._state;
}
set state(state) {
console.warn("You should not change state manually. Use setState() instead."), this._state = state;
}
setState(scope = "local", changes) {
let stateChanges = {}, stateRef = this._state;
scope === "global" ? stateRef = Booster._globalState : scope === "component" && (Booster._globalState.hasOwnProperty(this.constructor.name) || (Booster._globalState[this.constructor.name] = {}), stateRef = Booster._globalState[this.constructor.name]), Object.keys(changes).forEach((key) => {
Array.isArray(changes[key]) ? stateRef[key] != null && Array.isArray(stateRef[key]) && stateRef[key].length === changes[key].length ? changes[key].some((item, index) => stateRef[key][index] !== item ? (stateChanges[key] = changes[key], stateRef[key] = stateChanges[key], !0) : !1) : (stateChanges[key] = changes[key], stateRef[key] = stateChanges[key]) : typeof changes[key] == "object" ? (stateRef[key] != null && typeof stateRef[key] == "object" ? (stateChanges[key] = {}, Object.keys(changes[key]).forEach((subkey) => {
stateRef[key][subkey] !== changes[key][subkey] && (stateChanges[key][subkey] = changes[key][subkey]);
})) : stateChanges[key] = changes[key], stateRef[key] = {
...stateRef[key],
...stateChanges[key]
}) : stateRef !== changes[key] && (stateChanges[key] = changes[key], stateRef[key] = changes[key]);
}), Object.keys(stateChanges).forEach((key) => {
Array.isArray(changes[key]) ? stateChanges[key].length === 0 && delete stateChanges[key] : typeof changes[key] == "object" && Object.keys(stateChanges[key]).length === 0 && delete stateChanges[key];
}), stateRef = null, this.stateChange(stateChanges);
}
stateChange(changes) {
}
getState(scope = "local", defaults = {}) {
let stateRef = this._state;
return scope === "global" ? stateRef = Booster._globalState : scope === "component" && (Booster._globalState.hasOwnProperty(this.constructor.name) ? stateRef = Booster._globalState[this.constructor.name] : stateRef = {}), {
...defaults,
...stateRef
};
}
destroyState(scope = "local") {
scope === "global" ? Booster._globalState = {} : scope === "component" ? Booster._globalState.hasOwnProperty(this.constructor.name) && (Booster._globalState[this.constructor.name] = {}) : this._state = {};
}
css(urls) {
return Promise.all(urls.map(this._loadCSS));
}
_loadCSS(href) {
return new Promise((resolve) => {
if (Booster._sheets.includes(href))
return resolve();
Booster._sheets.push(href);
let link = document.createElement("link");
link.type = "text/css", link.rel = "stylesheet", link.onload = resolve, link.setAttribute("href", href), document.head.appendChild(link);
});
}
}
Object.defineProperty(Booster, "_sheets", {
value: [],
writable: !0
});
Object.defineProperty(Booster, "_globalState", {
value: {},
writable: !0
});
const event = (requirement) => new Promise((resolve) => {
let topic;
if (requirement.indexOf("(") !== -1) {
const topicStart = requirement.indexOf("(") + 1;
topic = requirement.slice(topicStart, -1);
}
topic ? document.body.addEventListener(topic, () => {
resolve();
}, { once: !0 }) : resolve();
}), idle = () => new Promise((resolve) => {
"requestIdleCallback" in window ? window.requestIdleCallback(resolve) : setTimeout(resolve, 200);
}), media = (requirement) => new Promise((resolve) => {
const queryStart = requirement.indexOf("("), query = requirement.slice(queryStart), mediaQuery = window.matchMedia(query);
mediaQuery.matches ? resolve() : mediaQuery.addEventListener("change", resolve, { once: !0 });
}), visible = (selector = null, requirement) => selector ? new Promise((resolve) => {
let rootMargin = "0px 0px 0px 0px";
if (requirement.indexOf("(") !== -1) {
const rootMarginStart = requirement.indexOf("(") + 1;
rootMargin = requirement.slice(rootMarginStart, -1);
}
const observer = new IntersectionObserver((entries) => {
entries[0].isIntersecting && (observer.disconnect(), resolve());
}, { rootMargin });
let elm = document.querySelector(selector);
elm ? observer.observe(elm) : resolve();
}) : Promise.resolve(!0);
function loadStrategies(strategy, selector) {
let promises = [];
if (strategy) {
let requirements = strategy.split("|").map((requirement) => requirement.trim()).filter((requirement) => requirement !== "immediate").filter((requirement) => requirement !== "eager");
for (let requirement of requirements) {
if (requirement.startsWith("event")) {
promises.push(
event(requirement)
);
continue;
}
if (requirement === "idle") {
promises.push(
idle()
);
continue;
}
if (requirement.startsWith("media")) {
promises.push(
media(requirement)
);
continue;
}
requirement.startsWith("visible") && promises.push(
visible(selector, requirement)
);
}
}
return promises;
}
class BoosterFactory extends Booster {
constructor() {
super();
__publicField(this, "loaded", []);
__publicField(this, "config", {});
this.config = {
origin: location.origin,
basePath: "scripts/boosts"
};
let configMeta = document.querySelector('meta[name="booster-config"]') ?? null;
configMeta && (this.config = {
...this.config,
...JSON.parse(configMeta.content)
}), this.config.basePath = this.config.basePath.replace(/^\/|\/$/g, ""), this.mount();
}
mount() {
let targetId = htmx.config.currentTargetId ?? "main", target = document.getElementById(targetId);
if (target) {
let components = target.querySelectorAll("[data-booster]");
for (let el of components)
this.lazyload(el);
target = null, components = null;
}
}
unmount() {
let targetId = htmx.config.currentTargetId ?? "main", target = document.getElementById(targetId);
if (target) {
for (let i = this.loaded.length - 1; i >= 0; i--) {
let inTarget = target.querySelector(this.loaded[i].selector), inDocument = document.querySelector(this.loaded[i].selector);
(inTarget || !inDocument) && (this.loaded[i].instance.unmount(), this.loaded.splice(i, 1));
}
target = null;
}
}
/**
* Import a component on demand, optionally using a loading strategy
*
* @param el
*/
lazyload(el) {
let component = el.dataset.booster, version = el.dataset.version ?? "1", strategy = el.dataset.load ?? null, selector = el.getAttribute("id") ? "#" + el.getAttribute("id") : '[data-booster="' + component + '"]', promises = loadStrategies(strategy, selector);
Promise.all(promises).then(() => {
import(
/* @vite-ignore */
`${this.config.origin}/${this.config.basePath}/${component}.js?v=${version}`
).then(
(lazyComponent) => {
let instance = new lazyComponent.default(selector);
instance.mounted = !0, this.loaded.push({
name: component,
selector,
instance
});
}
);
});
}
}
export {
Booster,
BoosterFactory,
loadStrategies
};
10 changes: 10 additions & 0 deletions lib/ext/booster-pack.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Booster Pack extension - module exports
*
* @author Mark Croxton, Hallmark Design
*/

import Booster from '../booster.js';
import BoosterFactory from '../boosterFactory.js';
import { loadStrategies } from '../loadStrategies.js';
export { Booster, BoosterFactory, loadStrategies };
4 changes: 2 additions & 2 deletions package-lock.json

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

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
{
"name": "htmx-booster-pack",
"version": "1.0.5",
"version": "1.0.6",
"description": "Minimal component framework for htmx",
"type": "module",
"exports": {
".": {
"import": "./dist/booster.min.js"
},
"./booster-pack": {
"import": "./dist/booster-pack.min.mjs"
}
},
"files": [
"dist/*"
],
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "vite build && vite build --config vite.config.pack.js --emptyOutDir=false",
"preview": "vite preview"
},
"repository": {
Expand Down
19 changes: 19 additions & 0 deletions vite.config.pack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { defineConfig } from 'vite';

export default defineConfig(({}) => {
return {
esbuild: {
minifyIdentifiers: false
},
build: {
lib: {
entry: {
"booster-pack": "./lib/ext/booster-pack.mjs"
},
formats: ["es"],
fileName: (format, name) => `${name}.min.mjs`
},
minify: true
}
}
});

0 comments on commit 48e73b2

Please sign in to comment.