Merge pull request #241 from dillonkearns/lazy-rendering

Handle Html.Lazy in HTML pre-rendering
This commit is contained in:
Dillon Kearns 2021-10-07 13:18:32 -07:00 committed by GitHub
commit 2d4350362a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 226 additions and 440 deletions

View File

@ -42,6 +42,7 @@
"elm/json": "1.1.3 <= v < 2.0.0", "elm/json": "1.1.3 <= v < 2.0.0",
"elm/regex": "1.0.0 <= v < 2.0.0", "elm/regex": "1.0.0 <= v < 2.0.0",
"elm/url": "1.0.0 <= v < 2.0.0", "elm/url": "1.0.0 <= v < 2.0.0",
"elm/virtual-dom": "1.0.2 <= v < 2.0.0",
"elm-community/dict-extra": "2.4.0 <= v < 3.0.0", "elm-community/dict-extra": "2.4.0 <= v < 3.0.0",
"elm-community/list-extra": "8.3.0 <= v < 9.0.0", "elm-community/list-extra": "8.3.0 <= v < 9.0.0",
"jfmengels/elm-review": "2.5.0 <= v < 3.0.0", "jfmengels/elm-review": "2.5.0 <= v < 3.0.0",

View File

@ -25,6 +25,7 @@
"elm/regex": "1.0.0", "elm/regex": "1.0.0",
"elm/time": "1.0.0", "elm/time": "1.0.0",
"elm/url": "1.0.0", "elm/url": "1.0.0",
"elm/virtual-dom": "1.0.2",
"elm-community/dict-extra": "2.4.0", "elm-community/dict-extra": "2.4.0",
"elm-community/list-extra": "8.3.0", "elm-community/list-extra": "8.3.0",
"justinmimbs/date": "3.2.1", "justinmimbs/date": "3.2.1",
@ -47,7 +48,6 @@
"elm/file": "1.0.5", "elm/file": "1.0.5",
"elm/parser": "1.1.0", "elm/parser": "1.1.0",
"elm/random": "1.0.0", "elm/random": "1.0.0",
"elm/virtual-dom": "1.0.2",
"fredcy/elm-parseint": "2.0.1", "fredcy/elm-parseint": "2.0.1",
"justinmimbs/time-extra": "1.1.0", "justinmimbs/time-extra": "1.1.0",
"lazamar/dict-parser": "1.0.2", "lazamar/dict-parser": "1.0.2",

View File

@ -22,6 +22,7 @@
"elm/regex": "1.0.0", "elm/regex": "1.0.0",
"elm/time": "1.0.0", "elm/time": "1.0.0",
"elm/url": "1.0.0", "elm/url": "1.0.0",
"elm/virtual-dom": "1.0.2",
"elm-community/dict-extra": "2.4.0", "elm-community/dict-extra": "2.4.0",
"elm-community/list-extra": "8.3.0", "elm-community/list-extra": "8.3.0",
"matheus23/elm-default-tailwind-modules": "2.0.1", "matheus23/elm-default-tailwind-modules": "2.0.1",
@ -41,7 +42,6 @@
"elm/file": "1.0.5", "elm/file": "1.0.5",
"elm/parser": "1.1.0", "elm/parser": "1.1.0",
"elm/random": "1.0.0", "elm/random": "1.0.0",
"elm/virtual-dom": "1.0.2",
"fredcy/elm-parseint": "2.0.1", "fredcy/elm-parseint": "2.0.1",
"mgold/elm-nonempty-list": "4.2.0", "mgold/elm-nonempty-list": "4.2.0",
"rtfeldman/elm-hex": "1.0.0" "rtfeldman/elm-hex": "1.0.0"

View File

@ -29,6 +29,7 @@ let prefetchedPages=[window.location.pathname],initialLocationHash=document.loca
font-size:14px; font-size:14px;
color:rgb(255, 0, 0); color:rgb(255, 0, 0);
}</style><div><p>Hello! 2 &gt; 1</p></div></div>&lt;script&gt;&lt;/script&gt; is unsafe in JSON unless it is escaped properly. }</style><div><p>Hello! 2 &gt; 1</p></div></div>&lt;script&gt;&lt;/script&gt; is unsafe in JSON unless it is escaped properly.
&lt;script&gt;&lt;/script&gt; is unsafe in JSON unless it is escaped properly.
</div> </div>
</body> </body>
</html> </html>

View File

@ -22,6 +22,7 @@
"elm/regex": "1.0.0", "elm/regex": "1.0.0",
"elm/time": "1.0.0", "elm/time": "1.0.0",
"elm/url": "1.0.0", "elm/url": "1.0.0",
"elm/virtual-dom": "1.0.2",
"elm-community/dict-extra": "2.4.0", "elm-community/dict-extra": "2.4.0",
"elm-community/list-extra": "8.3.0", "elm-community/list-extra": "8.3.0",
"matheus23/elm-default-tailwind-modules": "2.0.1", "matheus23/elm-default-tailwind-modules": "2.0.1",
@ -41,7 +42,6 @@
"elm/file": "1.0.5", "elm/file": "1.0.5",
"elm/parser": "1.1.0", "elm/parser": "1.1.0",
"elm/random": "1.0.0", "elm/random": "1.0.0",
"elm/virtual-dom": "1.0.2",
"fredcy/elm-parseint": "2.0.1", "fredcy/elm-parseint": "2.0.1",
"mgold/elm-nonempty-list": "4.2.0", "mgold/elm-nonempty-list": "4.2.0",
"rtfeldman/elm-hex": "1.0.0" "rtfeldman/elm-hex": "1.0.0"

View File

