Use esbuild to transpile port-data-source file, and watch for changes.

This commit is contained in:
Dillon Kearns 2022-03-02 10:14:55 -08:00
parent 8a0eae997b
commit 8489f44a3d
9 changed files with 127 additions and 29 deletions

View File

@ -0,0 +1,24 @@
import kleur from "kleur";
kleur.enabled = true;
export async function environmentVariable(name) {
const result = process.env[name];
if (result) {
return result;
} else {
throw `No environment variable called ${kleur
.yellow()
.underline(name)}\n\nAvailable:\n\n${Object.keys(process.env)
.slice(0, 5)
.join("\n")}`;
}
}
export async function hello(name) {
await waitFor(1000);
return `147 ${name}!!`;
}
function waitFor(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@ -24,6 +24,7 @@ const cookie = require("cookie");
const busboy = require("busboy"); const busboy = require("busboy");
const { createServer: createViteServer } = require("vite"); const { createServer: createViteServer } = require("vite");
const cliVersion = require("../../package.json").version; const cliVersion = require("../../package.json").version;
const esbuild = require("esbuild");
/** /**
* @param {{ port: string; base: string; https: boolean; debug: boolean; }} options * @param {{ port: string; base: string; https: boolean; debug: boolean; }} options
@ -137,6 +138,40 @@ async function start(options) {
base: options.base, base: options.base,
...viteConfig, ...viteConfig,
}); });
esbuild
.build({
entryPoints: ["./port-data-source"],
entryNames: "[dir]/[name]-[hash]",
outdir: ".elm-pages/compiled-ports",
assetNames: "[name]-[hash]",
chunkNames: "chunks/[name]-[hash]",
outExtension: { ".js": ".mjs" },
metafile: true,
bundle: false,
watch: true,
plugins: [
{
name: "example",
setup(build) {
build.onEnd((result) => {
global.portsFilePath = Object.keys(result.metafile.outputs)[0];
clients.forEach((client) => {
client.response.write(`data: content.dat\n\n`);
});
});
},
},
],
})
.then((result) => {
console.log("Watching port-data-source...");
})
.catch((error) => {
console.error("Failed to start port-data-source watcher", error);
});
const app = connect() const app = connect()
.use(timeMiddleware()) .use(timeMiddleware())
@ -288,6 +323,7 @@ async function start(options) {
mode: "dev-server", mode: "dev-server",
pathname, pathname,
serverRequest, serverRequest,
portsFilePath: global.portsFilePath,
}); });
readyThread.worker.on("message", (message) => { readyThread.worker.on("message", (message) => {
if (message.tag === "done") { if (message.tag === "done") {
@ -379,6 +415,7 @@ async function start(options) {
}); });
req.on("end", async function () { req.on("end", async function () {
// TODO run render directly instead of in worker thread
await runRenderThread( await runRenderThread(
await reqToJson(req, body, requestTime), await reqToJson(req, body, requestTime),
pathname, pathname,

5
generator/src/hello.ts Normal file
View File

@ -0,0 +1,5 @@
export function hello(): string {
console.log("HELLLO!!!!");
return "Hello World!";
}

View File

@ -7,10 +7,11 @@ let Elm;
global.staticHttpCache = {}; global.staticHttpCache = {};
async function run({ mode, pathname, serverRequest }) { async function run({ mode, pathname, serverRequest, portsFilePath }) {
console.time(`${threadId} ${pathname}`); console.time(`${threadId} ${pathname}`);
try { try {
const renderResult = await renderer( const renderResult = await renderer(
portsFilePath,
workerData.basePath, workerData.basePath,
requireElm(mode), requireElm(mode),
mode, mode,

View File

@ -30,6 +30,7 @@ module.exports =
* @returns * @returns
*/ */
async function run( async function run(
portsFile,
basePath, basePath,
elmModule, elmModule,
mode, mode,
@ -49,6 +50,7 @@ module.exports =
// we can provide a fake HTTP instead of xhr2 (which is otherwise needed for Elm HTTP requests from Node) // we can provide a fake HTTP instead of xhr2 (which is otherwise needed for Elm HTTP requests from Node)
XMLHttpRequest = {}; XMLHttpRequest = {};
const result = await runElmApp( const result = await runElmApp(
portsFile,
basePath, basePath,
elmModule, elmModule,
mode, mode,
@ -71,6 +73,7 @@ module.exports =
* @returns {Promise<({is404: boolean} & ( { kind: 'json'; contentJson: string} | { kind: 'html'; htmlString: string } | { kind: 'api-response'; body: string; }) )>} * @returns {Promise<({is404: boolean} & ( { kind: 'json'; contentJson: string} | { kind: 'html'; htmlString: string } | { kind: 'api-response'; body: string; }) )>}
*/ */
function runElmApp( function runElmApp(
portsFile,
basePath, basePath,
elmModule, elmModule,
mode, mode,
@ -161,7 +164,10 @@ function runElmApp(
} }
} else if (fromElm.tag === "DoHttp") { } else if (fromElm.tag === "DoHttp") {
const requestToPerform = fromElm.args[0]; const requestToPerform = fromElm.args[0];
if (requestToPerform.url.startsWith("elm-pages-internal://")) { if (
requestToPerform.url !== "elm-pages-internal://port" &&
requestToPerform.url.startsWith("elm-pages-internal://")
) {
runInternalJob( runInternalJob(
app, app,
mode, mode,
@ -171,7 +177,7 @@ function runElmApp(
patternsToWatch patternsToWatch
); );
} else { } else {
runHttpJob(app, mode, requestToPerform, fs, hasFsAccess); runHttpJob(portsFile, app, mode, requestToPerform, fs, hasFsAccess);
} }
} else if (fromElm.tag === "Errors") { } else if (fromElm.tag === "Errors") {
foundErrors = true; foundErrors = true;
@ -229,10 +235,18 @@ async function outputString(
/** @typedef { { head: any[]; errors: any[]; contentJson: any[]; html: string; route: string; title: string; } } Arg */ /** @typedef { { head: any[]; errors: any[]; contentJson: any[]; html: string; route: string; title: string; } } Arg */
async function runHttpJob(app, mode, requestToPerform, fs, hasFsAccess) { async function runHttpJob(
portsFile,
app,
mode,
requestToPerform,
fs,
hasFsAccess
) {
pendingDataSourceCount += 1; pendingDataSourceCount += 1;
try { try {
const responseFilePath = await lookupOrPerform( const responseFilePath = await lookupOrPerform(
portsFile,
mode, mode,
requestToPerform, requestToPerform,
hasFsAccess hasFsAccess

View File

@ -16,29 +16,36 @@ function requestToString(request) {
/** /**
* @param {Object} request * @param {Object} request
*/ */
function fullPath(request, hasFsAccess) { function fullPath(portsHash, request, hasFsAccess) {
const requestWithPortHash =
request.url === "elm-pages-internal://port"
? { portsHash, ...request }
: request;
if (hasFsAccess) { if (hasFsAccess) {
return path.join( return path.join(
process.cwd(), process.cwd(),
".elm-pages", ".elm-pages",
"http-response-cache", "http-response-cache",
requestToString(request) requestToString(requestWithPortHash)
); );
} else { } else {
return path.join("/", requestToString(request)); return path.join("/", requestToString(requestWithPortHash));
} }
} }
/** /**
* @param {string} mode * @param {string} mode
* @param {{url: string; headers: {[x: string]: string}; method: string; body: Body } } rawRequest * @param {{url: string;headers: {[x: string]: string;};method: string;body: Body;}} rawRequest
* @returns {Promise<string>} * @returns {Promise<string>}
* @param {string} portsFile
* @param {boolean} hasFsAccess
*/ */
function lookupOrPerform(mode, rawRequest, hasFsAccess) { function lookupOrPerform(portsFile, mode, rawRequest, hasFsAccess) {
const { fs } = require("./request-cache-fs.js")(hasFsAccess); const { fs } = require("./request-cache-fs.js")(hasFsAccess);
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const request = toRequest(rawRequest); const request = toRequest(rawRequest);
const responsePath = fullPath(request, hasFsAccess); const portsHash = portsFile.match(/-([^-]+)\.mjs$/)[1];
const responsePath = fullPath(portsHash, request, hasFsAccess);
// TODO check cache expiration time and delete and go to else if expired // TODO check cache expiration time and delete and go to else if expired
if (await checkFileExists(fs, responsePath)) { if (await checkFileExists(fs, responsePath)) {
@ -48,17 +55,14 @@ function lookupOrPerform(mode, rawRequest, hasFsAccess) {
let portDataSource = {}; let portDataSource = {};
let portDataSourceFound = false; let portDataSourceFound = false;
try { try {
portDataSource = requireUncached( portDataSource = await import(path.join(process.cwd(), portsFile));
mode,
path.join(process.cwd(), "port-data-source.js")
);
portDataSourceFound = true; portDataSourceFound = true;
} catch (e) {} } catch (e) {}
if (request.url.startsWith("port://")) { if (request.url === "elm-pages-internal://port") {
try { try {
const portName = request.url.replace(/^port:\/\//, ""); const { input, portName } = rawRequest.body.args[0];
// console.time(JSON.stringify(request.url));
if (!portDataSource[portName]) { if (!portDataSource[portName]) {
if (portDataSourceFound) { if (portDataSourceFound) {
throw `DataSource.Port.send "${portName}" is not defined. Be sure to export a function with that name from port-data-source.js`; throw `DataSource.Port.send "${portName}" is not defined. Be sure to export a function with that name from port-data-source.js`;
@ -70,9 +74,7 @@ function lookupOrPerform(mode, rawRequest, hasFsAccess) {
} }
await fs.promises.writeFile( await fs.promises.writeFile(
responsePath, responsePath,
JSON.stringify( JSON.stringify(jsonResponse(await portDataSource[portName](input)))
await portDataSource[portName](rawRequest.body.args[0])
)
); );
resolve(responsePath); resolve(responsePath);
} catch (error) { } catch (error) {
@ -228,4 +230,11 @@ function requireUncached(mode, filePath) {
return require(filePath); return require(filePath);
} }
/**
* @param {unknown} json
*/
function jsonResponse(json) {
return { bodyKind: "json", body: json };
}
module.exports = { lookupOrPerform }; module.exports = { lookupOrPerform };

1
package-lock.json generated
View File

@ -19,6 +19,7 @@
"devcert": "^1.2.0", "devcert": "^1.2.0",
"elm-doc-preview": "^5.0.5", "elm-doc-preview": "^5.0.5",
"elm-hot": "^1.1.6", "elm-hot": "^1.1.6",
"esbuild": "^0.14.23",
"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",

View File

@ -33,6 +33,7 @@
"devcert": "^1.2.0", "devcert": "^1.2.0",
"elm-doc-preview": "^5.0.5", "elm-doc-preview": "^5.0.5",
"elm-hot": "^1.1.6", "elm-hot": "^1.1.6",
"esbuild": "^0.14.23",
"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",
@ -73,4 +74,4 @@
"bin": { "bin": {
"elm-pages": "generator/src/cli.js" "elm-pages": "generator/src/cli.js"
} }
} }

View File

@ -8,8 +8,9 @@ module DataSource.Port exposing (get)
import DataSource import DataSource
import DataSource.Http import DataSource.Http
import DataSource.Internal.Request
import Json.Decode exposing (Decoder) import Json.Decode exposing (Decoder)
import Json.Encode import Json.Encode as Encode
{-| In a vanilla Elm application, ports let you either send or receive JSON data between your Elm application and the JavaScript context in the user's browser at runtime. {-| In a vanilla Elm application, ports let you either send or receive JSON data between your Elm application and the JavaScript context in the user's browser at runtime.
@ -73,12 +74,17 @@ prefer to add ANSI color codes within the error string in an exception and it wi
As with any JavaScript or NodeJS code, avoid doing blocking IO operations. For example, avoid using `fs.readFileSync`, because blocking IO can slow down your elm-pages builds and dev server. As with any JavaScript or NodeJS code, avoid doing blocking IO operations. For example, avoid using `fs.readFileSync`, because blocking IO can slow down your elm-pages builds and dev server.
-} -}
get : String -> Json.Encode.Value -> Decoder b -> DataSource.DataSource b get : String -> Encode.Value -> Decoder b -> DataSource.DataSource b
get portName input decoder = get portName input decoder =
DataSource.Http.request DataSource.Internal.Request.request
{ url = "port://" ++ portName { name = "port"
, method = "GET" , body =
, headers = [] Encode.object
, body = DataSource.Http.jsonBody input [ ( "input", input )
, ( "portName", Encode.string portName )
]
|> DataSource.Http.jsonBody
, expect =
decoder
|> DataSource.Http.expectJson
} }
(DataSource.Http.expectJson decoder)