esbuild-wasm-bundler/serve.tsx

153 lines
4.8 KiB
TypeScript
Raw Normal View History

2024-08-13 10:25:16 -04:00
import * as mime from "jsr:@std/media-types";
2024-06-12 14:48:53 -04:00
import { parseArgs } from "jsr:@std/cli/parse-args";
2024-06-14 17:07:50 -04:00
import bundler, {type ESBuild, type ImportMap} from "./mod.ts";
import Introspect from "./introspect.ts";
2024-06-07 16:54:12 -04:00
2024-06-12 14:48:53 -04:00
if(Deno.mainModule == import.meta.url)
{
serve(parseArgs(Deno.args) as ServeArgs);
}
2024-06-10 22:51:23 -04:00
2024-07-03 15:09:21 -04:00
export type ServeArgs = {path?:string, html?:string, port?:string|number};
2024-06-14 17:07:50 -04:00
async function serve(settings:ServeArgs):Promise<void>
2024-06-12 14:48:53 -04:00
{
2024-06-14 17:07:50 -04:00
// etag hash
const ETag = new Date().getTime().toString();
// extra parsing
const ExtensionsJS = ["tsx", "ts", "jsx", "js", "jsx", "mjs"];
const PathExtensionType=(inPath:string)=>
{
const path = inPath.endsWith("/") ? inPath.substring(0, inPath.length-1) : inPath;
const posDot = path.lastIndexOf(".");
const extension = (posDot > path.lastIndexOf("/")) ? path.substring(posDot+1) : null;
let type:string|null = null
if(extension)
{
2024-08-13 10:25:16 -04:00
type = (ExtensionsJS.includes(extension)) ? "application/javascript" : mime.contentType(extension);
2024-06-14 17:07:50 -04:00
}
return {path, extension, type};
}
// base path
2024-06-12 14:48:53 -04:00
let basePath = "";
2024-06-14 17:07:50 -04:00
let DenoConfig:ImportMap
const SetDirectory =async(inPath:string)=>{
2024-06-12 14:48:53 -04:00
if(inPath.startsWith("http://")||inPath.startsWith("https://")||inPath.startsWith("file://"))
{
basePath = inPath;
}
else
{
basePath = new URL(inPath, new URL(`file://${Deno.cwd().replaceAll("\\", "/")}/`).toString()).href;
}
if(!basePath.endsWith("/"))
{
basePath = basePath + "/";
}
console.log("Base Path:", basePath);
2024-06-14 17:07:50 -04:00
DenoConfig = await Introspect(basePath) as ImportMap;
console.log("found config", DenoConfig);
}
await SetDirectory(settings.path||"./");
2024-06-12 14:48:53 -04:00
2024-06-14 17:07:50 -04:00
// bundler
2024-06-12 14:48:53 -04:00
const transpiled:Map<string, string> = new Map();
2024-06-14 17:07:50 -04:00
const defaultConfig:ESBuild.BuildOptions = { entryPoints:[] as string[], outdir:"/", entryNames: `[dir][name]`, splitting: true };
const bundle =async(pathName:string, pathBase:string, buildConfig:ESBuild.BuildOptions = {}):Promise<string|void>=>
2024-06-07 16:54:12 -04:00
{
2024-06-14 17:07:50 -04:00
const lookupKey = pathName.substring(0, pathName.lastIndexOf("."))+".js";
const lookup = transpiled.get(lookupKey);
if(lookup)
2024-06-12 14:48:53 -04:00
{
2024-06-14 17:07:50 -04:00
return lookup;
2024-06-12 14:48:53 -04:00
}
2024-06-14 17:07:50 -04:00
buildConfig = {...defaultConfig, ...buildConfig}
buildConfig.entryPoints = [pathBase+pathName];
try
2024-06-12 14:48:53 -04:00
{
2024-06-14 17:07:50 -04:00
const results = await bundler(basePath, buildConfig, DenoConfig);
if(results.outputFiles)
{
results.outputFiles.forEach(output=>{transpiled.set(output.path, output.text); console.log("building", output.path);})
return results.outputFiles[0].text;
}
2024-06-12 14:48:53 -04:00
}
2024-06-14 17:07:50 -04:00
catch(_e){return;}
2024-06-12 14:48:53 -04:00
};
2024-06-14 17:07:50 -04:00
// index html serving
2024-06-12 14:48:53 -04:00
const resp404 = new Response("404", {status:404});
2024-06-14 17:07:50 -04:00
const resp304 = new Response(null, {status:304});
let respIndexBody:false|string = false;
2024-06-12 14:48:53 -04:00
if(settings.html)
{
2024-06-14 17:07:50 -04:00
if(settings.html.indexOf("<") != -1) // if html is actual markup instead of a url
2024-06-12 14:48:53 -04:00
{
2024-06-14 17:07:50 -04:00
respIndexBody = settings.html;
2024-06-12 14:48:53 -04:00
}
else
{
2024-06-14 17:07:50 -04:00
const load = await fetch(basePath+settings.html);
respIndexBody = await load.text();
2024-06-12 14:48:53 -04:00
}
}
2024-06-14 17:07:50 -04:00
///// go
Deno.serve({port:parseInt(settings.port as string)||8000}, async(req)=>
{
2024-08-13 10:25:16 -04:00
console.log("new request", req.url)
2024-06-14 17:07:50 -04:00
const checkHash = req.headers.get("if-none-match")
if(checkHash === ETag){
return resp304;
}
2024-06-12 14:48:53 -04:00
const url = new URL(req.url);
2024-06-14 17:07:50 -04:00
const {path, type} = PathExtensionType(url.pathname);
const headers = {"content-type":type||"", ETag, "Cache-Control":"max-age=3600"}
// serve javascript
if(type === "application/javascript")
2024-06-10 16:23:31 -04:00
{
2024-06-14 17:07:50 -04:00
const checkBuild = await bundle(path, basePath);
if(checkBuild)
2024-06-10 22:51:23 -04:00
{
2024-06-14 17:07:50 -04:00
return new Response(checkBuild, {headers});
}
}
// serve static
if(type)
{
try{
const handle = await fetch(basePath+path.substring(1));
return new Response(handle.body, {headers});
2024-06-10 22:51:23 -04:00
}
2024-06-14 17:07:50 -04:00
catch(_e){
return resp404;
}
2024-06-12 14:48:53 -04:00
}
2024-06-14 17:07:50 -04:00
// serve routes/implied index html files
if(respIndexBody)
2024-06-12 14:48:53 -04:00
{
2024-06-14 17:07:50 -04:00
return new Response(respIndexBody, {headers});
2024-06-12 14:48:53 -04:00
}
else
{
2024-06-14 17:07:50 -04:00
try{
const handle = await fetch(basePath+path.substring(1));
headers["content-type"] = "text/html"
return new Response(handle.body, {headers});
}
catch(_e){
return resp404;
2024-06-10 22:51:23 -04:00
}
2024-06-10 16:23:31 -04:00
}
2024-06-14 17:07:50 -04:00
2024-06-12 14:48:53 -04:00
});
}
2024-06-10 16:23:31 -04:00
2024-06-12 14:48:53 -04:00
export default serve