xpine 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ // src/build/spa.mjs
2
+ async function replaceDocumentContentsWithLinkResponse() {
3
+ const links = [...document.querySelectorAll("[data-spa]")];
4
+ for (const link of links) {
5
+ link.addEventListener("click", updatePageOnLinkClick);
6
+ }
7
+ }
8
+ async function updatePageOnLinkClick(e) {
9
+ const targetHref = e?.target?.getAttribute("href");
10
+ if (!targetHref) return;
11
+ if (isExternalURL(targetHref) && !e?.target?.getAttribute("data-spa-crossorigin")) return;
12
+ e.preventDefault();
13
+ try {
14
+ await getNewPageContent(targetHref);
15
+ if (targetHref === window.location.pathname) return;
16
+ window.history.pushState(
17
+ {
18
+ type: "link-click",
19
+ targetHref
20
+ },
21
+ "",
22
+ targetHref
23
+ );
24
+ const event = new CustomEvent("spa:updatePageURL", {
25
+ detail: {
26
+ href: targetHref
27
+ }
28
+ });
29
+ window.dispatchEvent(event);
30
+ } catch (err) {
31
+ console.error(err);
32
+ }
33
+ }
34
+ async function getNewPageContent(href) {
35
+ try {
36
+ const res = await fetch(href);
37
+ const text = await res.text();
38
+ const parser = new DOMParser();
39
+ const dom = parser.parseFromString(text, "text/html");
40
+ diffHead(dom);
41
+ const newScripts = removeBodyScripts(dom);
42
+ const clonedPersistentNodes = getPersistentNodesFromDocument(dom);
43
+ const body = dom.body.innerHTML;
44
+ document.body.innerHTML = body;
45
+ for (const clonedNode of clonedPersistentNodes) {
46
+ const newNode = document.body.querySelector(`[data-persistent="${clonedNode?.getAttribute("data-persistent")}"]`);
47
+ newNode.replaceWith(clonedNode);
48
+ }
49
+ replaceAttributesOnDocumentBody(dom);
50
+ replaceDocumentContentsWithLinkResponse();
51
+ for (const script of newScripts) document.body.appendChild(script);
52
+ const event = new CustomEvent("spa:updatePageContent", {
53
+ detail: {
54
+ href
55
+ }
56
+ });
57
+ window.dispatchEvent(event);
58
+ } catch (err) {
59
+ console.error(err);
60
+ console.error("Error getting link", href);
61
+ }
62
+ }
63
+ function diffHead(dom) {
64
+ const domHeadChildren = [...dom.head.children];
65
+ const currentHeadChildren = [...document.head.children];
66
+ const result = {
67
+ childrenToAdd: [],
68
+ childrenToRemove: []
69
+ };
70
+ for (const domChild of domHeadChildren) {
71
+ const foundInCurrentChildren = currentHeadChildren.find((child) => child.outerHTML === domChild.outerHTML);
72
+ if (!foundInCurrentChildren) result.childrenToAdd.push(domChild);
73
+ }
74
+ for (const currentChild of currentHeadChildren) {
75
+ const foundInDomChildren = domHeadChildren.find((child) => child.outerHTML === currentChild.outerHTML);
76
+ if (!foundInDomChildren) result.childrenToRemove.push(currentChild);
77
+ }
78
+ applyHeadDiffsToDocument(result);
79
+ }
80
+ function applyHeadDiffsToDocument(diff) {
81
+ for (const child of diff.childrenToRemove) {
82
+ document.head.removeChild(child);
83
+ }
84
+ for (const child of diff.childrenToAdd) {
85
+ if (child?.nodeName?.toLowerCase() == "script") {
86
+ const scriptElement = document.createElement("script");
87
+ portAttributesToElement(child, scriptElement);
88
+ scriptElement.innerHTML = child.innerHTML;
89
+ document.head.appendChild(scriptElement);
90
+ continue;
91
+ }
92
+ document.head.appendChild(child);
93
+ }
94
+ }
95
+ function removeBodyScripts(dom) {
96
+ const scripts = [...dom.body.querySelectorAll("script")];
97
+ const newElements = [];
98
+ for (const script of scripts) {
99
+ const element = document.createElement("script");
100
+ portAttributesToElement(script, element);
101
+ element.innerHTML = script.innerHTML;
102
+ dom.body.removeChild(script);
103
+ newElements.push(element);
104
+ }
105
+ return newElements;
106
+ }
107
+ function portAttributesToElement(oldEl, newEl) {
108
+ for (const attribute of oldEl.attributes) {
109
+ newEl.setAttribute(attribute.name, attribute.nodeValue);
110
+ }
111
+ }
112
+ function getPersistentNodesFromDocument(dom) {
113
+ const persistentNodesFromDom = [...dom.querySelectorAll("[data-persistent]")].map((el) => el.getAttribute("data-persistent"));
114
+ const persistentNodesFromBody = [...document.body.querySelectorAll("[data-persistent]")];
115
+ const validPersistentNodes = persistentNodesFromBody.filter((el) => {
116
+ return persistentNodesFromDom.includes(el.getAttribute("data-persistent"));
117
+ });
118
+ return validPersistentNodes.map((el) => el.cloneNode(true));
119
+ }
120
+ function replaceAttributesOnDocumentBody(dom) {
121
+ while (document.body.attributes.length > 0) {
122
+ document.body.removeAttribute(document.body.attributes[0].name);
123
+ }
124
+ for (const attribute of dom.body.attributes) {
125
+ document.body.setAttribute(attribute.name, attribute.nodeValue);
126
+ }
127
+ }
128
+ async function handleBackButton() {
129
+ await getNewPageContent(window.history.state?.targetHref || window.location.href);
130
+ }
131
+ function isRelativeURL(url) {
132
+ if (typeof url !== "string") return false;
133
+ try {
134
+ new URL(url);
135
+ return false;
136
+ } catch (err) {
137
+ if (url?.startsWith("//")) return false;
138
+ return true;
139
+ }
140
+ }
141
+ function isExternalURL(url) {
142
+ if (isRelativeURL(url)) return false;
143
+ try {
144
+ const computedURL = new URL(url);
145
+ return computedURL?.origin !== window.location?.origin;
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+ window.addEventListener("DOMContentLoaded", replaceDocumentContentsWithLinkResponse);
151
+ window.addEventListener("popstate", handleBackButton);
152
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL2J1aWxkL3NwYS5tanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8vIEVuYWJsZXMgU1BBIGxpa2UgYmVoYXZpb3IgZm9yIGxpbmtzXG5cbmFzeW5jIGZ1bmN0aW9uIHJlcGxhY2VEb2N1bWVudENvbnRlbnRzV2l0aExpbmtSZXNwb25zZSgpIHtcbiAgY29uc3QgbGlua3MgPSBbLi4uZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnW2RhdGEtc3BhXScpXTtcbiAgZm9yIChjb25zdCBsaW5rIG9mIGxpbmtzKSB7XG4gICAgbGluay5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIHVwZGF0ZVBhZ2VPbkxpbmtDbGljayk7XG4gIH1cblxufVxuXG5hc3luYyBmdW5jdGlvbiB1cGRhdGVQYWdlT25MaW5rQ2xpY2soZSkge1xuICBjb25zdCB0YXJnZXRIcmVmID0gZT8udGFyZ2V0Py5nZXRBdHRyaWJ1dGUoJ2hyZWYnKTtcbiAgaWYgKCF0YXJnZXRIcmVmKSByZXR1cm47XG4gIGlmIChpc0V4dGVybmFsVVJMKHRhcmdldEhyZWYpICYmICFlPy50YXJnZXQ/LmdldEF0dHJpYnV0ZSgnZGF0YS1zcGEtY3Jvc3NvcmlnaW4nKSkgcmV0dXJuO1xuICBlLnByZXZlbnREZWZhdWx0KCk7XG4gIHRyeSB7XG4gICAgYXdhaXQgZ2V0TmV3UGFnZUNvbnRlbnQodGFyZ2V0SHJlZik7XG4gICAgaWYgKHRhcmdldEhyZWYgPT09IHdpbmRvdy5sb2NhdGlvbi5wYXRobmFtZSkgcmV0dXJuO1xuICAgIHdpbmRvdy5oaXN0b3J5LnB1c2hTdGF0ZShcbiAgICAgIHtcbiAgICAgICAgdHlwZTogJ2xpbmstY2xpY2snLFxuICAgICAgICB0YXJnZXRIcmVmLFxuICAgICAgfSxcbiAgICAgICcnLFxuICAgICAgdGFyZ2V0SHJlZlxuICAgICk7XG4gICAgY29uc3QgZXZlbnQgPSBuZXcgQ3VzdG9tRXZlbnQoJ3NwYTp1cGRhdGVQYWdlVVJMJywge1xuICAgICAgZGV0YWlsOiB7XG4gICAgICAgIGhyZWY6IHRhcmdldEhyZWYsXG4gICAgICB9LFxuICAgIH0pO1xuICAgIHdpbmRvdy5kaXNwYXRjaEV2ZW50KGV2ZW50KTtcbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgY29uc29sZS5lcnJvcihlcnIpO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGdldE5ld1BhZ2VDb250ZW50KGhyZWYpIHtcbiAgdHJ5IHtcbiAgICBjb25zdCByZXMgPSBhd2FpdCBmZXRjaChocmVmKTtcbiAgICBjb25zdCB0ZXh0ID0gYXdhaXQgcmVzLnRleHQoKTtcbiAgICBjb25zdCBwYXJzZXIgPSBuZXcgRE9NUGFyc2VyKCk7XG4gICAgY29uc3QgZG9tID0gcGFyc2VyLnBhcnNlRnJvbVN0cmluZyh0ZXh0LCAndGV4dC9odG1sJyk7XG4gICAgZGlmZkhlYWQoZG9tKTtcbiAgICBjb25zdCBuZXdTY3JpcHRzID0gcmVtb3ZlQm9keVNjcmlwdHMoZG9tKTtcbiAgICBjb25zdCBjbG9uZWRQZXJzaXN0ZW50Tm9kZXMgPSBnZXRQZXJzaXN0ZW50Tm9kZXNGcm9tRG9jdW1lbnQoZG9tKTtcbiAgICBjb25zdCBib2R5ID0gZG9tLmJvZHkuaW5uZXJIVE1MO1xuICAgIGRvY3VtZW50LmJvZHkuaW5uZXJIVE1MID0gYm9keTtcbiAgICBmb3IgKGNvbnN0IGNsb25lZE5vZGUgb2YgY2xvbmVkUGVyc2lzdGVudE5vZGVzKSB7XG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBjb25zdCBuZXdOb2RlID0gZG9jdW1lbnQuYm9keS5xdWVyeVNlbGVjdG9yKGBbZGF0YS1wZXJzaXN0ZW50PVwiJHtjbG9uZWROb2RlPy5nZXRBdHRyaWJ1dGUoJ2RhdGEtcGVyc2lzdGVudCcpfVwiXWApO1xuICAgICAgbmV3Tm9kZS5yZXBsYWNlV2l0aChjbG9uZWROb2RlKTtcbiAgICB9XG4gICAgcmVwbGFjZUF0dHJpYnV0ZXNPbkRvY3VtZW50Qm9keShkb20pO1xuICAgIHJlcGxhY2VEb2N1bWVudENvbnRlbnRzV2l0aExpbmtSZXNwb25zZSgpO1xuICAgIGZvciAoY29uc3Qgc2NyaXB0IG9mIG5ld1NjcmlwdHMpIGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoc2NyaXB0KTtcbiAgICAvLyBTZW5kIGFuIGV2ZW50XG4gICAgY29uc3QgZXZlbnQgPSBuZXcgQ3VzdG9tRXZlbnQoJ3NwYTp1cGRhdGVQYWdlQ29udGVudCcsIHtcbiAgICAgIGRldGFpbDoge1xuICAgICAgICBocmVmLFxuICAgICAgfSxcbiAgICB9KTtcbiAgICB3aW5kb3cuZGlzcGF0Y2hFdmVudChldmVudCk7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGNvbnNvbGUuZXJyb3IoZXJyKTtcbiAgICBjb25zb2xlLmVycm9yKCdFcnJvciBnZXR0aW5nIGxpbmsnLCBocmVmKTtcbiAgfVxufVxuXG5mdW5jdGlvbiBkaWZmSGVhZChkb20pIHtcbiAgY29uc3QgZG9tSGVhZENoaWxkcmVuID0gWy4uLmRvbS5oZWFkLmNoaWxkcmVuXTtcbiAgY29uc3QgY3VycmVudEhlYWRDaGlsZHJlbiA9IFsuLi5kb2N1bWVudC5oZWFkLmNoaWxkcmVuXTtcbiAgY29uc3QgcmVzdWx0ID0ge1xuICAgIGNoaWxkcmVuVG9BZGQ6IFtdLFxuICAgIGNoaWxkcmVuVG9SZW1vdmU6IFtdLFxuICB9O1xuICBmb3IgKGNvbnN0IGRvbUNoaWxkIG9mIGRvbUhlYWRDaGlsZHJlbikge1xuICAgIGNvbnN0IGZvdW5kSW5DdXJyZW50Q2hpbGRyZW4gPSBjdXJyZW50SGVhZENoaWxkcmVuLmZpbmQoY2hpbGQgPT4gY2hpbGQub3V0ZXJIVE1MID09PSBkb21DaGlsZC5vdXRlckhUTUwpO1xuICAgIGlmICghZm91bmRJbkN1cnJlbnRDaGlsZHJlbikgcmVzdWx0LmNoaWxkcmVuVG9BZGQucHVzaChkb21DaGlsZCk7XG4gIH1cbiAgZm9yIChjb25zdCBjdXJyZW50Q2hpbGQgb2YgY3VycmVudEhlYWRDaGlsZHJlbikge1xuICAgIGNvbnN0IGZvdW5kSW5Eb21DaGlsZHJlbiA9IGRvbUhlYWRDaGlsZHJlbi5maW5kKGNoaWxkID0+IGNoaWxkLm91dGVySFRNTCA9PT0gY3VycmVudENoaWxkLm91dGVySFRNTCk7XG4gICAgaWYgKCFmb3VuZEluRG9tQ2hpbGRyZW4pIHJlc3VsdC5jaGlsZHJlblRvUmVtb3ZlLnB1c2goY3VycmVudENoaWxkKTtcbiAgfVxuICBhcHBseUhlYWREaWZmc1RvRG9jdW1lbnQocmVzdWx0KTtcbn1cblxuZnVuY3Rpb24gYXBwbHlIZWFkRGlmZnNUb0RvY3VtZW50KGRpZmYpIHtcbiAgZm9yIChjb25zdCBjaGlsZCBvZiBkaWZmLmNoaWxkcmVuVG9SZW1vdmUpIHtcbiAgICAvLyBSZW1vdmUgZnJvbSBoZWFkXG4gICAgZG9jdW1lbnQuaGVhZC5yZW1vdmVDaGlsZChjaGlsZCk7XG4gIH1cbiAgZm9yIChjb25zdCBjaGlsZCBvZiBkaWZmLmNoaWxkcmVuVG9BZGQpIHtcbiAgICAvLyBIYW5kbGUgc2NyaXB0IHRhZ3NcbiAgICBpZiAoY2hpbGQ/Lm5vZGVOYW1lPy50b0xvd2VyQ2FzZSgpID09ICdzY3JpcHQnKSB7XG4gICAgICBjb25zdCBzY3JpcHRFbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7XG4gICAgICBwb3J0QXR0cmlidXRlc1RvRWxlbWVudChjaGlsZCwgc2NyaXB0RWxlbWVudCk7XG4gICAgICBzY3JpcHRFbGVtZW50LmlubmVySFRNTCA9IGNoaWxkLmlubmVySFRNTDtcbiAgICAgIGRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoc2NyaXB0RWxlbWVudCk7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG4gICAgLy8gQWRkIHRvIHRoZSBoZWFkXG4gICAgZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChjaGlsZCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gcmVtb3ZlQm9keVNjcmlwdHMoZG9tKSB7XG4gIGNvbnN0IHNjcmlwdHMgPSBbLi4uZG9tLmJvZHkucXVlcnlTZWxlY3RvckFsbCgnc2NyaXB0JyldO1xuICBjb25zdCBuZXdFbGVtZW50cyA9IFtdO1xuICBmb3IgKGNvbnN0IHNjcmlwdCBvZiBzY3JpcHRzKSB7XG4gICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3NjcmlwdCcpO1xuICAgIHBvcnRBdHRyaWJ1dGVzVG9FbGVtZW50KHNjcmlwdCwgZWxlbWVudCk7XG4gICAgZWxlbWVudC5pbm5lckhUTUwgPSBzY3JpcHQuaW5uZXJIVE1MO1xuICAgIGRvbS5ib2R5LnJlbW92ZUNoaWxkKHNjcmlwdCk7XG4gICAgbmV3RWxlbWVudHMucHVzaChlbGVtZW50KTtcbiAgfVxuICByZXR1cm4gbmV3RWxlbWVudHM7XG59XG5cbmZ1bmN0aW9uIHBvcnRBdHRyaWJ1dGVzVG9FbGVtZW50KG9sZEVsLCBuZXdFbCkge1xuICBmb3IgKGNvbnN0IGF0dHJpYnV0ZSBvZiBvbGRFbC5hdHRyaWJ1dGVzKSB7XG4gICAgbmV3RWwuc2V0QXR0cmlidXRlKGF0dHJpYnV0ZS5uYW1lLCBhdHRyaWJ1dGUubm9kZVZhbHVlKTtcbiAgfVxufVxuXG5mdW5jdGlvbiBnZXRQZXJzaXN0ZW50Tm9kZXNGcm9tRG9jdW1lbnQoZG9tKSB7XG4gIC8vIEZpbmQgcGVyc2lzdGVudCBub2Rlc1xuICBjb25zdCBwZXJzaXN0ZW50Tm9kZXNGcm9tRG9tID0gWy4uLmRvbS5xdWVyeVNlbGVjdG9yQWxsKCdbZGF0YS1wZXJzaXN0ZW50XScpXS5tYXAoZWwgPT4gZWwuZ2V0QXR0cmlidXRlKCdkYXRhLXBlcnNpc3RlbnQnKSk7XG4gIGNvbnN0IHBlcnNpc3RlbnROb2Rlc0Zyb21Cb2R5ID0gWy4uLmRvY3VtZW50LmJvZHkucXVlcnlTZWxlY3RvckFsbCgnW2RhdGEtcGVyc2lzdGVudF0nKV07XG4gIC8vIENyb3NzIG1hdGNoIHRoZSBwZXJzaXN0ZW50IG5vZGVzIGJldHdlZW4gY3VycmVudCBwYWdlIGFuZCBuZXcgcGFnZSB0byBmaW5kIHZhbGlkIG9uZXNcbiAgY29uc3QgdmFsaWRQZXJzaXN0ZW50Tm9kZXMgPSBwZXJzaXN0ZW50Tm9kZXNGcm9tQm9keS5maWx0ZXIoZWwgPT4ge1xuICAgIHJldHVybiBwZXJzaXN0ZW50Tm9kZXNGcm9tRG9tLmluY2x1ZGVzKGVsLmdldEF0dHJpYnV0ZSgnZGF0YS1wZXJzaXN0ZW50JykpO1xuICB9KTtcbiAgLy8gQ2xvbmVcbiAgcmV0dXJuIHZhbGlkUGVyc2lzdGVudE5vZGVzLm1hcChlbCA9PiBlbC5jbG9uZU5vZGUodHJ1ZSkpO1xufVxuXG5mdW5jdGlvbiByZXBsYWNlQXR0cmlidXRlc09uRG9jdW1lbnRCb2R5KGRvbSkge1xuICB3aGlsZSAoZG9jdW1lbnQuYm9keS5hdHRyaWJ1dGVzLmxlbmd0aCA+IDApIHtcbiAgICBkb2N1bWVudC5ib2R5LnJlbW92ZUF0dHJpYnV0ZShkb2N1bWVudC5ib2R5LmF0dHJpYnV0ZXNbMF0ubmFtZSk7XG4gIH1cbiAgLy8gU2V0IGF0dHJpYnV0ZXMgb24gdGhlIGJvZHlcbiAgZm9yIChjb25zdCBhdHRyaWJ1dGUgb2YgZG9tLmJvZHkuYXR0cmlidXRlcykge1xuICAgIGRvY3VtZW50LmJvZHkuc2V0QXR0cmlidXRlKGF0dHJpYnV0ZS5uYW1lLCBhdHRyaWJ1dGUubm9kZVZhbHVlKTtcbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBoYW5kbGVCYWNrQnV0dG9uKCkge1xuICBhd2FpdCBnZXROZXdQYWdlQ29udGVudCh3aW5kb3cuaGlzdG9yeS5zdGF0ZT8udGFyZ2V0SHJlZiB8fCB3aW5kb3cubG9jYXRpb24uaHJlZik7XG59XG5cbmZ1bmN0aW9uIGlzUmVsYXRpdmVVUkwodXJsKSB7XG4gIGlmICh0eXBlb2YgdXJsICE9PSAnc3RyaW5nJykgcmV0dXJuIGZhbHNlO1xuICB0cnkge1xuICAgIG5ldyBVUkwodXJsKTtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGlmICh1cmw/LnN0YXJ0c1dpdGgoJy8vJykpIHJldHVybiBmYWxzZTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxufVxuXG5mdW5jdGlvbiBpc0V4dGVybmFsVVJMKHVybCkge1xuICBpZiAoaXNSZWxhdGl2ZVVSTCh1cmwpKSByZXR1cm4gZmFsc2U7XG4gIHRyeSB7XG4gICAgY29uc3QgY29tcHV0ZWRVUkwgPSBuZXcgVVJMKHVybCk7XG4gICAgcmV0dXJuIGNvbXB1dGVkVVJMPy5vcmlnaW4gIT09IHdpbmRvdy5sb2NhdGlvbj8ub3JpZ2luO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gZmFsc2U7IC8vIE5vdCBhIFVSTFxuICB9XG59XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgcmVwbGFjZURvY3VtZW50Q29udGVudHNXaXRoTGlua1Jlc3BvbnNlKTtcbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdwb3BzdGF0ZScsIGhhbmRsZUJhY2tCdXR0b24pO1xuXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBRUEsZUFBZSwwQ0FBMEM7QUFDdkQsUUFBTSxRQUFRLENBQUMsR0FBRyxTQUFTLGlCQUFpQixZQUFZLENBQUM7QUFDekQsYUFBVyxRQUFRLE9BQU87QUFDeEIsU0FBSyxpQkFBaUIsU0FBUyxxQkFBcUI7QUFBQSxFQUN0RDtBQUVGO0FBRUEsZUFBZSxzQkFBc0IsR0FBRztBQUN0QyxRQUFNLGFBQWEsR0FBRyxRQUFRLGFBQWEsTUFBTTtBQUNqRCxNQUFJLENBQUMsV0FBWTtBQUNqQixNQUFJLGNBQWMsVUFBVSxLQUFLLENBQUMsR0FBRyxRQUFRLGFBQWEsc0JBQXNCLEVBQUc7QUFDbkYsSUFBRSxlQUFlO0FBQ2pCLE1BQUk7QUFDRixVQUFNLGtCQUFrQixVQUFVO0FBQ2xDLFFBQUksZUFBZSxPQUFPLFNBQVMsU0FBVTtBQUM3QyxXQUFPLFFBQVE7QUFBQSxNQUNiO0FBQUEsUUFDRSxNQUFNO0FBQUEsUUFDTjtBQUFBLE1BQ0Y7QUFBQSxNQUNBO0FBQUEsTUFDQTtBQUFBLElBQ0Y7QUFDQSxVQUFNLFFBQVEsSUFBSSxZQUFZLHFCQUFxQjtBQUFBLE1BQ2pELFFBQVE7QUFBQSxRQUNOLE1BQU07QUFBQSxNQUNSO0FBQUEsSUFDRixD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
@@ -0,0 +1,420 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/scripts/build.ts
4
+ import path3 from "path";
5
+ import fs2 from "fs-extra";
6
+ import { build } from "esbuild";
7
+ import builtinModules from "builtin-modules";
8
+ import ts2 from "typescript";
9
+
10
+ // src/build/typescript-builder.ts
11
+ import ts from "typescript";
12
+ import fs from "fs-extra";
13
+ import path from "path";
14
+ function findDataAttributesAndFunctions(node, sourceFile, foundDataAttributes = [], foundFunctions = []) {
15
+ if (node.kind === ts.SyntaxKind.JsxAttribute) {
16
+ const attribute = getAttributeValuePair(node, sourceFile);
17
+ if (attribute?.[0] === "x-data" && attribute?.[1]) {
18
+ foundDataAttributes.push(attribute[1].replace(/[^a-zA-Z0-9]/g, ""));
19
+ }
20
+ }
21
+ if (node.kind === ts.SyntaxKind.SourceFile) {
22
+ node.forEachChild((child) => {
23
+ if ([ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.VariableDeclaration].includes(child.kind)) {
24
+ const functions = getFunctionDeclarationValue(child, sourceFile);
25
+ foundFunctions.push(functions);
26
+ }
27
+ });
28
+ }
29
+ node.forEachChild((child) => {
30
+ findDataAttributesAndFunctions(child, sourceFile, foundDataAttributes, foundFunctions);
31
+ });
32
+ return {
33
+ foundDataAttributes,
34
+ foundFunctions
35
+ };
36
+ }
37
+ function getAttributeValuePair(node, sourceFile) {
38
+ const result = [];
39
+ node.forEachChild((child) => {
40
+ result.push(child.getText(sourceFile));
41
+ });
42
+ return result;
43
+ }
44
+ function getFunctionDeclarationValue(node, sourceFile) {
45
+ const result = {
46
+ name: null,
47
+ isDefaultExport: false,
48
+ hasExport: false
49
+ };
50
+ node.forEachChild((child) => {
51
+ if (child.kind === ts.SyntaxKind.ExportKeyword) {
52
+ result.hasExport = true;
53
+ }
54
+ if (child.kind === ts.SyntaxKind.DefaultKeyword) {
55
+ result.isDefaultExport = true;
56
+ }
57
+ if (child.kind === ts.SyntaxKind.Identifier) {
58
+ result.name = child.getText(sourceFile);
59
+ }
60
+ });
61
+ return result;
62
+ }
63
+ function convertEntryPointsToSingleFile(entryPoints, tempWritePath) {
64
+ fs.writeFileSync(
65
+ tempWritePath,
66
+ entryPoints.map((entry) => `import "${entry}"`).join(";\n")
67
+ );
68
+ }
69
+ function removeClientScriptInTSXFile(pathName) {
70
+ const content = fs.readFileSync(pathName, "utf-8");
71
+ const source = ts.createSourceFile(
72
+ pathName,
73
+ content,
74
+ ts.ScriptTarget.Latest
75
+ );
76
+ let toRemoveFrom;
77
+ let clientDataStart;
78
+ const clientImportsToHoist = [];
79
+ const clientImportsToReplace = [];
80
+ source.forEachChild((child) => {
81
+ if (child.kind === ts.SyntaxKind.ExpressionStatement) {
82
+ const text = child.getText(source);
83
+ if (text.startsWith("<script")) {
84
+ toRemoveFrom = child.pos;
85
+ clientDataStart = child.end;
86
+ }
87
+ }
88
+ if (child.kind === ts.SyntaxKind.ImportDeclaration && child.pos >= clientDataStart) {
89
+ const text = child.getText(source);
90
+ child.forEachChild((child2) => {
91
+ if (child2.kind === ts.SyntaxKind.StringLiteral) {
92
+ const text2 = child2.getText(source);
93
+ const importPath = text2.replace(/[\"\']/g, "");
94
+ const isRelativeImport = importPath.startsWith(".") || importPath.startsWith("/");
95
+ if (!isRelativeImport) return;
96
+ clientImportsToReplace.push({
97
+ old: importPath,
98
+ new: path.join(path.dirname(pathName), importPath)
99
+ });
100
+ }
101
+ });
102
+ clientImportsToHoist.push(text);
103
+ }
104
+ });
105
+ let clientContent = !isNaN(clientDataStart) ? content.slice(clientDataStart) : "";
106
+ for (const clientImport of clientImportsToReplace) {
107
+ clientContent = clientContent.replace(clientImport.old, clientImport.new);
108
+ }
109
+ return {
110
+ content: !isNaN(toRemoveFrom) ? clientImportsToHoist.join("\n") + content.slice(0, toRemoveFrom) : content,
111
+ clientContent,
112
+ fullContent: content,
113
+ toRemoveFrom,
114
+ clientDataStart
115
+ };
116
+ }
117
+
118
+ // src/scripts/build.ts
119
+ import { globSync } from "glob";
120
+ import postcss from "postcss";
121
+ import tailwindPostcss from "@tailwindcss/postcss";
122
+
123
+ // src/util/get-config.ts
124
+ import path2 from "path";
125
+
126
+ // src/util/require.ts
127
+ import { createRequire } from "module";
128
+ var require2 = createRequire(import.meta.url);
129
+ var require_default = require2;
130
+
131
+ // src/util/get-config.ts
132
+ var rootDir = process.cwd();
133
+ function fromRoot(pathName) {
134
+ if (!pathName) return rootDir;
135
+ return path2.join(rootDir, pathName);
136
+ }
137
+ var userConfig = require_default(path2.join(process.cwd(), "./xpine.config.mjs")).default;
138
+ var configDefaults = {
139
+ rootDir: fromRoot(),
140
+ srcDir: fromRoot("./src"),
141
+ distDir: fromRoot("./dist"),
142
+ packageJsonPath: fromRoot("package.json"),
143
+ // We need to use getters here in the event someone wants to change folders, such as the dist folder
144
+ get distPublicDir() {
145
+ return path2.join(this.distDir, "./public");
146
+ },
147
+ get distPublicScriptsDir() {
148
+ return path2.join(this.distPublicDir, "./scripts");
149
+ },
150
+ get distTempFolder() {
151
+ return path2.join(this.distDir, "./temp");
152
+ },
153
+ get clientJSBundlePath() {
154
+ return path2.join(this.distPublicScriptsDir, "./app.js");
155
+ },
156
+ get alpineDataPath() {
157
+ return path2.join(this.distTempFolder, "./alpine-data.ts");
158
+ },
159
+ get serverDistDir() {
160
+ return path2.join(this.distDir, "./server");
161
+ },
162
+ get serverDistAppPath() {
163
+ return path2.join(this.serverDistDir, "./app.js");
164
+ },
165
+ // Important dirs/paths
166
+ get pagesDir() {
167
+ return path2.join(this.srcDir, "./pages");
168
+ },
169
+ get publicDir() {
170
+ return path2.join(this.srcDir, "./public");
171
+ },
172
+ get serverDir() {
173
+ return path2.join(this.srcDir, "./server");
174
+ },
175
+ get runDir() {
176
+ return path2.join(this.serverDir, "./run");
177
+ },
178
+ get serverAppPath() {
179
+ return path2.join(this.serverDir, "./app.ts");
180
+ },
181
+ get globalCSSFile() {
182
+ return path2.join(this.publicDir, "./styles/global.css");
183
+ }
184
+ };
185
+ var config = {
186
+ ...configDefaults,
187
+ ...userConfig
188
+ };
189
+
190
+ // src/scripts/build.ts
191
+ var extensions = [".ts", ".tsx"];
192
+ var packageJson = JSON.parse(fs2.readFileSync(config.packageJsonPath, "utf-8"));
193
+ var allPackages = Object.keys(packageJson.devDependencies).concat(Object.keys(packageJson.dependencies));
194
+ var allPackagesIncludingNode = allPackages.concat(builtinModules);
195
+ async function buildApp(isDev = false) {
196
+ try {
197
+ const srcDirFiles = globSync(config.srcDir + "/**/*.{js,ts,tsx,jsx}");
198
+ const { componentData, dataFiles } = await buildAppFiles(srcDirFiles, isDev);
199
+ const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
200
+ await buildClientSideFiles([alpineDataFile], isDev);
201
+ fs2.removeSync(config.distTempFolder);
202
+ await buildCSS();
203
+ await buildPublicFolderSymlinks();
204
+ } catch (err) {
205
+ console.error("Build failed");
206
+ console.error(err);
207
+ }
208
+ }
209
+ async function buildAppFiles(files, isDev) {
210
+ const componentData = [];
211
+ const dataFiles = [];
212
+ const backendFiles = files.filter((file) => extensions.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
213
+ fs2.ensureDirSync(config.distDir);
214
+ await build({
215
+ entryPoints: backendFiles,
216
+ format: "esm",
217
+ platform: "node",
218
+ outdir: config.distDir,
219
+ bundle: true,
220
+ sourcemap: isDev ? "inline" : false,
221
+ external: allPackages,
222
+ jsx: "transform",
223
+ minify: !isDev,
224
+ plugins: [
225
+ {
226
+ name: "add-dot-js",
227
+ setup(build2) {
228
+ build2.onResolve({ filter: /.*/ }, (args) => {
229
+ const hasAtSign = args.path.startsWith("@");
230
+ const isPackage = hasAtSign ? allPackagesIncludingNode.includes(args.path) : allPackagesIncludingNode.includes(args.path.split("/").shift());
231
+ if (args.importer && !isPackage) {
232
+ const calculatedDir = path3.join(args.resolveDir, args.path);
233
+ let existsAsFile = false;
234
+ for (const extension of extensions) {
235
+ const asFile = calculatedDir + extension;
236
+ const exists = fs2.existsSync(asFile);
237
+ if (exists) existsAsFile = true;
238
+ }
239
+ let outputPath = args.path + (existsAsFile ? "" : "/index") + ".js";
240
+ outputPath = args.path.endsWith(".js") || args.path.endsWith(".mjs") ? args.path : outputPath;
241
+ return { path: outputPath, external: true };
242
+ }
243
+ });
244
+ }
245
+ },
246
+ {
247
+ name: "insert-html-banner-and-remove-client-scripts",
248
+ setup(build2) {
249
+ build2.onLoad({ filter: /.tsx/ }, (args) => {
250
+ const cleanedContent = removeClientScriptInTSXFile(args.path);
251
+ const htmlImportStart = "import { html } from 'xpine';\n";
252
+ const newContent = `${htmlImportStart}${cleanedContent.content}`;
253
+ componentData.push({
254
+ ...args,
255
+ contents: `${htmlImportStart}${cleanedContent.fullContent}`,
256
+ clientContent: cleanedContent.clientContent
257
+ });
258
+ return {
259
+ contents: newContent,
260
+ loader: "tsx"
261
+ };
262
+ });
263
+ }
264
+ },
265
+ {
266
+ name: "get-data-files",
267
+ setup(build2) {
268
+ build2.onLoad({ filter: /\.data\.(js|mjs|ts)$/ }, (args) => {
269
+ const contents = fs2.readFileSync(args.path, "utf-8");
270
+ dataFiles.push({
271
+ ...args,
272
+ contents
273
+ });
274
+ return {
275
+ contents,
276
+ loader: "ts"
277
+ };
278
+ });
279
+ }
280
+ }
281
+ ]
282
+ });
283
+ await logSize(config.distDir, "app");
284
+ return {
285
+ componentData,
286
+ dataFiles
287
+ };
288
+ }
289
+ async function buildClientSideFiles(alpineDataFiles = [], isDev) {
290
+ const tempFilePath = path3.join(config.distTempFolder, "./app.ts");
291
+ fs2.ensureFileSync(tempFilePath);
292
+ const pagesScriptsGlob = config.publicDir + "/scripts/pages/**/*.{js,ts}";
293
+ const clientFiles = globSync(
294
+ config.publicDir + "/**/*.{js,ts}",
295
+ {
296
+ ignore: pagesScriptsGlob
297
+ }
298
+ );
299
+ convertEntryPointsToSingleFile(alpineDataFiles.concat(clientFiles), tempFilePath);
300
+ if (isDev) writeDevServerClientSideCode(tempFilePath);
301
+ writeSpaClientSideCode(tempFilePath);
302
+ await build({
303
+ entryPoints: [tempFilePath],
304
+ bundle: true,
305
+ outdir: config.distPublicScriptsDir,
306
+ minify: !isDev,
307
+ sourcemap: isDev ? "inline" : false
308
+ });
309
+ const pagesFiles = globSync(pagesScriptsGlob);
310
+ await build({
311
+ entryPoints: pagesFiles || [],
312
+ bundle: true,
313
+ outdir: config.distPublicScriptsDir + "/pages",
314
+ minify: !isDev,
315
+ sourcemap: isDev ? "inline" : false
316
+ });
317
+ await logSize(config.distPublicDir, "client");
318
+ }
319
+ function writeDevServerClientSideCode(tempFilePath) {
320
+ const devServerPath = path3.join(import.meta.dirname, "../static/dev-server.js");
321
+ const content = fs2.readFileSync(devServerPath, "utf-8");
322
+ fs2.appendFileSync(tempFilePath, `
323
+ ` + content);
324
+ }
325
+ function writeSpaClientSideCode(tempFilePath) {
326
+ const spaPath = path3.join(import.meta.dirname, "../static/spa.js");
327
+ const content = fs2.readFileSync(spaPath, "utf-8");
328
+ fs2.appendFileSync(tempFilePath, `
329
+ ` + content);
330
+ }
331
+ async function buildAlpineDataFile(componentData, dataFiles) {
332
+ const output = {
333
+ imports: [
334
+ "import Alpine from 'alpinejs';"
335
+ ],
336
+ code: [],
337
+ end: [
338
+ "window.Alpine = Alpine;"
339
+ ]
340
+ };
341
+ const dataFunctionResults = {
342
+ foundDataAttributes: [],
343
+ foundFunctions: [],
344
+ foundImports: []
345
+ };
346
+ const componentsAndDataFiles = componentData.concat(dataFiles);
347
+ for (const component of componentsAndDataFiles) {
348
+ const sourceFile = ts2.createSourceFile(
349
+ component.path,
350
+ component.contents,
351
+ ts2.ScriptTarget.Latest
352
+ );
353
+ const dataFunctionResult = findDataAttributesAndFunctions(sourceFile, sourceFile);
354
+ dataFunctionResults.foundDataAttributes.push(...dataFunctionResult.foundDataAttributes);
355
+ const foundFunctionsWithPath = dataFunctionResult.foundFunctions.map((item) => {
356
+ return {
357
+ ...item,
358
+ path: component.path,
359
+ content: component.clientContent
360
+ };
361
+ });
362
+ dataFunctionResults.foundFunctions.push(...foundFunctionsWithPath);
363
+ }
364
+ const validDataFunctions = dataFunctionResults.foundFunctions.filter((item) => {
365
+ return dataFunctionResults.foundDataAttributes.includes(item.name) && item.content;
366
+ });
367
+ for (const dataFunction of validDataFunctions) {
368
+ if (!dataFunction.hasExport) continue;
369
+ output.code.push(dataFunction.content);
370
+ output.code.push(`Alpine.data('${dataFunction.name}', ${dataFunction.name});`);
371
+ }
372
+ const result = output.imports.join("\n") + "\n" + output.code.join("\n") + "\n" + output.end.join("\n");
373
+ fs2.ensureFileSync(config.alpineDataPath);
374
+ fs2.writeFileSync(config.alpineDataPath, result);
375
+ return config.alpineDataPath;
376
+ }
377
+ async function buildCSS() {
378
+ const cssFiles = globSync(config.srcDir + "/**/*.css");
379
+ for (const file of cssFiles) {
380
+ const fileContents = fs2.readFileSync(file, "utf-8");
381
+ const result = await postcss([tailwindPostcss()]).process(fileContents, { from: file });
382
+ const newPath = file.replace(config.srcDir, config.distDir);
383
+ fs2.ensureFileSync(newPath);
384
+ fs2.writeFileSync(newPath, result.css);
385
+ }
386
+ logSize(config.distPublicDir, "css");
387
+ }
388
+ async function buildPublicFolderSymlinks() {
389
+ const files = globSync(config.publicDir + "/**/*.*", {
390
+ ignore: "/**/*.{css,js,ts,tsx,jsx}"
391
+ });
392
+ for (const file of files) {
393
+ try {
394
+ const newPath = file.replace(config.srcDir, config.distDir);
395
+ const splitNewPath = newPath.split("/");
396
+ splitNewPath.pop();
397
+ const newDir = splitNewPath.join("/");
398
+ fs2.ensureDirSync(newDir);
399
+ fs2.symlinkSync(file, newPath);
400
+ } catch {
401
+ }
402
+ }
403
+ }
404
+ async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
405
+ const files = globSync(pathName + "/**/*" + (type === "css" ? ".css" : ""));
406
+ const fileSizes = files.map((file) => {
407
+ if (!validExtensions.find((ext) => file.endsWith(ext))) return false;
408
+ return {
409
+ file,
410
+ size: fs2.statSync(file).size / (1024 * 1e3)
411
+ };
412
+ }).filter(Boolean);
413
+ const totalSize = fileSizes.reduce((total, current) => {
414
+ return current.size + total;
415
+ }, 0);
416
+ console.info(`[${totalSize.toFixed(3)} MB] Built ${type}`);
417
+ }
418
+
419
+ // src/scripts/xpine-build.ts
420
+ buildApp();