@ -16,12 +16,12 @@
} }
}, },
"../..": { "../..": {
"version": "2.1.7", "version": "2.1.9",
"dev": true, "dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"chokidar": "3.5.2", "chokidar": "3.5.2",
"commander": "8.0.0", "commander": "^8.1.0",
"connect": "^3.7.0", "connect": "^3.7.0",
"cross-spawn": "7.0.3", "cross-spawn": "7.0.3",
"elm-doc-preview": "^5.0.5", "elm-doc-preview": "^5.0.5",
@ -29,12 +29,13 @@
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"globby": "11.0.4", "globby": "11.0.4",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"jsesc": "^3.0.2",
"kleur": "^4.1.4", "kleur": "^4.1.4",
"micromatch": "^4.0.4", "micromatch": "^4.0.4",
"object-hash": "^2.2.0", "object-hash": "^2.2.0",
"serve-static": "^1.14.1", "serve-static": "^1.14.1",
"terser": "5.7.1", "terser": "^5.7.2",
"undici": "4.2.1", "undici": "^4.4.7",
"which": "^2.0.2" "which": "^2.0.2"
}, },
"bin": { "bin": {
@ -43,16 +44,17 @@
"devDependencies": { "devDependencies": {
"@types/cross-spawn": "^6.0.2", "@types/cross-spawn": "^6.0.2",
"@types/fs-extra": "9.0.12", "@types/fs-extra": "9.0.12",
"@types/micromatch": "^4.0.1", "@types/micromatch": "^4.0.2",
"@types/node": "12.20.12", "@types/node": "12.20.12",
"@types/serve-static": "1.13.10", "@types/serve-static": "1.13.10",
"cypress": "^8.0.0", "cypress": "^8.3.0",
"elm-optimize-level-2": "^0.1.5", "elm-optimize-level-2": "^0.1.5",
"elm-review": "^2.5.3", "elm-review": "^2.5.3",
"elm-test": "^0.19.1-revision7", "elm-test": "^0.19.1-revision7",
"elm-tooling": "^1.3.0", "elm-tooling": "^1.3.0",
"elm-verify-examples": "^5.0.0", "elm-verify-examples": "^5.0.0",
"mocha": "^8.4.0", "elmi-to-json": "^1.2.0",
"mocha": "^9.1.0",
"typescript": "4.3.5" "typescript": "4.3.5"
} }
}, },
@ -1487,14 +1489,14 @@
"requires": { "requires": {
"@types/cross-spawn": "^6.0.2", "@types/cross-spawn": "^6.0.2",
"@types/fs-extra": "9.0.12", "@types/fs-extra": "9.0.12",
"@types/micromatch": "^4.0.1", "@types/micromatch": "^4.0.2",
"@types/node": "12.20.12", "@types/node": "12.20.12",
"@types/serve-static": "1.13.10", "@types/serve-static": "1.13.10",
"chokidar": "3.5.2", "chokidar": "3.5.2",
"commander": "8.0.0", "commander": "^8.1.0",
"connect": "^3.7.0", "connect": "^3.7.0",
"cross-spawn": "7.0.3", "cross-spawn": "7.0.3",
"cypress": "^8.0.0", "cypress": "^8.3.0",
"elm-doc-preview": "^5.0.5", "elm-doc-preview": "^5.0.5",
"elm-hot": "^1.1.6", "elm-hot": "^1.1.6",
"elm-optimize-level-2": "^0.1.5", "elm-optimize-level-2": "^0.1.5",
@ -1502,17 +1504,19 @@
"elm-test": "^0.19.1-revision7", "elm-test": "^0.19.1-revision7",
"elm-tooling": "^1.3.0", "elm-tooling": "^1.3.0",
"elm-verify-examples": "^5.0.0", "elm-verify-examples": "^5.0.0",
"elmi-to-json": "^1.2.0",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"globby": "11.0.4", "globby": "11.0.4",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"jsesc": "^3.0.2",
"kleur": "^4.1.4", "kleur": "^4.1.4",
"micromatch": "^4.0.4", "micromatch": "^4.0.4",
"mocha": "^8.4.0", "mocha": "^9.1.0",
"object-hash": "^2.2.0", "object-hash": "^2.2.0",
"serve-static": "^1.14.1", "serve-static": "^1.14.1",
"terser": "5.7.1", "terser": "^5.7.2",
"typescript": "4.3.5", "typescript": "4.3.5",
"undici": "4.2.1", "undici": "^4.4.7",
"which": "^2.0.2" "which": "^2.0.2"
}, },
"dependencies": { "dependencies": {

View File

@ -4,7 +4,7 @@
"description": "Example site built with elm-pages.", "description": "Example site built with elm-pages.",
"scripts": { "scripts": {
"start": "elm-pages dev", "start": "elm-pages dev",
"test": "elm-pages build --debug && git diff --exit-code ./dist", "test": "elm-pages build --debug && git diff --exit-code ./dist && elm-pages build && git diff --exit-code ./dist",
"serve": "npm run build && http-server ./dist -a localhost -p 3000 -c-1", "serve": "npm run build && http-server ./dist -a localhost -p 3000 -c-1",
"build": "elm-pages build" "build": "elm-pages build"
}, },

View File

@ -8,6 +8,7 @@ import Head
import Head.Seo as Seo import Head.Seo as Seo
import Html.Styled as Html exposing (..) import Html.Styled as Html exposing (..)
import Html.Styled.Attributes as Attr import Html.Styled.Attributes as Attr
import Html.Styled.Lazy as HtmlLazy
import Page exposing (Page, PageWithState, StaticPayload) import Page exposing (Page, PageWithState, StaticPayload)
import Pages.PageUrl exposing (PageUrl) import Pages.PageUrl exposing (PageUrl)
import Pages.Url import Pages.Url
@ -91,6 +92,9 @@ view maybeUrl sharedModel static =
] ]
] ]
] ]
, text static.data
-- lazy and non-lazy versions render the same output
, Html.text static.data
, HtmlLazy.lazy (.data >> text) static
] ]
} }

View File

@ -23,6 +23,7 @@
"elm/regex": "1.0.0", "elm/regex": "1.0.0",
"elm/time": "1.0.0", "elm/time": "1.0.0",
"elm/url": "1.0.0", "elm/url": "1.0.0",
"elm/virtual-dom": "1.0.2",
"elm-community/dict-extra": "2.4.0", "elm-community/dict-extra": "2.4.0",
"elm-community/list-extra": "8.3.0", "elm-community/list-extra": "8.3.0",
"matheus23/elm-default-tailwind-modules": "2.0.1", "matheus23/elm-default-tailwind-modules": "2.0.1",
@ -43,7 +44,6 @@
"elm/file": "1.0.5", "elm/file": "1.0.5",
"elm/parser": "1.1.0", "elm/parser": "1.1.0",
"elm/random": "1.0.0", "elm/random": "1.0.0",
"elm/virtual-dom": "1.0.2",
"fredcy/elm-parseint": "2.0.1", "fredcy/elm-parseint": "2.0.1",
"mgold/elm-nonempty-list": "4.2.0", "mgold/elm-nonempty-list": "4.2.0",
"rtfeldman/elm-hex": "1.0.0" "rtfeldman/elm-hex": "1.0.0"

