elm-pages-v3-beta/index.js

190 lines
5.3 KiB
JavaScript
Raw Normal View History

const elmPagesVersion = require("./package.json").version;
2020-01-21 17:17:28 +03:00
let prefetchedPages;
let initialLocationHash;
let elmViewRendered = false;
let headTagsAdded = false;
2020-01-21 17:17:28 +03:00
module.exports = function pagesInit(
2020-01-21 17:17:28 +03:00
/** @type { mainElmModule: { init: any } } */ { mainElmModule }
) {
2020-01-21 17:17:28 +03:00
prefetchedPages = [window.location.pathname];
initialLocationHash = document.location.hash.replace(/^#/, "");
return new Promise(function(resolve, reject) {
document.addEventListener("DOMContentLoaded", _ => {
new MutationObserver(function() {
elmViewRendered = true;
if (headTagsAdded) {
document.dispatchEvent(new Event("prerender-trigger"));
}
}).observe(
document.body,
{ attributes: true, childList: true, subtree: true }
);
2020-01-21 17:17:28 +03:00
loadContentAndInitializeApp(mainElmModule).then(resolve, reject);
});
})
};
function loadContentAndInitializeApp(/** @type { init: any } */ mainElmModule) {
const isPrerendering = navigator.userAgent.indexOf("Headless") >= 0
const path = window.location.pathname.replace(/(\w)$/, "$1/")
return httpGet(`${window.location.origin}${path}content.json`).then(function(/** @type JSON */ contentJson) {
2020-01-21 17:17:28 +03:00
const app = mainElmModule.init({
flags: {
secrets: null,
baseUrl: isPrerendering
? window.location.origin
: document.querySelector("base").href,
isPrerendering: isPrerendering,
contentJson
}
});
app.ports.toJsPort.subscribe((
2020-01-28 08:40:30 +03:00
/** @type { { head: HeadTag[], allRoutes: string[] } } */ fromElm
) => {
appendTag({
name: "meta",
attributes: [
["name", "generator"],
["content", `elm-pages v${elmPagesVersion}`]
]
});
2020-01-28 08:40:30 +03:00
window.allRoutes = fromElm.allRoutes;
2020-01-21 17:17:28 +03:00
if (navigator.userAgent.indexOf("Headless") >= 0) {
2020-01-28 08:40:30 +03:00
fromElm.head.forEach(headTag => {
appendTag(headTag);
});
headTagsAdded = true;
if (elmViewRendered) {
document.dispatchEvent(new Event("prerender-trigger"));
}
} else {
setupLinkPrefetching();
}
});
2020-01-21 17:17:28 +03:00
return app
});
2020-01-21 17:17:28 +03:00
}
2020-02-01 19:21:02 +03:00
function setupLinkPrefetching() {
2020-01-21 17:17:28 +03:00
new MutationObserver(observeFirstRender).observe(document.body, {
attributes: true,
childList: true,
subtree: true
});
}
function loadNamedAnchor() {
if (initialLocationHash !== "") {
const namedAnchor = document.querySelector(
`[name=${initialLocationHash}]`
);
namedAnchor && namedAnchor.scrollIntoView();
}
2020-01-21 17:17:28 +03:00
}
2020-01-21 17:17:28 +03:00
function observeFirstRender(
/** @type {MutationRecord[]} */ mutationList,
/** @type {MutationObserver} */ firstRenderObserver
) {
loadNamedAnchor();
for (let mutation of mutationList) {
if (mutation.type === "childList") {
setupLinkPrefetchingHelp();
}
}
2020-01-21 17:17:28 +03:00
firstRenderObserver.disconnect();
new MutationObserver(observeUrlChanges).observe(document.body.children[0], {
attributes: true,
childList: false,
subtree: false
});
}
2020-01-21 17:17:28 +03:00
function observeUrlChanges(
/** @type {MutationRecord[]} */ mutationList,
/** @type {MutationObserver} */ _theObserver
) {
for (let mutation of mutationList) {
if (
mutation.type === "attributes" &&
mutation.attributeName === "data-url"
) {
setupLinkPrefetchingHelp();
}
}
2020-01-21 17:17:28 +03:00
}
function setupLinkPrefetchingHelp(
/** @type {MutationObserver} */ _mutationList,
/** @type {MutationObserver} */ _theObserver
) {
const links = document.querySelectorAll("a");
links.forEach(link => {
2020-02-01 19:21:02 +03:00
// console.log(link.pathname);
2020-01-21 17:17:28 +03:00
link.addEventListener("mouseenter", function(event) {
2019-09-17 19:40:35 +03:00
if (
2020-01-21 17:17:28 +03:00
event &&
event.target &&
event.target instanceof HTMLAnchorElement
2019-09-17 19:40:35 +03:00
) {
2020-01-21 17:17:28 +03:00
prefetchIfNeeded(event.target);
} else {
2020-02-01 19:21:02 +03:00
// console.log("Couldn't prefetch with event", event);
}
});
2020-01-21 17:17:28 +03:00
});
}
2020-01-21 17:17:28 +03:00
function prefetchIfNeeded(/** @type {HTMLAnchorElement} */ target) {
if (target.host === window.location.host) {
if (prefetchedPages.includes(target.pathname)) {
2020-02-01 19:21:02 +03:00
// console.log("Already preloaded", target.href);
2020-01-28 08:40:30 +03:00
} else if (!allRoutes.includes(target.pathname)) {
2020-02-01 19:21:02 +03:00
// console.log("Not a known route, skipping preload", target.pathname);
2020-01-28 08:40:30 +03:00
}
else {
2020-01-21 17:17:28 +03:00
prefetchedPages.push(target.pathname);
2020-02-01 19:21:02 +03:00
// console.log("Preloading...", target.pathname);
2020-01-21 17:17:28 +03:00
const link = document.createElement("link");
link.setAttribute("as", "fetch");
link.setAttribute("rel", "prefetch");
link.setAttribute("href", origin + target.pathname + "/content.json");
document.head.appendChild(link);
}
}
2020-01-21 17:17:28 +03:00
}
2020-01-21 17:17:28 +03:00
/** @typedef {{ name: string; attributes: string[][]; }} HeadTag */
function appendTag(/** @type {HeadTag} */ tagDetails) {
const meta = document.createElement(tagDetails.name);
tagDetails.attributes.forEach(([name, value]) => {
meta.setAttribute(name, value);
});
document.getElementsByTagName("head")[0].appendChild(meta);
}
2020-01-21 17:17:28 +03:00
function httpGet(/** @type string */ theUrl) {
return new Promise(function(resolve, reject) {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
2020-01-21 17:17:28 +03:00
resolve(JSON.parse(xmlHttp.responseText));
}
2020-01-21 17:17:28 +03:00
xmlHttp.onerror = reject;
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
2020-01-21 17:17:28 +03:00
})
}