lots of changes

This commit is contained in:
Seth Trowbridge 2024-06-14 17:07:50 -04:00
parent 85631f2173
commit b5aece91e1
7 changed files with 141 additions and 141 deletions

13
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"request":"attach",
"name":"Attach",
"type": "node"
}
]
}

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"exports": "./mod.ts", "exports": "./mod.ts",
"tasks": { "tasks": {
"serve": "deno run -A --no-lock serve.tsx --path=./test" "serve": "deno run -A --no-lock serve.tsx --path=./test --html=index.html",
"jsapi": "deno run -A --no-lock jsapi.tsx"
} }
} }

View File

@ -1,32 +0,0 @@
{
"version": "3",
"packages": {
"specifiers": {
"jsr:@std/cli": "jsr:@std/cli@0.224.6"
},
"jsr": {
"@std/cli@0.224.6": {
"integrity": "98d7e17a70c1c24980baa2b9a511b29e0a460e7a3b8e3d4e220979d77d5f348e"
}
}
},
"remote": {
"https://deno.land/x/esbuild@v0.19.2/wasm.js": "5ffeb3d973e57351eb4d2d03ffafc8ce5672e946d0f0a786c4aed2ca29cec004",
"https://deno.land/x/jsonct@v0.1.0/mod.ts": "dba7e7f3529be6369f5c718e3a18b69f15ffa176006d2a7565073ce6c5bd9f3f",
"https://deno.land/x/jsonct@v0.1.0/src/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
"https://deno.land/x/jsonct@v0.1.0/src/parse.ts": "a3a016822446b0584b40bae9098df480db5590a9915c9e3c623ba2801cf1b8df",
"https://esm.sh/esbuild-plugin-importmaps@1.0.0": "ab9e79660ff4d57d2ed7ef5e8516fbe0e79305267f22e1e7270d0a17ae0c2029",
"https://esm.sh/mime@4.0.3": "9e2ae6a32fa99974824ef71da6e10a921702bc82dd321802ad14b84ae68b515d",
"https://esm.sh/preact@10.22.0/compat/jsx-runtime": "a2f6ddc2ce374813df1c13826a9ad010e90b5a70a989f1069a367ef60dd52eb0",
"https://esm.sh/stable/preact@10.22.0/denonext/compat.js": "7c0b206984707cfef58efae492ea8d5212b8f620dd8c83294a5c832fb422c766",
"https://esm.sh/stable/preact@10.22.0/denonext/compat/jsx-runtime.js": "fecfa3df69d580507801575175087de9a2a9fc23bb4900004a1f4cbd5b362634",
"https://esm.sh/stable/preact@10.22.0/denonext/hooks.js": "09230113132c216bbc3847aaad11289771e088be1b0eb9e49cbc724faaeac205",
"https://esm.sh/stable/preact@10.22.0/denonext/jsx-runtime.js": "de60943799b1cbe6066c4f83f4ca71ef37011d7f5be7bef58ed980e8ff3f996a",
"https://esm.sh/stable/preact@10.22.0/denonext/preact.mjs": "20c9563e051dd66e053d3afb450f61b48f2fa0d0ce4f69f8f0a2f23c1ef090da",
"https://esm.sh/v135/@jspm/import-map@1.0.8/denonext/import-map.mjs": "fc291e729df6bef849df47df8893b64749785ca65fd5fe1d0e7969db5d3b63ea",
"https://esm.sh/v135/esbuild-plugin-importmaps@1.0.0/denonext/esbuild-plugin-importmaps.mjs": "08b603d074dd2861345f7d224c255c46d7f7213a283026552c492f465fe595ce",
"https://esm.sh/v135/mime@4.0.3/denonext/mime.mjs": "b0fd9fc3e38041cc74ea371133ae2d30e1d610f6c89f038c609a63cce1160d05",
"https://esm.sh/v135/mime@4.0.3/denonext/types/other.js": "45cb4b3bf4f070e2d92a2c60e67570fb73b6aac6672a86da50448a85ee15be1d",
"https://esm.sh/v135/mime@4.0.3/denonext/types/standard.js": "46a2e7cf4eec9dfed5e9928d6597aa01898030654c18e8be71facd4d904e4d1c"
}
}

