mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2025-01-06 14:26:35 +03:00
Render head tags in beta pre-renderer.
This commit is contained in:
parent
7beaf5ddaf
commit
40c3f5a16c
@ -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>
|
||||
`;
|
||||
|
60
generator/src/seo-renderer.js
Normal file
60
generator/src/seo-renderer.js
Normal 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>`;
|
||||
}
|
65
src/Head.elm
65
src/Head.elm
@ -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
|
||||
|
@ -10,6 +10,7 @@ module Pages.ContentCache exposing
|
||||
, lookup
|
||||
, lookupMetadata
|
||||
, pagesWithErrors
|
||||
, parseContent
|
||||
, pathForUrl
|
||||
, routesForCache
|
||||
, update
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -9,4 +9,4 @@ type Effect pathKey
|
||||
| SendJsData (ToJsPayload pathKey)
|
||||
| FetchHttp { masked : RequestDetails, unmasked : RequestDetails }
|
||||
| Batch (List (Effect pathKey))
|
||||
| SendSinglePage ToJsSuccessPayloadNew
|
||||
| SendSinglePage (ToJsSuccessPayloadNew pathKey)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user