Add a prefetch attribute.

This commit is contained in:
Dillon Kearns 2021-05-14 19:33:52 -07:00
parent e308b38534
commit d2bf1e1204
5 changed files with 66 additions and 90 deletions

View File

@ -8,6 +8,7 @@ import Date exposing (Date)
import OptimizedDecoder
import Pages.ImagePath exposing (ImagePath)
import Pages.PagePath as PagePath exposing (PagePath)
import Route
type alias BlogPost =
@ -26,7 +27,7 @@ blogPostsGlob =
|> Glob.toDataSource
allMetadata : DataSource.DataSource (List ( PagePath, ArticleMetadata ))
allMetadata : DataSource.DataSource (List ( Route.Route, ArticleMetadata ))
allMetadata =
--StaticFile.glob "content/blog/*.md"
blogPostsGlob
@ -36,7 +37,7 @@ allMetadata =
|> List.map
(\{ filePath, slug } ->
DataSource.map2 Tuple.pair
(DataSource.succeed <| "blog/" ++ slug)
(DataSource.succeed <| Route.Blog__Slug_ { slug = slug })
(StaticFile.request filePath (StaticFile.frontmatter frontmatterDecoder))
)
)
@ -45,15 +46,12 @@ allMetadata =
(\articles ->
articles
|> List.filterMap
(\( path, metadata ) ->
(\( route, metadata ) ->
if metadata.draft then
Nothing
else
Just
( path |> PagePath.external
, metadata
)
Just ( route, metadata )
)
)

View File

@ -1,7 +1,6 @@
module Page.Blog exposing (Data, Model, Msg, page)
import Article
import Data.Author
import DataSource
import Date
import Document exposing (Document)
@ -11,7 +10,7 @@ import Html.Styled exposing (..)
import Html.Styled.Attributes as Attr exposing (css)
import Page exposing (DynamicContext, PageWithState, StaticPayload)
import Pages.ImagePath as ImagePath
import Pages.PagePath as PagePath exposing (PagePath)
import Route exposing (Route)
import Shared
import SiteOld
import Tailwind.Breakpoints as Bp
@ -42,7 +41,7 @@ data =
type alias Data =
List ( PagePath, Article.ArticleMetadata )
List ( Route, Article.ArticleMetadata )
init :
@ -194,11 +193,25 @@ head staticPayload =
|> Seo.website
blogCard : ( PagePath, Article.ArticleMetadata ) -> Html msg
blogCard ( path, info ) =
link : Route.Route -> List (Attribute msg) -> List (Html msg) -> Html msg
link route attrs children =
a
[ Attr.href (PagePath.toString path)
, css
(Attr.href
("/"
++ (Route.routeToPath (Just route)
|> String.join "/"
)
)
:: Attr.attribute "elm-pages:prefetch" ""
:: attrs
)
children
blogCard : ( Route, Article.ArticleMetadata ) -> Html msg
blogCard ( route, info ) =
link route
[ css
[ Tw.flex
, Tw.flex_col
, Tw.rounded_lg

View File

@ -138,6 +138,7 @@ headerLink : PagePath -> String -> String -> Html msg
headerLink currentPagePath linkTo name =
a
[ Attr.href ("/" ++ linkTo)
, Attr.attribute "elm-pages:prefetch" "true"
]
[ linkInner currentPagePath linkTo name ]

View File

@ -2,7 +2,6 @@ import userInit from "/index.js";
let prefetchedPages;
let initialLocationHash;
let elmViewRendered = false;
function pagesInit(
/** @type { mainElmModule: { init: any } } */ { mainElmModule }
@ -12,14 +11,6 @@ function pagesInit(
return new Promise(function (resolve, reject) {
document.addEventListener("DOMContentLoaded", (_) => {
new MutationObserver(function () {
elmViewRendered = true;
}).observe(document.body, {
attributes: true,
childList: true,
subtree: true,
});
loadContentAndInitializeApp(mainElmModule).then(resolve, reject);
});
});
@ -58,28 +49,14 @@ function loadContentAndInitializeApp(
},
});
app.ports.toJsPort.subscribe((
/** @type { { allRoutes: string[] } } */ fromElm
) => {
window.allRoutes = fromElm.allRoutes.map(
(route) => new URL(route, document.baseURI).href
);
setupLinkPrefetching();
app.ports.toJsPort.subscribe((fromElm) => {
loadNamedAnchor();
});
return app;
});
}
function setupLinkPrefetching() {
new MutationObserver(observeFirstRender).observe(document.body, {
attributes: true,
childList: true,
subtree: true,
});
}
function loadNamedAnchor() {
if (initialLocationHash !== "") {
const namedAnchor = document.querySelector(`[name=${initialLocationHash}]`);
@ -87,62 +64,14 @@ function loadNamedAnchor() {
}
}
function observeFirstRender(
/** @type {MutationRecord[]} */ mutationList,
/** @type {MutationObserver} */ firstRenderObserver
) {
loadNamedAnchor();
for (let mutation of mutationList) {
if (mutation.type === "childList") {
setupLinkPrefetchingHelp();
}
}
firstRenderObserver.disconnect();
new MutationObserver(observeUrlChanges).observe(document.body.children[0], {
attributes: true,
childList: false,
subtree: false,
});
}
function observeUrlChanges(
/** @type {MutationRecord[]} */ mutationList,
/** @type {MutationObserver} */ _theObserver
) {
for (let mutation of mutationList) {
if (
mutation.type === "attributes" &&
mutation.attributeName === "data-url"
) {
setupLinkPrefetchingHelp();
}
}
}
function setupLinkPrefetchingHelp(
/** @type {MutationObserver} */ _mutationList,
/** @type {MutationObserver} */ _theObserver
) {
const links = document.querySelectorAll("a");
links.forEach((link) => {
// console.log(link.pathname);
link.addEventListener("mouseenter", function (event) {
if (event && event.target && event.target instanceof HTMLAnchorElement) {
prefetchIfNeeded(event.target);
} else {
// console.log("Couldn't prefetch with event", event);
}
});
});
}
function prefetchIfNeeded(/** @type {HTMLAnchorElement} */ target) {
if (target.host === window.location.host) {
if (prefetchedPages.includes(target.pathname)) {
// console.log("Already preloaded", target.href);
// console.log("Not a known route, skipping preload", target.pathname);
} else if (
!allRoutes.includes(new URL(target.pathname, document.baseURI).href)
// !allRoutes.includes(new URL(target.pathname, document.baseURI).href)
false
) {
} else {
prefetchedPages.push(target.pathname);
@ -182,3 +111,38 @@ if (typeof connect === "function") {
});
});
}
/** @param {MouseEvent} event */
const trigger_prefetch = (event) => {
const a = find_anchor(/** @type {Node} */ (event.target));
if (a && a.href && a.hasAttribute("elm-pages:prefetch")) {
console.log("PREFETCH", a.href);
prefetchIfNeeded(a);
// this.prefetch(new URL(/** @type {string} */ (a.href)));
}
};
/** @type {NodeJS.Timeout} */
let mousemove_timeout;
/** @param {MouseEvent} event */
const handle_mousemove = (event) => {
clearTimeout(mousemove_timeout);
mousemove_timeout = setTimeout(() => {
trigger_prefetch(event);
}, 20);
};
addEventListener("touchstart", trigger_prefetch);
addEventListener("mousemove", handle_mousemove);
/**
* @param {Node} node
// * @rturns {HTMLAnchorElement | SVGAElement}
* @returns {HTMLAnchorElement}
*/
function find_anchor(node) {
while (node && node.nodeName.toUpperCase() !== "A") node = node.parentNode; // SVG <a> elements have a lowercase name
// return /** @type {HTMLAnchorElement | SVGAElement} */ (node);
return /** @type {HTMLAnchorElement} */ (node);
}

View File

@ -8,7 +8,7 @@
"strict": true,
"sourceMap": false,
"resolveJsonModule": true,
"lib": ["es2015", "es2017.object", "esnext"]
"lib": ["es2015", "es2017.object", "esnext", "dom"]
},
"exclude": ["node_modules", "generated"]
}