View File

@ -1,10 +1,4 @@
import { parse as JSONC } from "https://deno.land/x/jsonct@v0.1.0/mod.ts"; import { parse as JSONC } from "https://deno.land/x/jsonct@v0.1.0/mod.ts";
import { parseArgs } from "jsr:@std/cli/parse-args";
if(Deno.mainModule == import.meta.url)
{
serve(parseArgs(Deno.args) as ServeArgs);
}
type JsonLike = { [key: string]: string | string[] | JsonLike; }; type JsonLike = { [key: string]: string | string[] | JsonLike; };
@ -13,11 +7,13 @@ export const Root = new URL(`file://${Deno.cwd().replaceAll("\\", "/")}`).toStri
export default async function HuntConfig(directory=Root) export default async function HuntConfig(directory=Root)
{ {
let path:string, json:JsonLike|undefined; let path:string, json:JsonLike|undefined;
console.log("searchig in directory", directory)
const loadJSON =async(inPath:string)=>{ const loadJSON =async(inPath:string)=>{
try{ try{
const resp = await fetch(directory + inPath); const path = new URL(inPath, directory);
if(inPath.endsWith(".jsonc")) console.log("looking at", path.href);
const resp = await fetch(path);
if(inPath.endsWith("./.jsonc"))
{ {
const text = await resp.text(); const text = await resp.text();
json = JSONC(text) as JsonLike; json = JSONC(text) as JsonLike;
@ -36,20 +32,20 @@ export default async function HuntConfig(directory=Root)
try// look for `deno.json` try// look for `deno.json`
{ {
json = await loadJSON("deno.json"); json = await loadJSON("./deno.json");
} }
catch(_e) catch(_e)
{ {
try // look for `deno.jsonc` try // look for `deno.jsonc`
{ {
json = await loadJSON("deno.jsonc"); json = await loadJSON("./deno.jsonc");
} }
catch(_e) catch(_e)
{ {
try // look in the vscode plugin settings try // look in the vscode plugin settings
{ {
json = await loadJSON(".vscode/settings.json") json = await loadJSON("./.vscode/settings.json")
path = json ? json["deno.config"] as string : ""; path = json ? json["deno.config"] as string : "";
json = undefined; json = undefined;
if(path) if(path)

View File

@ -1,3 +1,9 @@
import Bundle from "./mod.ts"; import Bundle, { type ImportMap} from "./mod.ts";
import Config from "./test/deno.json" with {type:"json"};
Bundle({entryPoints:["./test/app.tsx"]}, {}) const path = import.meta.resolve("./test/");
const {outputFiles} = await Bundle(path, {entryPoints:["./app.tsx"]}, Config);
for(const item of outputFiles)
{
console.log(item.text);
}

26
mod.ts
View File

@ -1,14 +1,14 @@
import * as ESBuild from "https://deno.land/x/esbuild@v0.19.2/wasm.js"; import * as ESBuild from "https://deno.land/x/esbuild@v0.19.2/wasm.js";
import * as Mapper from "https://esm.sh/esbuild-plugin-importmaps@1.0.0"; // https://github.com/andstellar/esbuild-plugin-importmaps import * as Mapper from "https://esm.sh/esbuild-plugin-importmaps@1.0.0"; // https://github.com/andstellar/esbuild-plugin-importmaps
import Introspect, {Root} from "./introspect.ts"; import Introspect from "./introspect.ts";
const prefix = "/_dot_importer_/"; const prefix = "/_dot_importer_/";
const resolvePlugin:ESBuild.Plugin = { const resolvePlugin =(fullPathDir:string):ESBuild.Plugin=>({
name: "resolve-plugin", name: "resolve-plugin",
setup(build) { setup(build) {
build.onResolve({ filter: /^(\.\/|\.\.\/).*/ }, (args)=> build.onResolve({ filter: /^(\.\/|\.\.\/).*/ }, (args)=>
{ {
let resolveRoot = args.importer||Root; let resolveRoot = args.importer||fullPathDir;
if(resolveRoot.startsWith(prefix)) if(resolveRoot.startsWith(prefix))
{ {
resolveRoot = resolveRoot.substring(prefix.length); resolveRoot = resolveRoot.substring(prefix.length);
@ -21,33 +21,31 @@ const resolvePlugin:ESBuild.Plugin = {
}); });
build.onLoad({ filter: /.*/, namespace:"http" }, async(args)=> { build.onLoad({ filter: /.*/, namespace:"http" }, async(args)=> {
const fetchPath = args.path.substring(prefix.length); const fetchPath = args.path.substring(prefix.length);
console.log("fetch path", fetchPath);
const result = await fetch(fetchPath); const result = await fetch(fetchPath);
const contents = await result.text(); const contents = await result.text();
return { contents, loader: `tsx` }; return { contents, loader: `tsx` };
}); });
}, },
}; });
await ESBuild.initialize({ worker: false }); await ESBuild.initialize({ worker: false });
export type ImportMap = Parameters<typeof Mapper.importmapPlugin>[0]; export type ImportMap = Parameters<typeof Mapper.importmapPlugin>[0];
export type BuildOptions = ESBuild.BuildOptions; export type BuildOptions = ESBuild.BuildOptions;
/** /**
* *
* @param directory Full file:// or http(s):// path to the directory containing assets you want to build (needed to resolve relative imports)
* @param buildOptions ESBuild "build" options (will be merged with "reasonable defaults") for docs: https://esbuild.github.io/api/#general-options * @param buildOptions ESBuild "build" options (will be merged with "reasonable defaults") for docs: https://esbuild.github.io/api/#general-options
* @param importMap * @param importMap An object to act as the import map ({imports:Record<string, string>}). If this is left blank, a configuration will be scanned for in the "directory"
* @returns build result * @returns build result
*/ */
export default async function(buildOptions={} as BuildOptions, importMap:ImportMap|string|false = false):Promise<ESBuild.BuildResult<ESBuild.BuildOptions>> export default async function(directory:string, buildOptions={} as BuildOptions, importMap?:ImportMap):Promise<ESBuild.BuildResult<ESBuild.BuildOptions>>
{ {
if(importMap === false) if(!importMap)
{ {
importMap = await Introspect() as ImportMap; importMap = await Introspect(directory) as ImportMap
} }
else if(typeof importMap === "string") console.log("using import map", importMap);
{
importMap = await Introspect(importMap) as ImportMap;
}
const configuration:ESBuild.BuildOptions = { const configuration:ESBuild.BuildOptions = {
entryPoints: ["entry"], entryPoints: ["entry"],
bundle: true, bundle: true,
@ -57,7 +55,7 @@ export default async function(buildOptions={} as BuildOptions, importMap:ImportM
jsxImportSource: "react", jsxImportSource: "react",
...buildOptions, ...buildOptions,
plugins: [ plugins: [
resolvePlugin, resolvePlugin(directory),
Mapper.importmapPlugin(importMap) as ESBuild.Plugin, Mapper.importmapPlugin(importMap) as ESBuild.Plugin,
...buildOptions.plugins||[] ...buildOptions.plugins||[]
] ]

174
serve.tsx
View File

@ -1,6 +1,7 @@
import bundler from "./mod.ts";
import mime from "https://esm.sh/mime@4.0.3"; import mime from "https://esm.sh/mime@4.0.3";
import { parseArgs } from "jsr:@std/cli/parse-args"; 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) if(Deno.mainModule == import.meta.url)
{ {
@ -8,10 +9,31 @@ if(Deno.mainModule == import.meta.url)
} }
type ServeArgs = {path?:string, html?:string, port?:string|number}; type ServeArgs = {path?:string, html?:string, port?:string|number};
function serve(settings:ServeArgs):void 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.getType(extension);
}
return {path, extension, type};
}
// base path
let basePath = ""; let basePath = "";
const SetDirectory =(inPath:string)=>{ let DenoConfig:ImportMap
const SetDirectory =async(inPath:string)=>{
if(inPath.startsWith("http://")||inPath.startsWith("https://")||inPath.startsWith("file://")) if(inPath.startsWith("http://")||inPath.startsWith("https://")||inPath.startsWith("file://"))
{ {
basePath = inPath; basePath = inPath;
@ -25,107 +47,103 @@ function serve(settings:ServeArgs):void
basePath = basePath + "/"; basePath = basePath + "/";
} }
console.log("Base Path:", basePath); console.log("Base Path:", basePath);
DenoConfig = await Introspect(basePath) as ImportMap;
console.log("found config", DenoConfig);
} }
await SetDirectory(settings.path||"./");
SetDirectory(settings.path||"./"); // bundler
const transpiled:Map<string, string> = new Map(); const transpiled:Map<string, string> = new Map();
const ServeJSCode =(inText:string)=> new Response(inText, {headers:{"content-type":"application/javascript"}}); const defaultConfig:ESBuild.BuildOptions = { entryPoints:[] as string[], outdir:"/", entryNames: `[dir][name]`, splitting: true };
const ServeStatic =async(inPath:string)=> 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 try
{ {
const handle = await fetch(basePath+inPath.substring(1)); const results = await bundler(basePath, buildConfig, DenoConfig);
return new Response(handle.body, {headers:{"content-type": mime.getType(inPath)??""}}) if(results.outputFiles)
} {
catch(_e) results.outputFiles.forEach(output=>{transpiled.set(output.path, output.text); console.log("building", output.path);})
{ return results.outputFiles[0].text;
console.log("404", inPath); }
return resp404;
} }
catch(_e){return;}
}; };
// index html serving
const resp404 = new Response("404", {status:404}); const resp404 = new Response("404", {status:404});
let respIndex:false|Response|Promise<Response> = false; const resp304 = new Response(null, {status:304});
let respIndexBody:false|string = false;
if(settings.html) if(settings.html)
{ {
if(settings.html.indexOf("<") != -1) if(settings.html.indexOf("<") != -1) // if html is actual markup instead of a url
{ {
respIndex= new Response(settings.html, {headers:{"content-type":"text-html"}}); respIndexBody = settings.html;
} }
else else
{ {
respIndex = ServeStatic(settings.html); const load = await fetch(basePath+settings.html);
respIndexBody = await load.text();
} }
} }
///// go
Deno.serve({port:parseInt(settings.port as string)||8000}, async(req)=>{ Deno.serve({port:parseInt(settings.port as string)||8000}, async(req)=>
const url = new URL(req.url); {
const index = url.pathname.lastIndexOf("."); const checkHash = req.headers.get("if-none-match")
if(index > -1) if(checkHash === ETag){
{ return resp304;
const ext = url.pathname.substring(index+1);
if(ext === "ts" || ext == "tsx" || ext == "js" || ext == "jsx")
{
if(ext == "js")
{
const lookup = transpiled.get(url.pathname);
if(lookup)
{
return ServeJSCode(lookup);
}
}
else
{
const lookup = transpiled.get(url.pathname.substring(0, index)+".js");
if(lookup)
{
return ServeJSCode(lookup);
}
}
try
{
const results = await bundler({
entryPoints:[basePath+url.pathname],
outdir:"/",
entryNames: `[dir][name]`,
splitting: true
}, basePath);
if(results.outputFiles)
{
results.outputFiles.forEach(output=>transpiled.set(output.path, output.text))
return ServeJSCode(results.outputFiles[0].text);
}
else
{
throw(new Error("no output"));
}
}
catch(_e)
{
return resp404;
}
}
else
{
return ServeStatic(url.pathname);
}
} }
if(respIndex) 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")
{ {
return respIndex; 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 else
{ {
let indexLookup = url.pathname; try{
if(!indexLookup.endsWith("/")) const handle = await fetch(basePath+path.substring(1));
{ headers["content-type"] = "text/html"
indexLookup = indexLookup+"/"; return new Response(handle.body, {headers});
}
catch(_e){
return resp404;
} }
return ServeStatic(indexLookup+"index.html");
} }
}); });