mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-11-28 23:12:22 +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 spawnCallback = require("cross-spawn").spawn;
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const fsHelpers = require("./dir-helpers.js");
|
||||||
|
const fsPromises = require("fs").promises;
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const kleur = require("kleur");
|
const kleur = require("kleur");
|
||||||
const debug = true;
|
|
||||||
const { inject } = require("elm-hot");
|
const { inject } = require("elm-hot");
|
||||||
const pathToClientElm = path.join(
|
const pathToClientElm = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
@ -10,21 +11,6 @@ const pathToClientElm = path.join(
|
|||||||
"browser-elm.js"
|
"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) {
|
async function compileElmForBrowser(options) {
|
||||||
await runElm(options, "./.elm-pages/Main.elm", pathToClientElm);
|
await runElm(options, "./.elm-pages/Main.elm", pathToClientElm);
|
||||||
return fs.promises.writeFile(
|
return fs.promises.writeFile(
|
||||||
@ -32,13 +18,147 @@ async function compileElmForBrowser(options) {
|
|||||||
inject(await fs.promises.readFile(pathToClientElm, "utf-8")).replace(
|
inject(await fs.promises.readFile(pathToClientElm, "utf-8")).replace(
|
||||||
/return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_FORM_TO_STRING.\)/g,
|
/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 " +
|
"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
|
(true
|
||||||
? "_Json_wrap([...(appendSubmitter(new FormData(_Json_unwrap(event).target), _Json_unwrap(event)))])"
|
? // TODO remove hardcoding
|
||||||
|
"_Json_wrap([...(appendSubmitter(new FormData(_Json_unwrap(event).target), _Json_unwrap(event)))])"
|
||||||
: "[...(new FormData(event.target))")
|
: "[...(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} elmEntrypointPath
|
||||||
* @param {string} outputPath
|
* @param {string} outputPath
|
||||||
@ -56,6 +176,7 @@ async function runElm(options, elmEntrypointPath, outputPath, cwd) {
|
|||||||
"--output",
|
"--output",
|
||||||
outputPath,
|
outputPath,
|
||||||
...(options.debug ? ["--debug"] : []),
|
...(options.debug ? ["--debug"] : []),
|
||||||
|
...(options.optimize ? ["--optimize"] : []),
|
||||||
"--report",
|
"--report",
|
||||||
"json",
|
"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 = {
|
module.exports = {
|
||||||
spawnElmMake,
|
|
||||||
compileElmForBrowser,
|
compileElmForBrowser,
|
||||||
runElmReview,
|
runElmReview,
|
||||||
|
compileCliApp,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,9 +4,9 @@ const which = require("which");
|
|||||||
const chokidar = require("chokidar");
|
const chokidar = require("chokidar");
|
||||||
const { URL } = require("url");
|
const { URL } = require("url");
|
||||||
const {
|
const {
|
||||||
spawnElmMake,
|
|
||||||
compileElmForBrowser,
|
compileElmForBrowser,
|
||||||
runElmReview,
|
runElmReview,
|
||||||
|
compileCliApp,
|
||||||
} = require("./compile-elm.js");
|
} = require("./compile-elm.js");
|
||||||
const http = require("http");
|
const http = require("http");
|
||||||
const https = require("https");
|
const https = require("https");
|
||||||
@ -64,7 +64,18 @@ async function start(options) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
let clientElmMakeProcess = compileElmForBrowser(options);
|
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);
|
watchElmSourceDirs(true);
|
||||||
|
|
||||||
async function setup() {
|
async function setup() {
|
||||||
@ -107,14 +118,6 @@ async function start(options) {
|
|||||||
watcher.add(sourceDirs);
|
watcher.add(sourceDirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compileCliApp(options) {
|
|
||||||
await spawnElmMake(
|
|
||||||
options,
|
|
||||||
".elm-pages/Main.elm",
|
|
||||||
"elm.js",
|
|
||||||
"elm-stuff/elm-pages/"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const viteConfig = await import(
|
const viteConfig = await import(
|
||||||
path.join(process.cwd(), "elm-pages.config.mjs")
|
path.join(process.cwd(), "elm-pages.config.mjs")
|
||||||
)
|
)
|
||||||
@ -211,7 +214,13 @@ async function start(options) {
|
|||||||
try {
|
try {
|
||||||
await codegen.generate(options.base);
|
await codegen.generate(options.base);
|
||||||
clientElmMakeProcess = compileElmForBrowser(options);
|
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])
|
Promise.all([clientElmMakeProcess, pendingCliCompile])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -237,7 +246,13 @@ async function start(options) {
|
|||||||
pendingCliCompile = Promise.reject(errorJson);
|
pendingCliCompile = Promise.reject(errorJson);
|
||||||
} else {
|
} else {
|
||||||
clientElmMakeProcess = compileElmForBrowser(options);
|
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])
|
Promise.all([clientElmMakeProcess, pendingCliCompile])
|
||||||
|
Loading…
Reference in New Issue
Block a user