View File

@ -22,6 +22,7 @@
"elm/regex": "1.0.0", "elm/regex": "1.0.0",
"elm/time": "1.0.0", "elm/time": "1.0.0",
"elm/url": "1.0.0", "elm/url": "1.0.0",
"elm/virtual-dom": "1.0.2",
"elm-community/dict-extra": "2.4.0", "elm-community/dict-extra": "2.4.0",
"elm-community/list-extra": "8.3.0", "elm-community/list-extra": "8.3.0",
"matheus23/elm-default-tailwind-modules": "2.0.1", "matheus23/elm-default-tailwind-modules": "2.0.1",
@ -41,7 +42,6 @@
"elm/file": "1.0.5", "elm/file": "1.0.5",
"elm/parser": "1.1.0", "elm/parser": "1.1.0",
"elm/random": "1.0.0", "elm/random": "1.0.0",
"elm/virtual-dom": "1.0.2",
"fredcy/elm-parseint": "2.0.1", "fredcy/elm-parseint": "2.0.1",
"mgold/elm-nonempty-list": "4.2.0", "mgold/elm-nonempty-list": "4.2.0",
"rtfeldman/elm-hex": "1.0.0" "rtfeldman/elm-hex": "1.0.0"

View File

@ -344,12 +344,61 @@ async function compileCliApp(options) {
); );
const elmFileContent = await fsPromises.readFile(ELM_FILE_PATH, "utf-8"); const elmFileContent = await fsPromises.readFile(ELM_FILE_PATH, "utf-8");
// Source: https://github.com/elm-explorations/test/blob/d5eb84809de0f8bbf50303efd26889092c800609/src/Elm/Kernel/HtmlAsJson.js
const forceThunksSource = ` _HtmlAsJson_toJson(x)
}
var virtualDomKernelConstants =
{
nodeTypeTagger: 4,
nodeTypeThunk: 5,
kids: "e",
refs: "l",
thunk: "m",
node: "k",
value: "a"
}
function forceThunks(vNode) {
if (typeof vNode !== "undefined" && vNode.$ === "#2") {
// This is a tuple (the kids : List (String, Html) field of a Keyed node); recurse into the right side of the tuple
vNode.b = forceThunks(vNode.b);
}
if (typeof vNode !== 'undefined' && vNode.$ === virtualDomKernelConstants.nodeTypeThunk && !vNode[virtualDomKernelConstants.node]) {
// This is a lazy node; evaluate it
var args = vNode[virtualDomKernelConstants.thunk];
vNode[virtualDomKernelConstants.node] = vNode[virtualDomKernelConstants.thunk].apply(args);
// And then recurse into the evaluated node
vNode[virtualDomKernelConstants.node] = forceThunks(vNode[virtualDomKernelConstants.node]);
}
if (typeof vNode !== 'undefined' && vNode.$ === virtualDomKernelConstants.nodeTypeTagger) {
// This is an Html.map; recurse into the node it is wrapping
vNode[virtualDomKernelConstants.node] = forceThunks(vNode[virtualDomKernelConstants.node]);
}
if (typeof vNode !== 'undefined' && typeof vNode[virtualDomKernelConstants.kids] !== 'undefined') {
// This is something with children (either a node with kids : List Html, or keyed with kids : List (String, Html));
// recurse into the children
vNode[virtualDomKernelConstants.kids] = vNode[virtualDomKernelConstants.kids].map(forceThunks);
}
return vNode;
}
function _HtmlAsJson_toJson(html) {
`;
await fsPromises.writeFile( await fsPromises.writeFile(
ELM_FILE_PATH, ELM_FILE_PATH,
elmFileContent elmFileContent
.replace( .replace(
/return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_JSON_STRINGIFY.\)/g, /return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_JSON_STRINGIFY.\)/g,
"return " + (options.debug ? "_Json_wrap(x)" : "x") "return " +
(options.debug
? `${forceThunksSource}
return _Json_wrap(forceThunks(html));
`
: `${forceThunksSource}
return forceThunks(html);
`)
) )
.replace( .replace(
"return ports ? { ports: ports } : {};", "return ports ? { ports: ports } : {};",

View File

@ -87,9 +87,6 @@ config =
, "src/Pages/Http.elm" -- reports incorrect unused custom type constructor , "src/Pages/Http.elm" -- reports incorrect unused custom type constructor
, "src/DataSource/ServerRequest.elm" -- temporarily removed from exposed modules for alpha serverless , "src/DataSource/ServerRequest.elm" -- temporarily removed from exposed modules for alpha serverless
] ]
|> Rule.ignoreErrorsForDirectories
[ "src/ElmHtml"
]
) )
) )
) )
@ -98,6 +95,7 @@ config =
rule rule
|> Rule.ignoreErrorsForDirectories |> Rule.ignoreErrorsForDirectories
[ "src/ElmHtml" [ "src/ElmHtml"
, "src/Test"
] ]
) )

View File

