Render head tags in beta pre-renderer.

This commit is contained in:
Dillon Kearns 2020-10-11 08:43:38 -07:00
parent 7beaf5ddaf
commit 40c3f5a16c
7 changed files with 212 additions and 59 deletions

View File

@ -1,5 +1,6 @@
const fs = require("fs");
const path = require("path");
const seo = require("./seo-renderer.js");
async function run() {
XMLHttpRequest = require("xhr2");
@ -65,7 +66,7 @@ async function run() {
}
function outputString(/** @type { Object } */ fromElm) {
fs.writeFileSync("dist/index.html", wrapHtml(fromElm.html));
fs.writeFileSync("dist/index.html", wrapHtml(fromElm));
let contentJson = {};
contentJson["body"] = "Hello!";
contentJson["staticData"] = fromElm.contentJson;
@ -73,7 +74,7 @@ function outputString(/** @type { Object } */ fromElm) {
}
run();
function wrapHtml(/** @type { string } */ html) {
function wrapHtml(/** @type { Object } */ fromElm) {
/*html*/
return `<!DOCTYPE html>
<html lang="en">
@ -100,8 +101,9 @@ function wrapHtml(/** @type { string } */ html) {
<script defer="defer" src="main.js"></script>
<script defer="defer" src="index.js" type="module"></script>
<link rel="preload" href="main.js" as="script">
${seo.toString(fromElm.head)}
<body>
${html}
${fromElm.html}
</body>
</html>
`;

View File

@ -0,0 +1,60 @@
const elmPagesVersion = "TODO";
module.exports = { toString };
function toString(/** @type { SeoTag[] } */ tags) {
// appendTag({
// type: "head",
// name: "meta",
// attributes: [
// ["name", "generator"],
// ["content", `elm-pages v${elmPagesVersion}`],
// ],
// });
const generatorTag /** @type { HeadTag } */ = {
type: "head",
name: "meta",
attributes: [
["name", "generator"],
["content", `elm-pages v${elmPagesVersion}`],
],
};
// tags.concat([generatorTag]);
return tags
.map((headTag) => {
if (headTag.type === "head") {
return appendTag(headTag);
} else if (headTag.type === "json-ld") {
return appendJsonLdTag(headTag);
} else {
throw new Error(`Unknown tag type ${JSON.stringify(headTag)}`);
}
})
.join("\n");
}
/** @typedef {HeadTag | JsonLdTag} SeoTag */
/** @typedef {{ name: string; attributes: string[][]; type: 'head' }} HeadTag */
function appendTag(/** @type {HeadTag} */ tagDetails) {
// const meta = document.createElement(tagDetails.name);
const tagsString = tagDetails.attributes.map(([name, value]) => {
// meta.setAttribute(name, value);
return `${name}="${value}"`;
});
return ` <${tagDetails.name} ${tagsString.join(" ")} />`;
// document.getElementsByTagName("head")[0].appendChild(meta);
}
/** @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);
return `<script type="application/ld+json">
${JSON.stringify(tagDetails.contents)}
</script>`;
}

View File

@ -5,6 +5,7 @@ module Head exposing
, AttributeValue
, currentPageFullUrl, fullImageUrl, fullPageUrl, raw
, toJson, canonicalLink
, codec
)
{-| This module contains low-level functions for building up
@ -37,6 +38,7 @@ writing a plugin package to extend `elm-pages`.
-}
import Codec exposing (Codec)
import Json.Encode
import Pages.ImagePath as ImagePath exposing (ImagePath)
import Pages.Internal.String as String
@ -313,6 +315,69 @@ toJson canonicalSiteUrl currentPagePath tag =
]
codec : Codec (Tag pathKey)
codec =
Codec.custom
(\fTag fStructuredData v ->
case v of
Tag err ->
fTag err
StructuredData ok ->
fStructuredData ok
)
|> Codec.variant1 "Tag" Tag tagCodec
|> Codec.variant1 "StructuredData" StructuredData codecStructuredData
|> Codec.buildCustom
tagCodec : Codec (Details pathKey)
tagCodec =
--Debug.todo ""
Codec.object Details
|> Codec.field "name" .name Codec.string
|> Codec.field "attributes" .attributes (Codec.list (Codec.tuple Codec.string attributeCodec))
|> Codec.buildObject
--{ name : String
--, attributes : List ( String, AttributeValue pathKey )
--}
attributeCodec : Codec (AttributeValue pathKey)
attributeCodec =
Codec.custom
(\fRaw fFull fCurrent v ->
case v of
Raw string ->
fRaw string
FullUrl string ->
fFull string
FullUrlToCurrentPage ->
fCurrent
)
|> Codec.variant1 "Raw" Raw Codec.string
|> Codec.variant1 "FullUrl" FullUrl Codec.string
|> Codec.variant0 "FullUrlToCurrentPage" FullUrlToCurrentPage
|> Codec.buildCustom
--type AttributeValue pathKey
-- = Raw String
-- | FullUrl String
-- | FullUrlToCurrentPage
codecStructuredData : Codec Json.Encode.Value
codecStructuredData =
Codec.value
encodeProperty : String -> String -> ( String, AttributeValue pathKey ) -> Json.Encode.Value
encodeProperty canonicalSiteUrl currentPagePath ( name, value ) =
case value of

View File

@ -10,6 +10,7 @@ module Pages.ContentCache exposing
, lookup
, lookupMetadata
, pagesWithErrors
, parseContent
, pathForUrl
, routesForCache
, update

View File

@ -244,6 +244,7 @@ perform cliMsgConstructor toJsPort effect =
Json.Encode.object
[ ( "html", Json.Encode.string info.html )
, ( "contentJson", Json.Encode.dict identity Json.Encode.string info.contentJson )
, ( "head", Json.Encode.list (Head.toJson "https://canonical.com/" info.route) info.head )
]
|> toJsPort
|> Cmd.map never
@ -520,7 +521,10 @@ nextStepToEffect config model nextStep =
pending =
[]
in
( { model | allRawResponses = updatedAllRawResponses, pendingRequests = pending }
( { model
| allRawResponses = updatedAllRawResponses
, pendingRequests = pending
}
, doNow
|> List.map Effect.FetchHttp
|> Effect.Batch
@ -533,6 +537,15 @@ nextStepToEffect config model nextStep =
contentCache =
ContentCache.init config.document config.content Nothing
renderer value =
ContentCache.parseContent "md" value config.document
updatedCache =
ContentCache.update contentCache
renderer
urls
{ body = "", staticData = model.allRawResponses }
siteMetadata =
contentCache
|> Result.map
@ -557,10 +570,55 @@ nextStepToEffect config model nextStep =
_ ->
todo "Expected exactly one item."
pageModel : userModel
pageModel =
config.init
(Just
{ path = currentPage.path
, query = Nothing
, fragment = Nothing
}
)
|> Tuple.first
renderedView : { title : String, body : Html userMsg }
renderedView =
viewRequest
|> makeItWork
twoThings.view pageModel pageView
pageView : view
pageView =
case ContentCache.lookup config.pathKey updatedCache urls of
Just ( pagePath, entry ) ->
case entry of
ContentCache.Parsed frontmatter viewResult ->
expectOk viewResult.body
_ ->
todo <| "Unhandled content cache state - Not Parsed" ++ toString entry
_ ->
todo "Unhandled content cache state - Nothing"
fakeUrl =
{ protocol = Url.Https
, host = config.canonicalSiteUrl
, port_ = Nothing
, path = currentPage.path |> PagePath.toString
, query = Nothing
, fragment = Nothing
}
urls =
{ currentUrl = fakeUrl
, baseUrl =
{ protocol = Url.Https
, host = config.canonicalSiteUrl
, port_ = Nothing
, path = ""
, query = Nothing
, fragment = Nothing
}
}
--viewFnResult =
-- --currentPage
@ -573,62 +631,22 @@ nextStepToEffect config model nextStep =
-- |> (\request ->
-- StaticHttpRequest.resolve ApplicationType.Browser request viewResult.staticData
-- )
makeItWork :
StaticHttp.Request
{ view :
userModel
-> view
-> { title : String, body : Html userMsg }
, head : List (Head.Tag pathKey)
}
-> { title : String, body : Html userMsg }
--makeItWork :
-- StaticHttp.Request
-- { view :
-- userModel
-- -> view
-- -> { title : String, body : Html userMsg }
-- , head : List (Head.Tag pathKey)
-- }
-- -> { title : String, body : Html userMsg }
makeItWork request =
case StaticHttpRequest.resolve ApplicationType.Browser request (staticData |> Dict.map (\k v -> Just v)) of
Err error ->
todo (toString error)
Ok functions ->
let
pageModel : userModel
pageModel =
config.init
(Just
{ path = currentPage.path
, query = Nothing
, fragment = Nothing
}
)
|> Tuple.first
fakeUrl =
{ protocol = Url.Https
, host = ""
, port_ = Nothing
, path = "" -- TODO
, query = Nothing
, fragment = Nothing
}
urls =
{ currentUrl = fakeUrl
, baseUrl = fakeUrl
}
pageView : view
pageView =
case ContentCache.lookup config.pathKey contentCache urls of
Just ( pagePath, entry ) ->
case entry of
ContentCache.Parsed frontmatter viewResult ->
expectOk viewResult.body
_ ->
todo "Unhandled content cache state"
_ ->
todo "Unhandled content cache state"
in
functions.view pageModel pageView
functions
staticData =
case toJsPayload of
@ -652,6 +670,9 @@ nextStepToEffect config model nextStep =
}
viewRequest =
config.view (siteMetadata |> Result.withDefault []) currentPage
twoThings =
viewRequest |> makeItWork
in
case siteMetadata of
Ok [ ( page, metadata ) ] ->
@ -681,6 +702,7 @@ nextStepToEffect config model nextStep =
renderedView.body
|> viewRenderer
, errors = []
, head = twoThings.head
}
|> Effect.SendSinglePage
)

View File

@ -9,4 +9,4 @@ type Effect pathKey
| SendJsData (ToJsPayload pathKey)
| FetchHttp { masked : RequestDetails, unmasked : RequestDetails }
| Batch (List (Effect pathKey))
| SendSinglePage ToJsSuccessPayloadNew
| SendSinglePage (ToJsSuccessPayloadNew pathKey)

View File

@ -3,6 +3,7 @@ module Pages.Internal.Platform.ToJsPayload exposing (..)
import BuildError
import Codec exposing (Codec)
import Dict exposing (Dict)
import Head
import Json.Decode as Decode
import Json.Encode
import Pages.ImagePath as ImagePath
@ -25,11 +26,12 @@ type alias ToJsSuccessPayload pathKey =
}
type alias ToJsSuccessPayloadNew =
type alias ToJsSuccessPayloadNew pathKey =
{ route : String
, html : String
, contentJson : Dict String String
, errors : List String
, head : List (Head.Tag pathKey)
}
@ -137,7 +139,7 @@ successCodec =
|> Codec.buildObject
successCodecNew : Codec ToJsSuccessPayloadNew
successCodecNew : Codec (ToJsSuccessPayloadNew pathKey)
successCodecNew =
Codec.object ToJsSuccessPayloadNew
|> Codec.field "route"
@ -150,4 +152,5 @@ successCodecNew =
.contentJson
(Codec.dict Codec.string)
|> Codec.field "errors" .errors (Codec.list Codec.string)
|> Codec.field "head" .head (Codec.list Head.codec)
|> Codec.buildObject