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 fs = require("fs");
const path = require("path"); const path = require("path");
const seo = require("./seo-renderer.js");
async function run() { async function run() {
XMLHttpRequest = require("xhr2"); XMLHttpRequest = require("xhr2");
@ -65,7 +66,7 @@ async function run() {
} }
function outputString(/** @type { Object } */ fromElm) { function outputString(/** @type { Object } */ fromElm) {
fs.writeFileSync("dist/index.html", wrapHtml(fromElm.html)); fs.writeFileSync("dist/index.html", wrapHtml(fromElm));
let contentJson = {}; let contentJson = {};
contentJson["body"] = "Hello!"; contentJson["body"] = "Hello!";
contentJson["staticData"] = fromElm.contentJson; contentJson["staticData"] = fromElm.contentJson;
@ -73,7 +74,7 @@ function outputString(/** @type { Object } */ fromElm) {
} }
run(); run();
function wrapHtml(/** @type { string } */ html) { function wrapHtml(/** @type { Object } */ fromElm) {
/*html*/ /*html*/
return `<!DOCTYPE html> return `<!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -100,8 +101,9 @@ function wrapHtml(/** @type { string } */ html) {
<script defer="defer" src="main.js"></script> <script defer="defer" src="main.js"></script>
<script defer="defer" src="index.js" type="module"></script> <script defer="defer" src="index.js" type="module"></script>
<link rel="preload" href="main.js" as="script"> <link rel="preload" href="main.js" as="script">
${seo.toString(fromElm.head)}
<body> <body>
${html} ${fromElm.html}
</body> </body>
</html> </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 , AttributeValue
, currentPageFullUrl, fullImageUrl, fullPageUrl, raw , currentPageFullUrl, fullImageUrl, fullPageUrl, raw
, toJson, canonicalLink , toJson, canonicalLink
, codec
) )
{-| This module contains low-level functions for building up {-| 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 Json.Encode
import Pages.ImagePath as ImagePath exposing (ImagePath) import Pages.ImagePath as ImagePath exposing (ImagePath)
import Pages.Internal.String as String 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 : String -> String -> ( String, AttributeValue pathKey ) -> Json.Encode.Value
encodeProperty canonicalSiteUrl currentPagePath ( name, value ) = encodeProperty canonicalSiteUrl currentPagePath ( name, value ) =
case value of case value of

View File

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

View File

@ -244,6 +244,7 @@ perform cliMsgConstructor toJsPort effect =
Json.Encode.object Json.Encode.object
[ ( "html", Json.Encode.string info.html ) [ ( "html", Json.Encode.string info.html )
, ( "contentJson", Json.Encode.dict identity Json.Encode.string info.contentJson ) , ( "contentJson", Json.Encode.dict identity Json.Encode.string info.contentJson )
, ( "head", Json.Encode.list (Head.toJson "https://canonical.com/" info.route) info.head )
] ]
|> toJsPort |> toJsPort
|> Cmd.map never |> Cmd.map never
@ -520,7 +521,10 @@ nextStepToEffect config model nextStep =
pending = pending =
[] []
in in
( { model | allRawResponses = updatedAllRawResponses, pendingRequests = pending } ( { model
| allRawResponses = updatedAllRawResponses
, pendingRequests = pending
}
, doNow , doNow
|> List.map Effect.FetchHttp |> List.map Effect.FetchHttp
|> Effect.Batch |> Effect.Batch
@ -533,6 +537,15 @@ nextStepToEffect config model nextStep =
contentCache = contentCache =
ContentCache.init config.document config.content Nothing 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 = siteMetadata =
contentCache contentCache
|> Result.map |> Result.map
@ -557,10 +570,55 @@ nextStepToEffect config model nextStep =
_ -> _ ->
todo "Expected exactly one item." 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 : { title : String, body : Html userMsg }
renderedView = renderedView =
viewRequest twoThings.view pageModel pageView
|> makeItWork
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 = --viewFnResult =
-- --currentPage -- --currentPage
@ -573,62 +631,22 @@ nextStepToEffect config model nextStep =
-- |> (\request -> -- |> (\request ->
-- StaticHttpRequest.resolve ApplicationType.Browser request viewResult.staticData -- StaticHttpRequest.resolve ApplicationType.Browser request viewResult.staticData
-- ) -- )
makeItWork : --makeItWork :
StaticHttp.Request -- StaticHttp.Request
{ view : -- { view :
userModel -- userModel
-> view -- -> view
-> { title : String, body : Html userMsg } -- -> { title : String, body : Html userMsg }
, head : List (Head.Tag pathKey) -- , head : List (Head.Tag pathKey)
} -- }
-> { title : String, body : Html userMsg } -- -> { title : String, body : Html userMsg }
makeItWork request = makeItWork request =
case StaticHttpRequest.resolve ApplicationType.Browser request (staticData |> Dict.map (\k v -> Just v)) of case StaticHttpRequest.resolve ApplicationType.Browser request (staticData |> Dict.map (\k v -> Just v)) of
Err error -> Err error ->
todo (toString error) todo (toString error)
Ok functions -> Ok functions ->
let functions
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
staticData = staticData =
case toJsPayload of case toJsPayload of
@ -652,6 +670,9 @@ nextStepToEffect config model nextStep =
} }
viewRequest = viewRequest =
config.view (siteMetadata |> Result.withDefault []) currentPage config.view (siteMetadata |> Result.withDefault []) currentPage
twoThings =
viewRequest |> makeItWork
in in
case siteMetadata of case siteMetadata of
Ok [ ( page, metadata ) ] -> Ok [ ( page, metadata ) ] ->
@ -681,6 +702,7 @@ nextStepToEffect config model nextStep =
renderedView.body renderedView.body
|> viewRenderer |> viewRenderer
, errors = [] , errors = []
, head = twoThings.head
} }
|> Effect.SendSinglePage |> Effect.SendSinglePage
) )

View File

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

View File

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