@ -1,151 +0,0 @@
module ElmHtml.ToElmString exposing
( nodeRecordToString, toElmString, toElmStringWithOptions
, FormatOptions, defaultFormatOptions
)
{-| Convert ElmHtml to string of Elm code.
@docs nodeRecordToString, toElmString, toElmStringWithOptions
@docs FormatOptions, defaultFormatOptions
-}
import Dict exposing (Dict)
import ElmHtml.InternalTypes exposing (..)
import String
{-| Formatting options to be used for converting to string
-}
type alias FormatOptions =
{ indent : Int
, newLines : Bool
}
{-| default formatting options
-}
defaultFormatOptions : FormatOptions
defaultFormatOptions =
{ indent = 0
, newLines = False
}
nodeToLines : FormatOptions -> ElmHtml msg -> List String
nodeToLines options nodeType =
case nodeType of
TextTag { text } ->
[ "Html.text \"" ++ text ++ "\"" ]
NodeEntry record ->
nodeRecordToString options record
CustomNode record ->
[]
MarkdownNode record ->
[ record.model.markdown ]
NoOp ->
[]
{-| Convert a given html node to a string based on the type
-}
toElmString : ElmHtml msg -> String
toElmString =
toElmStringWithOptions defaultFormatOptions
{-| same as toElmString, but with options
-}
toElmStringWithOptions : FormatOptions -> ElmHtml msg -> String
toElmStringWithOptions options =
nodeToLines options
>> String.join
(if options.newLines then
"\n"
else
""
)
{-| Convert a node record to a string. This basically takes the tag name, then
pulls all the facts into tag declaration, then goes through the children and
nests them under this one
-}
nodeRecordToString : FormatOptions -> NodeRecord msg -> List String
nodeRecordToString options { tag, children, facts } =
let
openTag : List (Maybe String) -> String
openTag extras =
let
trimmedExtras =
List.filterMap (\x -> x) extras
|> List.map String.trim
|> List.filter ((/=) "")
filling =
case trimmedExtras of
[] ->
""
more ->
" " ++ String.join " " more
in
"Html." ++ tag ++ " [" ++ filling
childrenStrings =
List.map (nodeToLines options) children
|> List.concat
|> List.map ((++) (String.repeat options.indent " "))
styles =
case Dict.toList facts.styles of
[] ->
Nothing
stylesList ->
stylesList
|> List.map (\( key, value ) -> "(\"" ++ key ++ "\",\"" ++ value ++ "\")")
|> String.join ", "
|> (\styleString -> "Html.Attributes.style [" ++ styleString ++ "]")
|> Just
classes =
Dict.get "className" facts.stringAttributes
|> Maybe.map (\name -> "Html.Attributes.class [\"" ++ name ++ "\"]")
stringAttributes =
Dict.filter (\k v -> k /= "className") facts.stringAttributes
|> Dict.toList
|> List.map (\( k, v ) -> "Html.Attributes." ++ k ++ " \"" ++ v ++ "\"")
|> String.join ", "
|> Just
boolAttributes =
Dict.toList facts.boolAttributes
|> List.map
(\( k, v ) ->
"Html.Attributes.property \""
++ k
++ "\" <| Json.Encode.bool "
++ (if v then
"True"
else
"False"
)
)
|> String.join " "
|> Just
in
[ openTag [ classes, styles, stringAttributes, boolAttributes ] ]
++ [ " ] "
, "[ "
, String.join "" childrenStrings
, "]"
]

View File

@ -1,82 +0,0 @@
module ElmHtml.ToHtml exposing (toHtml, factsToAttributes)
{-| This module is particularly useful for putting parsed Html into Elm.Html at runtime.
Estentially allowing the user to use tools like html-to-elm on their code.
@docs toHtml, factsToAttributes
-}
import Dict exposing (Dict)
import ElmHtml.InternalTypes exposing (..)
import Html
import Html.Attributes
import Html.Events
import Json.Decode
import Json.Encode
import String
{-| Turns ElmHtml into normal Elm Html
-}
toHtml : ElmHtml msg -> Html.Html msg
toHtml elmHtml =
case elmHtml of
TextTag text ->
Html.text text.text
NodeEntry { tag, children, facts } ->
Html.node tag [] (List.map toHtml children)
CustomNode record ->
--let
-- _ =
-- Debug.log "Custom node is not supported" ""
--in
Html.text ""
MarkdownNode record ->
--let
-- _ =
-- Debug.log "Markdown node is not supported" ""
--in
Html.text ""
NoOp ->
Html.text ""
stylesToAttribute : Dict String String -> List (Html.Attribute msg)
stylesToAttribute =
Dict.toList
>> List.map (\( k, v ) -> Html.Attributes.style k v)
eventsToAttributes : Dict String (Json.Decode.Decoder msg) -> List (Html.Attribute msg)
eventsToAttributes =
Dict.toList
>> List.map (\( x, y ) -> Html.Events.on x y)
stringAttributesToAttributes : Dict String String -> List (Html.Attribute msg)
stringAttributesToAttributes =
Dict.toList
>> List.map (\( x, y ) -> Html.Attributes.attribute x y)
boolAttributesToAttributes : Dict String Bool -> List (Html.Attribute msg)
boolAttributesToAttributes =
Dict.toList
>> List.map (\( x, y ) -> Html.Attributes.property x (Json.Encode.bool y))
{-| Turns a fact record into a list of attributes
-}
factsToAttributes : Facts msg -> List (Html.Attribute msg)
factsToAttributes facts =
List.concat
[ stylesToAttribute facts.styles
, eventsToAttributes facts.events
, stringAttributesToAttributes facts.stringAttributes
, boolAttributesToAttributes facts.boolAttributes
]

View File

@ -1,17 +1,18 @@
module HtmlPrinter exposing (htmlToString) module HtmlPrinter exposing (htmlToString)
import ElmHtml.InternalTypes exposing (decodeElmHtml)
import ElmHtml.ToString exposing (defaultFormatOptions, nodeToStringWithOptions)
import Html exposing (Html) import Html exposing (Html)
import Json.Decode as Decode import Json.Decode as Decode
import Json.Encode import Json.Encode
import Test.Html.Internal.ElmHtml.InternalTypes exposing (decodeElmHtml)
import Test.Html.Internal.ElmHtml.ToString exposing (defaultFormatOptions, nodeToStringWithOptions)
import VirtualDom
htmlToString : Html msg -> String htmlToString : Html msg -> String
htmlToString viewHtml = htmlToString viewHtml =
case case
Decode.decodeValue Decode.decodeValue
(decodeElmHtml (\_ _ -> Decode.succeed ())) (decodeElmHtml (\_ _ -> VirtualDom.Normal (Decode.succeed ())))
(asJsonView viewHtml) (asJsonView viewHtml)
of of
Ok str -> Ok str ->

View File

@ -91,6 +91,7 @@ cliApplication config =
site = site =
getSiteConfig config getSiteConfig config
getSiteConfig : ProgramConfig userMsg userModel (Maybe route) siteData pageData sharedData -> SiteConfig siteData
getSiteConfig fullConfig = getSiteConfig fullConfig =
case fullConfig.site of case fullConfig.site of
Just mySite -> Just mySite ->

View File

