mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-11-25 09:21:57 +03:00
Include Html.Lazy thunk evaluation in server-side rendering in dev server.
This commit is contained in:
parent
344341a93a
commit
4c9986702b
@ -1,8 +1,9 @@
|
||||
const spawnCallback = require("cross-spawn").spawn;
|
||||
const fs = require("fs");
|
||||
const fsHelpers = require("./dir-helpers.js");
|
||||
const fsPromises = require("fs").promises;
|
||||
const path = require("path");
|
||||
const kleur = require("kleur");
|
||||
const debug = true;
|
||||
const { inject } = require("elm-hot");
|
||||
const pathToClientElm = path.join(
|
||||
process.cwd(),
|
||||
@ -10,21 +11,6 @@ const pathToClientElm = path.join(
|
||||
"browser-elm.js"
|
||||
);
|
||||
|
||||
async function spawnElmMake(options, elmEntrypointPath, outputPath, cwd) {
|
||||
const fullOutputPath = cwd ? path.join(cwd, outputPath) : outputPath;
|
||||
await runElm(options, elmEntrypointPath, outputPath, cwd);
|
||||
|
||||
await fs.promises.writeFile(
|
||||
fullOutputPath,
|
||||
(await fs.promises.readFile(fullOutputPath, "utf-8"))
|
||||
.replace(
|
||||
/return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_JSON_STRINGIFY.\)/g,
|
||||
"return " + (debug ? "_Json_wrap(x)" : "x")
|
||||
)
|
||||
.replace(`console.log('App dying')`, "")
|
||||
);
|
||||
}
|
||||
|
||||
async function compileElmForBrowser(options) {
|
||||
await runElm(options, "./.elm-pages/Main.elm", pathToClientElm);
|
||||
return fs.promises.writeFile(
|
||||
@ -32,13 +18,147 @@ async function compileElmForBrowser(options) {
|
||||
inject(await fs.promises.readFile(pathToClientElm, "utf-8")).replace(
|
||||
/return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_FORM_TO_STRING.\)/g,
|
||||
"let appendSubmitter = (myFormData, event) => { event.submitter && event.submitter.name && event.submitter.name.length > 0 ? myFormData.append(event.submitter.name, event.submitter.value) : myFormData; return myFormData }; return " +
|
||||
(debug
|
||||
? "_Json_wrap([...(appendSubmitter(new FormData(_Json_unwrap(event).target), _Json_unwrap(event)))])"
|
||||
(true
|
||||
? // TODO remove hardcoding
|
||||
"_Json_wrap([...(appendSubmitter(new FormData(_Json_unwrap(event).target), _Json_unwrap(event)))])"
|
||||
: "[...(new FormData(event.target))")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function compileCliApp(
|
||||
options,
|
||||
elmEntrypointPath,
|
||||
outputPath,
|
||||
cwd,
|
||||
readFrom
|
||||
) {
|
||||
await compileElm(options, elmEntrypointPath, outputPath, cwd);
|
||||
|
||||
const elmFileContent = await fsPromises.readFile(readFrom, "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(
|
||||
readFrom,
|
||||
elmFileContent
|
||||
.replace(
|
||||
/return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_JSON_STRINGIFY.\)/g,
|
||||
"return " +
|
||||
// TODO should the logic for this be `if options.optimize`? Or does the first case not make sense at all?
|
||||
(true
|
||||
? `${forceThunksSource}
|
||||
return _Json_wrap(forceThunks(html));
|
||||
`
|
||||
: `${forceThunksSource}
|
||||
return forceThunks(html);
|
||||
`)
|
||||
)
|
||||
.replace(/console\.log..App dying../, "")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} elmEntrypointPath
|
||||
* @param {string} outputPath
|
||||
* @param {string | undefined} cwd
|
||||
*/
|
||||
async function compileElm(options, elmEntrypointPath, outputPath, cwd) {
|
||||
await spawnElmMake(options, elmEntrypointPath, outputPath, cwd);
|
||||
if (!options.debug) {
|
||||
// TODO maybe pass in a boolean argument for whether it's build or dev server, and only do eol2 for build
|
||||
// await elmOptimizeLevel2(outputPath, cwd);
|
||||
}
|
||||
}
|
||||
|
||||
function spawnElmMake(options, elmEntrypointPath, outputPath, cwd) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const subprocess = spawnCallback(
|
||||
`lamdera`,
|
||||
[
|
||||
"make",
|
||||
elmEntrypointPath,
|
||||
"--output",
|
||||
outputPath,
|
||||
// TODO use --optimize for prod build
|
||||
...(options.debug ? ["--debug"] : []),
|
||||
"--report",
|
||||
"json",
|
||||
],
|
||||
{
|
||||
// ignore stdout
|
||||
// stdio: ["inherit", "ignore", "inherit"],
|
||||
|
||||
cwd: cwd,
|
||||
}
|
||||
);
|
||||
if (await fsHelpers.fileExists(outputPath)) {
|
||||
await fsPromises.unlink(outputPath, {
|
||||
force: true /* ignore errors if file doesn't exist */,
|
||||
});
|
||||
}
|
||||
let commandOutput = "";
|
||||
|
||||
subprocess.stderr.on("data", function (data) {
|
||||
commandOutput += data;
|
||||
});
|
||||
subprocess.on("error", function () {
|
||||
reject(commandOutput);
|
||||
});
|
||||
|
||||
subprocess.on("close", async (code) => {
|
||||
if (
|
||||
code == 0 &&
|
||||
(await fsHelpers.fileExists(outputPath)) &&
|
||||
commandOutput === ""
|
||||
) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(commandOutput);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} elmEntrypointPath
|
||||
* @param {string} outputPath
|
||||
@ -56,6 +176,7 @@ async function runElm(options, elmEntrypointPath, outputPath, cwd) {
|
||||
"--output",
|
||||
outputPath,
|
||||
...(options.debug ? ["--debug"] : []),
|
||||
...(options.optimize ? ["--optimize"] : []),
|
||||
"--report",
|
||||
"json",
|
||||
],
|
||||
@ -129,10 +250,44 @@ async function runElmReview(cwd) {
|
||||
});
|
||||
}
|
||||
|
||||
function elmOptimizeLevel2(outputPath, cwd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const optimizedOutputPath = outputPath + ".opt";
|
||||
const subprocess = spawnCallback(
|
||||
`elm-optimize-level-2`,
|
||||
[outputPath, "--output", optimizedOutputPath],
|
||||
{
|
||||
// ignore stdout
|
||||
// stdio: ["inherit", "ignore", "inherit"],
|
||||
|
||||
cwd: cwd,
|
||||
}
|
||||
);
|
||||
let commandOutput = "";
|
||||
|
||||
subprocess.stderr.on("data", function (data) {
|
||||
commandOutput += data;
|
||||
});
|
||||
|
||||
subprocess.on("close", async (code) => {
|
||||
if (
|
||||
code === 0 &&
|
||||
commandOutput === "" &&
|
||||
(await fsHelpers.fileExists(optimizedOutputPath))
|
||||
) {
|
||||
await fs.promises.copyFile(optimizedOutputPath, outputPath);
|
||||
resolve();
|
||||
} else {
|
||||
reject(commandOutput);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
spawnElmMake,
|
||||
compileElmForBrowser,
|
||||
runElmReview,
|
||||
compileCliApp,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -4,9 +4,9 @@ const which = require("which");
|
||||
const chokidar = require("chokidar");
|
||||
const { URL } = require("url");
|
||||
const {
|
||||
spawnElmMake,
|
||||
compileElmForBrowser,
|
||||
runElmReview,
|
||||
compileCliApp,
|
||||
} = require("./compile-elm.js");
|
||||
const http = require("http");
|
||||
const https = require("https");
|
||||
@ -64,7 +64,18 @@ async function start(options) {
|
||||
process.exit(1);
|
||||
}
|
||||
let clientElmMakeProcess = compileElmForBrowser(options);
|
||||
let pendingCliCompile = compileCliApp(options);
|
||||
console.log({ options });
|
||||
let pendingCliCompile = compileCliApp(
|
||||
options,
|
||||
".elm-pages/Main.elm",
|
||||
|
||||
path.join(process.cwd(), "elm-stuff/elm-pages/", "elm.js"),
|
||||
|
||||
// "elm.js",
|
||||
"elm-stuff/elm-pages/",
|
||||
path.join("elm-stuff/elm-pages/", "elm.js")
|
||||
);
|
||||
|
||||
watchElmSourceDirs(true);
|
||||
|
||||
async function setup() {
|
||||
@ -107,14 +118,6 @@ async function start(options) {
|
||||
watcher.add(sourceDirs);
|
||||
}
|
||||
|
||||
async function compileCliApp(options) {
|
||||
await spawnElmMake(
|
||||
options,
|
||||
".elm-pages/Main.elm",
|
||||
"elm.js",
|
||||
"elm-stuff/elm-pages/"
|
||||
);
|
||||
}
|
||||
const viteConfig = await import(
|
||||
path.join(process.cwd(), "elm-pages.config.mjs")
|
||||
)
|
||||
@ -211,7 +214,13 @@ async function start(options) {
|
||||
try {
|
||||
await codegen.generate(options.base);
|
||||
clientElmMakeProcess = compileElmForBrowser(options);
|
||||
pendingCliCompile = compileCliApp(options);
|
||||
pendingCliCompile = compileCliApp(
|
||||
options,
|
||||
".elm-pages/Main.elm",
|
||||
"elm.js",
|
||||
"elm-stuff/elm-pages/",
|
||||
path.join("elm-stuff/elm-pages/.elm-pages/", "elm.js")
|
||||
);
|
||||
|
||||
Promise.all([clientElmMakeProcess, pendingCliCompile])
|
||||
.then(() => {
|
||||
@ -237,7 +246,13 @@ async function start(options) {
|
||||
pendingCliCompile = Promise.reject(errorJson);
|
||||
} else {
|
||||
clientElmMakeProcess = compileElmForBrowser(options);
|
||||
pendingCliCompile = compileCliApp(options);
|
||||
pendingCliCompile = compileCliApp(
|
||||
options,
|
||||
".elm-pages/Main.elm",
|
||||
"elm.js",
|
||||
"elm-stuff/elm-pages/",
|
||||
path.join("elm-stuff/elm-pages/.elm-pages/", "elm.js")
|
||||
);
|
||||
}
|
||||
|
||||
Promise.all([clientElmMakeProcess, pendingCliCompile])
|
||||
|
Loading…
Reference in New Issue
Block a user