153 lines
4.8 KiB
TypeScript
153 lines
4.8 KiB
TypeScript
import * as mime from "jsr:@std/media-types";
|
|
import { parseArgs } from "jsr:@std/cli/parse-args";
|
|
import bundler, {type ESBuild, type ImportMap} from "./mod.ts";
|
|
import Introspect from "./introspect.ts";
|
|
|
|
if(Deno.mainModule == import.meta.url)
|
|
{
|
|
serve(parseArgs(Deno.args) as ServeArgs);
|
|
}
|
|
|
|
export type ServeArgs = {path?:string, html?:string, port?:string|number};
|
|
async function serve(settings:ServeArgs):Promise<void>
|
|
{
|
|
// 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)
|
|
{
|
|
type = (ExtensionsJS.includes(extension)) ? "application/javascript" : mime.contentType(extension);
|
|
}
|
|
|
|
return {path, extension, type};
|
|
}
|
|
|
|
// base path
|
|
let basePath = "";
|
|
let DenoConfig:ImportMap
|
|
const SetDirectory =async(inPath:string)=>{
|
|
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);
|
|
|
|
DenoConfig = await Introspect(basePath) as ImportMap;
|
|
console.log("found config", DenoConfig);
|
|
}
|
|
await SetDirectory(settings.path||"./");
|
|
|
|
// bundler
|
|
const transpiled:Map<string, string> = new Map();
|
|
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>=>
|
|
{
|
|
const lookupKey = pathName.substring(0, pathName.lastIndexOf("."))+".js";
|
|
const lookup = transpiled.get(lookupKey);
|
|
if(lookup)
|
|
{
|
|
return lookup;
|
|
}
|
|
buildConfig = {...defaultConfig, ...buildConfig}
|
|
buildConfig.entryPoints = [pathBase+pathName];
|
|
try
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
catch(_e){return;}
|
|
};
|
|
|
|
// index html serving
|
|
const resp404 = new Response("404", {status:404});
|
|
const resp304 = new Response(null, {status:304});
|
|
let respIndexBody:false|string = false;
|
|
if(settings.html)
|
|
{
|
|
if(settings.html.indexOf("<") != -1) // if html is actual markup instead of a url
|
|
{
|
|
respIndexBody = settings.html;
|
|
}
|
|
else
|
|
{
|
|
const load = await fetch(basePath+settings.html);
|
|
respIndexBody = await load.text();
|
|
}
|
|
}
|
|
|
|
///// go
|
|
Deno.serve({port:parseInt(settings.port as string)||8000}, async(req)=>
|
|
{
|
|
console.log("new request", req.url)
|
|
const checkHash = req.headers.get("if-none-match")
|
|
if(checkHash === ETag){
|
|
return resp304;
|
|
}
|
|
|
|
const url = new URL(req.url);
|
|
const {path, type} = PathExtensionType(url.pathname);
|
|
const headers = {"content-type":type||"", ETag, "Cache-Control":"max-age=3600"}
|
|
|
|
// serve javascript
|
|
if(type === "application/javascript")
|
|
{
|
|
const checkBuild = await bundle(path, basePath);
|
|
if(checkBuild)
|
|
{
|
|
return new Response(checkBuild, {headers});
|
|
}
|
|
}
|
|
|
|
// serve static
|
|
if(type)
|
|
{
|
|
try{
|
|
const handle = await fetch(basePath+path.substring(1));
|
|
return new Response(handle.body, {headers});
|
|
}
|
|
catch(_e){
|
|
return resp404;
|
|
}
|
|
}
|
|
|
|
// serve routes/implied index html files
|
|
if(respIndexBody)
|
|
{
|
|
return new Response(respIndexBody, {headers});
|
|
}
|
|
else
|
|
{
|
|
try{
|
|
const handle = await fetch(basePath+path.substring(1));
|
|
headers["content-type"] = "text/html"
|
|
return new Response(handle.body, {headers});
|
|
}
|
|
catch(_e){
|
|
return resp404;
|
|
}
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
export default serve |