@ -1,12 +1,23 @@
module ElmHtml.Constants exposing (styleKey, eventKey, attributeKey, attributeNamespaceKey, knownKeys) module Test.Html.Internal.ElmHtml.Constants exposing
( propKey, styleKey, eventKey, attributeKey, attributeNamespaceKey
, knownKeys
)
{-| Constants for representing internal keys for Elm's vdom implementation {-| Constants for representing internal keys for Elm's vdom implementation
@docs styleKey, eventKey, attributeKey, attributeNamespaceKey, knownKeys @docs propKey, styleKey, eventKey, attributeKey, attributeNamespaceKey
@docs knownKeys
-} -}
{-| Internal key for attribute properties
-}
propKey : String
propKey =
"a2"
{-| Internal key for style {-| Internal key for style
-} -}
styleKey : String styleKey : String
@ -14,26 +25,21 @@ styleKey =
"a1" "a1"
{-| Internal key for 'on' events {-| Internal key for style
-} -}
eventKey : String eventKey : String
eventKey = eventKey =
"a0" "a0"
propertyKey : String {-| Internal key for style
propertyKey =
"a2"
{-| Internal key for attributes
-} -}
attributeKey : String attributeKey : String
attributeKey = attributeKey =
"a3" "a3"
{-| Internal key for namespaced attributes {-| Internal key for style
-} -}
attributeNamespaceKey : String attributeNamespaceKey : String
attributeNamespaceKey = attributeNamespaceKey =

View File

