2019-09-17 00:30:04 +03:00
|
|
|
const elmPagesVersion = require("./package.json").version;
|
|
|
|
|
2020-01-21 17:17:28 +03:00
|
|
|
let prefetchedPages;
|
|
|
|
let initialLocationHash;
|
2020-01-31 19:46:18 +03:00
|
|
|
let elmViewRendered = false;
|
|
|
|
let headTagsAdded = false;
|
2020-01-21 17:17:28 +03:00
|
|
|
|
2019-09-17 01:22:05 +03:00
|
|
|
module.exports = function pagesInit(
|
2020-01-21 17:17:28 +03:00
|
|
|
/** @type { mainElmModule: { init: any } } */ { mainElmModule }
|
2019-09-17 01:22:05 +03:00
|
|
|
) {
|
2020-01-21 17:17:28 +03:00
|
|
|
prefetchedPages = [window.location.pathname];
|
|
|
|
initialLocationHash = document.location.hash.replace(/^#/, "");
|
|
|
|
|
2020-04-10 15:06:57 +03:00
|
|
|
return new Promise(function (resolve, reject) {
|
2020-01-21 17:17:28 +03:00
|
|
|
document.addEventListener("DOMContentLoaded", _ => {
|
2020-04-10 15:06:57 +03:00
|
|
|
new MutationObserver(function () {
|
2020-02-14 07:56:18 +03:00
|
|
|
elmViewRendered = true;
|
|
|
|
if (headTagsAdded) {
|
|
|
|
document.dispatchEvent(new Event("prerender-trigger"));
|
|
|
|
}
|
|
|
|
}).observe(
|
|
|
|
document.body,
|
|
|
|
{ attributes: true, childList: true, subtree: true }
|
|
|
|
);
|
2020-01-31 19:46:18 +03:00
|
|
|
|
2020-01-21 17:17:28 +03:00
|
|
|
loadContentAndInitializeApp(mainElmModule).then(resolve, reject);
|
|
|
|
});
|
|
|
|
})
|
|
|
|
};
|
|
|
|
|
|
|
|
function loadContentAndInitializeApp(/** @type { init: any } */ mainElmModule) {
|
2020-02-14 07:56:18 +03:00
|
|
|
const isPrerendering = navigator.userAgent.indexOf("Headless") >= 0
|
|
|
|
const path = window.location.pathname.replace(/(\w)$/, "$1/")
|
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
return Promise.all([
|
|
|
|
getConfig(),
|
|
|
|
httpGet(`${window.location.origin}${path}content.json`)]).then(function (/** @type {[DevServerConfig?, JSON]} */[devServerConfig, contentJson]) {
|
|
|
|
const app = mainElmModule.init({
|
|
|
|
flags: {
|
|
|
|
secrets: null,
|
|
|
|
baseUrl: isPrerendering
|
|
|
|
? window.location.origin
|
|
|
|
: document.baseURI,
|
|
|
|
isPrerendering: isPrerendering,
|
|
|
|
isDevServer: !!module.hot,
|
|
|
|
isElmDebugMode: devServerConfig ? devServerConfig.elmDebugger : false,
|
|
|
|
contentJson,
|
|
|
|
}
|
|
|
|
});
|
2019-08-15 03:42:02 +03:00
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
app.ports.toJsPort.subscribe((
|
2020-04-10 15:06:57 +03:00
|
|
|
/** @type { { head: SeoTag[], allRoutes: string[] } } */ fromElm
|
2020-05-10 06:33:32 +03:00
|
|
|
) => {
|
|
|
|
appendTag({
|
2020-05-11 01:00:55 +03:00
|
|
|
type: 'head',
|
2020-05-10 06:33:32 +03:00
|
|
|
name: "meta",
|
|
|
|
attributes: [
|
|
|
|
["name", "generator"],
|
|
|
|
["content", `elm-pages v${elmPagesVersion}`]
|
|
|
|
]
|
|
|
|
});
|
2020-02-14 07:56:18 +03:00
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
window.allRoutes = fromElm.allRoutes.map(route => new URL(route, document.baseURI).href);
|
2020-01-21 17:17:28 +03:00
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
if (navigator.userAgent.indexOf("Headless") >= 0) {
|
|
|
|
fromElm.head.forEach(headTag => {
|
2020-05-11 01:00:55 +03:00
|
|
|
if (headTag.type === 'head') {
|
|
|
|
appendTag(headTag);
|
|
|
|
} else if (headTag.type === 'json-ld') {
|
|
|
|
appendJsonLdTag(headTag);
|
|
|
|
} else {
|
|
|
|
throw new Error(`Unknown tag type #{headTag}`)
|
|
|
|
}
|
2020-05-10 06:33:32 +03:00
|
|
|
});
|
|
|
|
headTagsAdded = true;
|
|
|
|
if (elmViewRendered) {
|
|
|
|
document.dispatchEvent(new Event("prerender-trigger"));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
setupLinkPrefetching();
|
2020-04-14 01:12:16 +03:00
|
|
|
}
|
2019-09-17 00:30:04 +03:00
|
|
|
});
|
2020-02-14 07:56:18 +03:00
|
|
|
|
2020-01-21 17:17:28 +03:00
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
if (module.hot) {
|
|
|
|
// found this trick in the next.js source code
|
|
|
|
// https://github.com/zeit/next.js/blob/886037b1bac4bdbfeb689b032c1612750fb593f7/packages/next/client/dev/error-overlay/eventsource.js
|
|
|
|
// https://github.com/zeit/next.js/blob/886037b1bac4bdbfeb689b032c1612750fb593f7/packages/next/client/dev/dev-build-watcher.js
|
|
|
|
// more details about this API at https://www.html5rocks.com/en/tutorials/eventsource/basics/
|
|
|
|
let source = new window.EventSource('/__webpack_hmr')
|
|
|
|
source.addEventListener('message', (e) => {
|
|
|
|
// console.log('message!!!!!', e)
|
|
|
|
// console.log(e.data.action)
|
|
|
|
// console.log('ACTION', e.data.action);
|
|
|
|
// if (e.data && e.data.action)
|
2020-05-03 21:40:47 +03:00
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
if (event.data === '\uD83D\uDC93') {
|
|
|
|
// heartbeat
|
2020-04-10 15:06:57 +03:00
|
|
|
} else {
|
2020-05-10 06:33:32 +03:00
|
|
|
const obj = JSON.parse(event.data)
|
|
|
|
// console.log('obj.action', obj.action);
|
2020-05-03 21:40:47 +03:00
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
if (obj.action === 'building') {
|
2020-05-12 02:43:09 +03:00
|
|
|
app.ports.fromJsPort.send({ action: 'hmr-check' });
|
2020-05-10 06:33:32 +03:00
|
|
|
} else if (obj.action === 'built') {
|
2020-05-03 21:40:47 +03:00
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
let currentPath = window.location.pathname.replace(/(\w)$/, "$1/")
|
|
|
|
httpGet(`${window.location.origin}${currentPath}content.json`).then(function (/** @type JSON */ contentJson) {
|
2020-05-03 21:40:47 +03:00
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
app.ports.fromJsPort.send({ contentJson: contentJson });
|
|
|
|
});
|
|
|
|
}
|
2020-05-03 03:22:53 +03:00
|
|
|
|
2020-01-31 19:46:18 +03:00
|
|
|
}
|
2020-05-10 06:33:32 +03:00
|
|
|
})
|
2020-01-20 19:03:42 +03:00
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
}
|
2020-01-20 19:03:42 +03:00
|
|
|
|
2020-05-10 06:33:32 +03:00
|
|
|
return app
|
|
|
|
});
|
2020-01-21 17:17:28 +03:00
|
|
|
}
|
2019-08-15 03:42:02 +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();
|
2019-09-15 02:55:33 +03:00
|
|
|
}
|
2020-01-21 17:17:28 +03:00
|
|
|
}
|
2019-09-15 02:55:33 +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();
|
2019-09-23 01:51:22 +03:00
|
|
|
}
|
|
|
|
}
|
2020-01-21 17:17:28 +03:00
|
|
|
firstRenderObserver.disconnect();
|
|
|
|
new MutationObserver(observeUrlChanges).observe(document.body.children[0], {
|
|
|
|
attributes: true,
|
|
|
|
childList: false,
|
|
|
|
subtree: false
|
|
|
|
});
|
|
|
|
}
|
2019-09-23 01:51:22 +03:00
|
|
|
|
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();
|
2019-09-15 07:06:13 +03:00
|
|
|
}
|
|
|
|
}
|
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-04-10 15:06:57 +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);
|
2019-09-15 07:06:13 +03:00
|
|
|
}
|
2019-09-15 02:55:33 +03:00
|
|
|
});
|
2020-01-21 17:17:28 +03:00
|
|
|
});
|
|
|
|
}
|
2019-09-15 02:22:13 +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);
|
|
|
|
// console.log("Not a known route, skipping preload", target.pathname);
|
2020-03-28 23:02:35 +03:00
|
|
|
} else if (!allRoutes.includes(new URL(target.pathname, document.baseURI).href)) {
|
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);
|
2019-09-15 02:22:13 +03:00
|
|
|
}
|
|
|
|
}
|
2020-01-21 17:17:28 +03:00
|
|
|
}
|
2019-09-15 02:22:13 +03:00
|
|
|
|
2020-04-10 15:06:57 +03:00
|
|
|
/** @typedef {HeadTag | JsonLdTag} SeoTag */
|
|
|
|
|
|
|
|
/** @typedef {{ name: string; attributes: string[][]; type: 'head' }} HeadTag */
|
2020-01-21 17:17:28 +03:00
|
|
|
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-20 19:03:42 +03:00
|
|
|
|
2020-04-10 15:06:57 +03:00
|
|
|
/** @typedef {{ contents: Object; type: 'json-ld' }} JsonLdTag */
|
|
|
|
function appendJsonLdTag(/** @type {JsonLdTag} */ tagDetails) {
|
|
|
|
let jsonLdScript = document.createElement('script');
|
|
|
|
jsonLdScript.type = "application/ld+json";
|
|
|
|
jsonLdScript.innerHTML = JSON.stringify(tagDetails.contents);
|
|
|
|
document.getElementsByTagName("head")[0].appendChild(jsonLdScript);
|
|
|
|
}
|
|
|
|
|
2020-01-21 17:17:28 +03:00
|
|
|
function httpGet(/** @type string */ theUrl) {
|
2020-04-10 15:06:57 +03:00
|
|
|
return new Promise(function (resolve, reject) {
|
2020-01-21 17:17:28 +03:00
|
|
|
const xmlHttp = new XMLHttpRequest();
|
2020-04-10 15:06:57 +03:00
|
|
|
xmlHttp.onreadystatechange = function () {
|
|
|
|
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
|
|
|
|
resolve(JSON.parse(xmlHttp.responseText));
|
2020-01-20 19:03:42 +03:00
|
|
|
}
|
2020-01-21 17:17:28 +03:00
|
|
|
xmlHttp.onerror = reject;
|
|
|
|
xmlHttp.open("GET", theUrl, true); // true for asynchronous
|
2020-01-20 19:03:42 +03:00
|
|
|
xmlHttp.send(null);
|
2020-01-21 17:17:28 +03:00
|
|
|
})
|
2020-01-20 19:03:42 +03:00
|
|
|
}
|
2020-05-10 06:33:32 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @returns { Promise<DevServerConfig?>}
|
|
|
|
*/
|
|
|
|
function getConfig() {
|
|
|
|
if (module.hot) {
|
|
|
|
return httpGet(`/elm-pages-dev-server-options`)
|
|
|
|
} else {
|
|
|
|
return Promise.resolve(null)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @typedef { { elmDebugger : boolean } } DevServerConfig */
|