@ -1,4 +1,4 @@
module ElmHtml.Helpers exposing (filterKnownKeys) module Test.Html.Internal.ElmHtml.Helpers exposing (filterKnownKeys)
{-| Internal helpers for ElmHtml {-| Internal helpers for ElmHtml
@ -7,7 +7,7 @@ module ElmHtml.Helpers exposing (filterKnownKeys)
-} -}
import Dict exposing (Dict) import Dict exposing (Dict)
import ElmHtml.Constants exposing (..) import Test.Html.Internal.ElmHtml.Constants exposing (knownKeys)
{-| Filter out keys that we don't know {-| Filter out keys that we don't know

View File

@ -1,4 +1,4 @@
module ElmHtml.InternalTypes exposing module Test.Html.Internal.ElmHtml.InternalTypes exposing
( ElmHtml(..), TextTagRecord, NodeRecord, CustomNodeRecord, MarkdownNodeRecord ( ElmHtml(..), TextTagRecord, NodeRecord, CustomNodeRecord, MarkdownNodeRecord
, Facts, Tagger, EventHandler, ElementKind(..) , Facts, Tagger, EventHandler, ElementKind(..)
, Attribute(..), AttributeRecord, NamespacedAttributeRecord, PropertyRecord, EventRecord , Attribute(..), AttributeRecord, NamespacedAttributeRecord, PropertyRecord, EventRecord
@ -18,12 +18,14 @@ module ElmHtml.InternalTypes exposing
-} -}
import Dict exposing (Dict) import Dict exposing (Dict)
import ElmHtml.Constants exposing (..)
import ElmHtml.Helpers exposing (..)
import ElmHtml.Markdown exposing (..)
import Html.Events import Html.Events
import Json.Decode exposing (field) import Json.Decode exposing (field)
import Json.Encode import Json.Encode
import Test.Html.Internal.ElmHtml.Constants as Constants exposing (..)
import Test.Html.Internal.ElmHtml.Helpers exposing (..)
import Test.Html.Internal.ElmHtml.Markdown exposing (..)
import Test.Internal.KernelConstants exposing (kernelConstants)
import VirtualDom
{-| Type tree for representing Elm's Html {-| Type tree for representing Elm's Html
@ -105,7 +107,7 @@ type alias EventHandler =
-} -}
type alias Facts msg = type alias Facts msg =
{ styles : Dict String String { styles : Dict String String
, events : Dict String (Json.Decode.Decoder msg) , events : Dict String (VirtualDom.Handler msg)
, attributeNamespace : Maybe Json.Decode.Value , attributeNamespace : Maybe Json.Decode.Value
, stringAttributes : Dict String String , stringAttributes : Dict String String
, boolAttributes : Dict String Bool , boolAttributes : Dict String Bool
@ -125,7 +127,7 @@ type ElementKind
type HtmlContext msg type HtmlContext msg
= HtmlContext (List Tagger) (List Tagger -> EventHandler -> Json.Decode.Decoder msg) = HtmlContext (List Tagger) (List Tagger -> EventHandler -> VirtualDom.Handler msg)
{-| Type for representing Elm's Attributes {-| Type for representing Elm's Attributes
@ -145,7 +147,7 @@ type Attribute
= Attribute AttributeRecord = Attribute AttributeRecord
| NamespacedAttribute NamespacedAttributeRecord | NamespacedAttribute NamespacedAttributeRecord
| Property PropertyRecord | Property PropertyRecord
| Styles (List ( String, String )) | Style { key : String, value : String }
| Event EventRecord | Event EventRecord
@ -179,70 +181,55 @@ type alias PropertyRecord =
type alias EventRecord = type alias EventRecord =
{ key : String { key : String
, decoder : Json.Decode.Value , decoder : Json.Decode.Value
, options : { stopPropagation : Bool, preventDefault : Bool } , options : EventOptions
} }
factsKey : String type alias EventOptions =
factsKey = { stopPropagation : Bool
"d" , preventDefault : Bool
}
descendantsCountKey : String
descendantsCountKey =
"b"
tagKey : String
tagKey =
"c"
childrenKey : String
childrenKey =
"e"
{-| decode a json object into ElmHtml, you have to pass a function that decodes {-| decode a json object into ElmHtml, you have to pass a function that decodes
events from Html Nodes. If you don't want to decode event msgs, you can ignore it: events from Html Nodes. If you don't want to decode event msgs, you can ignore it:
decodeElmHtml (\_ \_ -> ()) jsonHtml decodeElmHtml (\_ _ -> VirtualDom.Normal (Json.Decode.succeed ())) jsonHtml
if you do want to decode them, you will probably need to write some native code if you do want to decode them, you will probably need to write some native code
like elm-html-test does to extract the function inside those. like elm-html-test does to extract the function inside those.
-} -}
decodeElmHtml : (List Tagger -> EventHandler -> Json.Decode.Decoder msg) -> Json.Decode.Decoder (ElmHtml msg) decodeElmHtml : (List Tagger -> EventHandler -> VirtualDom.Handler msg) -> Json.Decode.Decoder (ElmHtml msg)
decodeElmHtml eventDecoder = decodeElmHtml eventDecoder =
contextDecodeElmHtml (HtmlContext [] eventDecoder) contextDecodeElmHtml (HtmlContext [] eventDecoder)
contextDecodeElmHtml : HtmlContext msg -> Json.Decode.Decoder (ElmHtml msg) contextDecodeElmHtml : HtmlContext msg -> Json.Decode.Decoder (ElmHtml msg)
contextDecodeElmHtml context = contextDecodeElmHtml context =
field "$" Json.Decode.int field kernelConstants.virtualDom.nodeType Json.Decode.int
|> Json.Decode.andThen |> Json.Decode.andThen
(\typeInt -> (\nodeType ->
case typeInt of if nodeType == kernelConstants.virtualDom.nodeTypeText then
0 ->
Json.Decode.map TextTag decodeTextTag Json.Decode.map TextTag decodeTextTag
2 -> else if nodeType == kernelConstants.virtualDom.nodeTypeKeyedNode then
Json.Decode.map NodeEntry (decodeKeyedNode context) Json.Decode.map NodeEntry (decodeKeyedNode context)
1 -> else if nodeType == kernelConstants.virtualDom.nodeTypeNode then
Json.Decode.map NodeEntry (decodeNode context) Json.Decode.map NodeEntry (decodeNode context)
3 -> else if nodeType == kernelConstants.virtualDom.nodeTypeCustom then
decodeCustomNode context decodeCustomNode context
4 -> else if nodeType == kernelConstants.virtualDom.nodeTypeTagger then
decodeTagger context decodeTagger context
5 -> else if nodeType == kernelConstants.virtualDom.nodeTypeThunk then
field "node" (contextDecodeElmHtml context) field kernelConstants.virtualDom.node (contextDecodeElmHtml context)
_ -> else
Json.Decode.fail ("No such type as " ++ String.fromInt typeInt) Json.Decode.fail ("No such type as " ++ String.fromInt nodeType)
) )
@ -250,31 +237,21 @@ contextDecodeElmHtml context =
-} -}
decodeTextTag : Json.Decode.Decoder TextTagRecord decodeTextTag : Json.Decode.Decoder TextTagRecord
decodeTextTag = decodeTextTag =
field "a" (Json.Decode.andThen (\text -> Json.Decode.succeed { text = text }) Json.Decode.string) field kernelConstants.virtualDom.text (Json.Decode.andThen (\text -> Json.Decode.succeed { text = text }) Json.Decode.string)
{-| encode text tag
-}
encodeTextTag : TextTagRecord -> Json.Encode.Value
encodeTextTag { text } =
Json.Encode.object [ ( "a", Json.Encode.string text ) ]
{-| decode a tagger {-| decode a tagger
-} -}
decodeTagger : HtmlContext msg -> Json.Decode.Decoder (ElmHtml msg) decodeTagger : HtmlContext msg -> Json.Decode.Decoder (ElmHtml msg)
decodeTagger (HtmlContext taggers eventDecoder) = decodeTagger (HtmlContext taggers eventDecoder) =
Json.Decode.field "j" Json.Decode.value Json.Decode.field kernelConstants.virtualDom.tagger Json.Decode.value
|> Json.Decode.andThen |> Json.Decode.andThen
(\tagger -> (\tagger ->
let let
nodeDecoder = nodeDecoder =
contextDecodeElmHtml (HtmlContext (taggers ++ [ tagger ]) eventDecoder) contextDecodeElmHtml (HtmlContext (taggers ++ [ tagger ]) eventDecoder)
in in
-- The child node is at the k field of tagger Json.Decode.at [ kernelConstants.virtualDom.node ] nodeDecoder
-- TODO determine if the descendantsCount should be updated for this node,
-- because the tagger object counds as one node. this.b = 1 + (this.k.b || 0)
Json.Decode.at [ "k" ] nodeDecoder
) )
@ -287,10 +264,10 @@ decodeKeyedNode context =
Json.Decode.field "b" (contextDecodeElmHtml context) Json.Decode.field "b" (contextDecodeElmHtml context)
in in
Json.Decode.map4 NodeRecord Json.Decode.map4 NodeRecord
(Json.Decode.field tagKey Json.Decode.string) (Json.Decode.field kernelConstants.virtualDom.tag Json.Decode.string)
(Json.Decode.field childrenKey (Json.Decode.list decodeSecondNode)) (Json.Decode.field kernelConstants.virtualDom.kids (Json.Decode.list decodeSecondNode))
(Json.Decode.field factsKey (decodeFacts context)) (Json.Decode.field kernelConstants.virtualDom.facts (decodeFacts context))
(Json.Decode.field descendantsCountKey Json.Decode.int) (Json.Decode.field kernelConstants.virtualDom.descendantsCount Json.Decode.int)
{-| decode a node record {-| decode a node record
@ -298,23 +275,10 @@ decodeKeyedNode context =
decodeNode : HtmlContext msg -> Json.Decode.Decoder (NodeRecord msg) decodeNode : HtmlContext msg -> Json.Decode.Decoder (NodeRecord msg)
decodeNode context = decodeNode context =
Json.Decode.map4 NodeRecord Json.Decode.map4 NodeRecord
(field tagKey Json.Decode.string) (field kernelConstants.virtualDom.tag Json.Decode.string)
(field childrenKey (Json.Decode.list (contextDecodeElmHtml context))) (field kernelConstants.virtualDom.kids (Json.Decode.list (contextDecodeElmHtml context)))
(field factsKey (decodeFacts context)) (field kernelConstants.virtualDom.facts (decodeFacts context))
(field descendantsCountKey Json.Decode.int) (field kernelConstants.virtualDom.descendantsCount Json.Decode.int)
{-| encode a node record: currently does not support facts or children
-}
encodeNodeRecord : NodeRecord msg -> Json.Encode.Value
encodeNodeRecord record =
Json.Encode.object
[ ( tagKey, Json.Encode.string record.tag )
--, ( childrenKey, Json.Encode.list encodeElmHtml)
--, ( factsKey, encodeFacts)
, ( descendantsCountKey, Json.Encode.int record.descendantsCount )
]
{-| decode custom node into either markdown or custom {-| decode custom node into either markdown or custom
@ -332,8 +296,8 @@ decodeCustomNode context =
decodeCustomNodeRecord : HtmlContext msg -> Json.Decode.Decoder (CustomNodeRecord msg) decodeCustomNodeRecord : HtmlContext msg -> Json.Decode.Decoder (CustomNodeRecord msg)
decodeCustomNodeRecord context = decodeCustomNodeRecord context =
Json.Decode.map2 CustomNodeRecord Json.Decode.map2 CustomNodeRecord
(field factsKey (decodeFacts context)) (field kernelConstants.virtualDom.facts (decodeFacts context))
(field "g" Json.Decode.value) (field kernelConstants.virtualDom.model Json.Decode.value)
{-| decode markdown node record {-| decode markdown node record
@ -341,8 +305,8 @@ decodeCustomNodeRecord context =
decodeMarkdownNodeRecord : HtmlContext msg -> Json.Decode.Decoder (MarkdownNodeRecord msg) decodeMarkdownNodeRecord : HtmlContext msg -> Json.Decode.Decoder (MarkdownNodeRecord msg)
decodeMarkdownNodeRecord context = decodeMarkdownNodeRecord context =
Json.Decode.map2 MarkdownNodeRecord Json.Decode.map2 MarkdownNodeRecord
(field factsKey (decodeFacts context)) (field kernelConstants.virtualDom.facts (decodeFacts context))
(field "g" decodeMarkdownModel) (field kernelConstants.virtualDom.model decodeMarkdownModel)
{-| decode the styles {-| decode the styles
@ -355,19 +319,6 @@ decodeStyles =
] ]
{-| encode styles
-}
encodeStyles : Dict String String -> Json.Encode.Value
encodeStyles stylesDict =
let
encodedDict =
stylesDict
|> Dict.toList
|> List.map (\( k, v ) -> ( k, Json.Encode.string v ))
in
Json.Encode.object [ ( styleKey, Json.Encode.object encodedDict ) ]
{-| grab things from attributes via a decoder, then anything that isn't filtered on {-| grab things from attributes via a decoder, then anything that isn't filtered on
the object the object
-} -}
@ -409,7 +360,7 @@ decodeAttributes decoder =
] ]
decodeEvents : (EventHandler -> Json.Decode.Decoder msg) -> Json.Decode.Decoder (Dict String (Json.Decode.Decoder msg)) decodeEvents : (EventHandler -> VirtualDom.Handler msg) -> Json.Decode.Decoder (Dict String (VirtualDom.Handler msg))
decodeEvents taggedEventDecoder = decodeEvents taggedEventDecoder =
Json.Decode.oneOf Json.Decode.oneOf
[ Json.Decode.field eventKey (Json.Decode.dict (Json.Decode.map taggedEventDecoder Json.Decode.value)) [ Json.Decode.field eventKey (Json.Decode.dict (Json.Decode.map taggedEventDecoder Json.Decode.value))
@ -453,50 +404,36 @@ like elm-html-test does to extract the function inside those.
-} -}
decodeAttribute : Json.Decode.Decoder Attribute decodeAttribute : Json.Decode.Decoder Attribute
decodeAttribute = decodeAttribute =
Json.Decode.field "key" Json.Decode.string Json.Decode.field "$" Json.Decode.string
|> Json.Decode.andThen |> Json.Decode.andThen
(\key -> (\tag ->
if key == attributeKey then if tag == Constants.attributeKey then
Json.Decode.map2 AttributeRecord Json.Decode.map2 (\key val -> Attribute (AttributeRecord key val))
(Json.Decode.field "realKey" Json.Decode.string) (Json.Decode.field "n" Json.Decode.string)
(Json.Decode.field "value" Json.Decode.string) (Json.Decode.field "o" Json.Decode.string)
|> Json.Decode.map Attribute
else if key == attributeNamespaceKey then else if tag == Constants.attributeNamespaceKey then
Json.Decode.map3 NamespacedAttributeRecord Json.Decode.map3 NamespacedAttributeRecord
(Json.Decode.field "realKey" Json.Decode.string) (Json.Decode.field "n" Json.Decode.string)
(Json.Decode.at [ "value", "value" ] Json.Decode.string) (Json.Decode.at [ "o", "o" ] Json.Decode.string)
(Json.Decode.at [ "value", "namespace" ] Json.Decode.string) (Json.Decode.at [ "o", "f" ] Json.Decode.string)
|> Json.Decode.map NamespacedAttribute |> Json.Decode.map NamespacedAttribute
else if key == styleKey then else if tag == Constants.styleKey then
Json.Decode.map2 (\a b -> ( a, b )) Json.Decode.map2 (\key val -> Style { key = key, value = val })
(Json.Decode.field "_0" Json.Decode.string) (Json.Decode.field "n" Json.Decode.string)
(Json.Decode.field "_1" Json.Decode.string) (Json.Decode.field "o" Json.Decode.string)
|> elmListDecoder
|> Json.Decode.field "value"
|> Json.Decode.map Styles
else if key == eventKey then else if tag == Constants.propKey then
Json.Decode.map3 EventRecord Json.Decode.map2 (\key val -> Property (PropertyRecord key val))
(Json.Decode.field "realKey" Json.Decode.string) (Json.Decode.field "n" Json.Decode.string)
(Json.Decode.at [ "value", "decoder" ] Json.Decode.value) (Json.Decode.at [ "o", "a" ] Json.Decode.value)
(Json.Decode.at [ "value", "options" ] decodeOptions)
|> Json.Decode.map Event
else else
Json.Decode.field "value" Json.Decode.value Json.Decode.fail ("Unexpected Html.Attribute tag: " ++ tag)
|> Json.Decode.map (PropertyRecord key >> Property)
) )
decodeOptions : Json.Decode.Decoder { stopPropagation : Bool, preventDefault : Bool }
decodeOptions =
Json.Decode.map2 (\stopPropagation preventDefault -> { stopPropagation = stopPropagation, preventDefault = preventDefault })
(Json.Decode.field "stopPropagation" Json.Decode.bool)
(Json.Decode.field "preventDefault" Json.Decode.bool)
elmListDecoder : Json.Decode.Decoder a -> Json.Decode.Decoder (List a) elmListDecoder : Json.Decode.Decoder a -> Json.Decode.Decoder (List a)
elmListDecoder itemDecoder = elmListDecoder itemDecoder =
elmListDecoderHelp itemDecoder [] elmListDecoderHelp itemDecoder []

View File

@ -1,18 +1,19 @@
module ElmHtml.Markdown exposing module Test.Html.Internal.ElmHtml.Markdown exposing
( MarkdownOptions, MarkdownModel, baseMarkdownModel ( MarkdownOptions, MarkdownModel, baseMarkdownModel
, encodeOptions, encodeMarkdownModel, decodeMarkdownModel , decodeMarkdownModel
) )
{-| Markdown helpers {-| Markdown helpers
@docs MarkdownOptions, MarkdownModel, baseMarkdownModel @docs MarkdownOptions, MarkdownModel, baseMarkdownModel
@docs encodeOptions, encodeMarkdownModel, decodeMarkdownModel @docs decodeMarkdownModel
-} -}
import Json.Decode exposing (field) import Json.Decode exposing (field)
import Json.Encode import Json.Encode
import Test.Internal.KernelConstants exposing (kernelConstants)
{-| Just a default markdown model {-| Just a default markdown model
@ -47,27 +48,9 @@ type alias MarkdownModel =
} }
{-| We don't really care about encoding options right now
TODO: we will if we want to represent things as we do for elm-html
-}
encodeOptions : MarkdownOptions -> Json.Decode.Value
encodeOptions options =
Json.Encode.null
{-| encode markdown model
-}
encodeMarkdownModel : MarkdownModel -> Json.Decode.Value
encodeMarkdownModel model =
Json.Encode.object
[ ( "options", encodeOptions model.options )
, ( "markdown", Json.Encode.string model.markdown )
]
{-| decode a markdown model {-| decode a markdown model
-} -}
decodeMarkdownModel : Json.Decode.Decoder MarkdownModel decodeMarkdownModel : Json.Decode.Decoder MarkdownModel
decodeMarkdownModel = decodeMarkdownModel =
field "markdown" Json.Decode.string field kernelConstants.markdown.markdown Json.Decode.string
|> Json.Decode.map (MarkdownModel baseMarkdownModel.options) |> Json.Decode.map (MarkdownModel baseMarkdownModel.options)

View File

@ -1,4 +1,4 @@
module ElmHtml.ToString exposing module Test.Html.Internal.ElmHtml.ToString exposing
( nodeRecordToString, nodeToString, nodeToStringWithOptions ( nodeRecordToString, nodeToString, nodeToStringWithOptions
, FormatOptions, defaultFormatOptions , FormatOptions, defaultFormatOptions
) )
@ -12,8 +12,8 @@ module ElmHtml.ToString exposing
-} -}
import Dict exposing (Dict) import Dict exposing (Dict)
import ElmHtml.InternalTypes exposing (..)
import String import String
import Test.Html.Internal.ElmHtml.InternalTypes exposing (..)
{-| Formatting options to be used for converting to string {-| Formatting options to be used for converting to string
@ -111,8 +111,8 @@ nodeRecordToString options { tag, children, facts } =
[] -> [] ->
Nothing Nothing
stylesList -> styleValues ->
stylesList styleValues
|> List.map (\( key, value ) -> key ++ ":" ++ value ++ ";") |> List.map (\( key, value ) -> key ++ ":" ++ value ++ ";")
|> String.join "" |> String.join ""
|> (\styleString -> "style=\"" ++ styleString ++ "\"") |> (\styleString -> "style=\"" ++ styleString ++ "\"")
@ -130,19 +130,17 @@ nodeRecordToString options { tag, children, facts } =
|> String.join " " |> String.join " "
|> Just |> Just
boolToString b =
case b of
True ->
"True"
False ->
"False"
boolAttributes = boolAttributes =
Dict.toList facts.boolAttributes Dict.toList facts.boolAttributes
|> List.map |> List.map (\( k, v ) -> k ++ "=" ++ (String.toLower <| boolToString v))
(\( k, v ) ->
k
++ "="
++ (if v then
"true"
else
"false"
)
)
|> String.join " " |> String.join " "
|> Just |> Just
in in

View File

@ -0,0 +1,34 @@
module Test.Internal.KernelConstants exposing (kernelConstants)
{-| This module defines the mapping of optimized field name and enum values
for kernel code in other packages the we depend on.
-}
{-| NOTE: this is duplicating constants also defined in src/Elm/Kernel/HtmlAsJson.js
so if you make any changes here, be sure to synchronize them there!
-}
kernelConstants =
{ virtualDom =
{ nodeType = "$"
, nodeTypeText = 0
, nodeTypeKeyedNode = 2
, nodeTypeNode = 1
, nodeTypeCustom = 3
, nodeTypeTagger = 4
, nodeTypeThunk = 5
, tag = "c"
, kids = "e"
, facts = "d"
, descendantsCount = "b"
, text = "a"
, refs = "l"
, node = "k"
, tagger = "j"
, model = "g"
}
, markdown =
{ options = "a"
, markdown = "b"
}
}

View File

@ -20,6 +20,7 @@ import Pages.Internal.Platform.ToJsPayload as ToJsPayload
import Pages.Internal.StaticHttpBody as StaticHttpBody import Pages.Internal.StaticHttpBody as StaticHttpBody
import Pages.Manifest as Manifest import Pages.Manifest as Manifest
import Pages.ProgramConfig exposing (ProgramConfig) import Pages.ProgramConfig exposing (ProgramConfig)
import Pages.SiteConfig exposing (SiteConfig)
import Pages.StaticHttp.Request as Request import Pages.StaticHttp.Request as Request
import Path import Path
import ProgramTest exposing (ProgramTest) import ProgramTest exposing (ProgramTest)
@ -1124,6 +1125,7 @@ startLowLevel apiRoutes staticHttpCache pages =
|> ProgramTest.start (flags (Encode.encode 0 encodedFlags)) |> ProgramTest.start (flags (Encode.encode 0 encodedFlags))
site : SiteConfig ()
site = site =
{ data = DataSource.succeed () { data = DataSource.succeed ()
, canonicalUrl = "canonical-site-url" , canonicalUrl = "canonical-site-url"