Compare commits

..

No commits in common. "master" and "0.0.33" have entirely different histories.

17 changed files with 301 additions and 236 deletions

View File

@ -1,2 +1 @@
deno.lock deno.lock
.env

8
.vscode/launch.json vendored
View File

@ -5,17 +5,13 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Debug Serve Mode",
"request": "launch", "request": "launch",
"name": "Debug Serve Mode",
"type": "node", "type": "node",
"cwd": "${workspaceFolder}/example",
"runtimeExecutable": "deno", "runtimeExecutable": "deno",
"runtimeArgs": ["task", "debug"], "runtimeArgs": ["task", "debug"],
"attachSimplePort": 9229 "attachSimplePort": 9229
},
{
"name":"Attach",
"request": "attach",
"type": "node"
} }
] ]
} }

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"deno.enable": true
}

View File

@ -1,7 +1,3 @@
import * as ISO from ">able/iso-elements.tsx";
console.log(ISO)
export default ()=><div> export default ()=><div>
<h1 class="p-4 bg-red-500 text-white">App</h1> <h1>App!</h1>
</div>; </div>;

View File

@ -167,10 +167,9 @@ export async function Check()
const importMap = imports.json.imports as Record<string, string>; const importMap = imports.json.imports as Record<string, string>;
const bake =async(obj:ConfigCheck)=> await Deno.writeTextFile(Deno.cwd()+"/"+obj.path, JSON.stringify(obj.json, null, "\t")); const bake =async(obj:ConfigCheck)=> await Deno.writeTextFile(Deno.cwd()+"/"+obj.path, JSON.stringify(obj.json, null, "\t"));
importMap["react"] = `https://esm.sh/preact@10.20.2/compat`; importMap["react"] = `https://esm.sh/preact@10.17.1/compat`;
importMap["react/"] = `https://esm.sh/preact@10.20.2/compat/`; importMap["react/"] = `https://esm.sh/preact@10.17.1/compat/`;
importMap["@preact/signals"] = `https://esm.sh/@preact/signals@1.2.3?deps=preact@10.20.2`; importMap["@preact/signals"] = `https://esm.sh/@preact/signals@1.2.1`;
importMap["@twind/core"] = `https://esm.sh/v126/@twind/core@1.1.3/es2022/core.mjs`;
importMap[">able/"] = `${RootHost}`; importMap[">able/"] = `${RootHost}`;
if(!importMap[">able/app.tsx"]) if(!importMap[">able/app.tsx"])
{ {
@ -196,28 +195,18 @@ export async function Check()
const confTasks = (config.json.tasks || {}) as Record<string, string>; const confTasks = (config.json.tasks || {}) as Record<string, string>;
config.json.tasks = {...confTasks, ...tasks}; config.json.tasks = {...confTasks, ...tasks};
const optionsRequired = const options =
{ {
"lib": ["deno.window", "dom", "dom.iterable", "dom.asynciterable"], "lib": ["deno.window", "dom", "dom.asynciterable"],
"jsx": "react-jsx" "jsx": "react-jsx",
"jsxImportSource": "react"
} }
const optionsCurrent = config.json.compilerOptions as Record<string, string|string[]> || {}; const compOpts = config.json.compilerOptions as Record<string, string|string[]> || {};
//const compLib:string[] = compOpts.lib as string[] || []; const compLib:string[] = compOpts.lib as string[] || [];
compOpts.jsx = options.jsx;
if(!optionsCurrent.lib) compOpts.jsxImportSource = options.jsxImportSource;
{ compOpts.lib = [...compLib, ...options.lib];
optionsCurrent.lib = []; config.json.compilerOptions = compOpts;
}
optionsRequired.lib.forEach(s=>
{
if(!optionsCurrent.lib.includes(s))
{
(optionsCurrent.lib as string[]).push(s);
}
});
optionsCurrent.jsx = optionsRequired.jsx;
config.json.compilerOptions = optionsCurrent;
await bake(imports); await bake(imports);
await bake(config); await bake(config);

16
cli.tsx
View File

@ -99,8 +99,8 @@ if(arg._.length)
} }
case "cloud" : case "cloud" :
{ {
const useToken = await collect("DENO_DEPLOY_TOKEN", arg, env); let useToken = await collect("DENO_DEPLOY_TOKEN", arg, env);
const useProject = await collect("DENO_DEPLOY_PROJECT", arg, env); let useProject = await collect("DENO_DEPLOY_PROJECT", arg, env);
let scanProd:string[]|string|null = prompt(`Do you want to deploy to *production*?`); let scanProd:string[]|string|null = prompt(`Do you want to deploy to *production*?`);
if(scanProd) if(scanProd)
@ -113,23 +113,19 @@ if(arg._.length)
scanProd = []; scanProd = [];
} }
const command = [ await SubProcess([
"run", "run",
"-A", "-A",
"--no-lock", "--no-lock",
`--config=${config.path}`, `--config=${config.path}`,
"https://deno.land/x/deploy@1.12.0/deployctl.ts", "https://deno.land/x/deploy/deployctl.ts",
"deploy", "deploy",
`--project=${useProject}`, `--project=${useProject}`,
`--token=${useToken}`, `--token=${useToken}`,
`--import-map=${imports.path}`, `--import-map=${imports.path}`,
`--exclude=.*`, RootHost+"run.tsx",
...scanProd, ...scanProd,
RootHost+"run.tsx"]; ...Deno.args]);
await SubProcess(command);
break;
} }
case "upgrade" : case "upgrade" :
{ {

View File

@ -1,28 +1,26 @@
{ {
"imports": { "imports": {
"react": "https://esm.sh/preact@10.17.1/compat",
"react": "https://esm.sh/preact@10.20.2/compat", "react/": "https://esm.sh/preact@10.17.1/compat/",
"react-original": "https://esm.sh/preact@10.20.2/compat", ">able/": "http://localhost:4507/",
"react-original/": "https://esm.sh/preact@10.20.2/compat/", ">able/app.tsx": "./app.tsx",
"react/": "https://esm.sh/preact@10.20.2/compat/", "@preact/signals": "https://esm.sh/@preact/signals@1.2.1"
"@preact/signals": "https://esm.sh/@preact/signals@1.2.3?deps=preact@10.20.2",
"signals-original": "https://esm.sh/@preact/signals@1.2.3?deps=preact@10.20.2",
"@twind/core": "https://esm.sh/v126/@twind/core@1.1.3/es2022/core.mjs",
">other/": "https://esm.sh/",
">able/": "./",
">able/app.tsx": "./app.tsx"
}, },
"tasks": { "tasks": {
"check": "deno run -A --no-lock ./cli.tsx check", "check": "deno run -A --no-lock http://localhost:4507/cli.tsx check",
"local": "deno run -A --no-lock ./cli.tsx local", "local": "deno run -A --no-lock http://localhost:4507/cli.tsx local",
"debug": "deno run -A --no-lock ./cli.tsx debug", "debug": "deno run -A --no-lock http://localhost:4507/cli.tsx debug",
"serve": "deno run -A --no-lock ./cli.tsx serve", "serve": "deno run -A --no-lock http://localhost:4507/cli.tsx serve",
"cloud": "deno run -A --no-lock ./cli.tsx cloud", "cloud": "deno run -A --no-lock http://localhost:4507/cli.tsx cloud",
"install": "deno install -A -r -f -n able ./cli.tsx" "install": "deno install -A -r -f http://localhost:4507/cli.tsx"
}, },
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx", "jsx": "react-jsx",
"lib": ["deno.window", "dom", "dom.iterable", "dom.asynciterable"] "jsxImportSource": "react",
"lib": [
"deno.window",
"dom",
"dom.asynciterable"
]
} }
} }

View File

@ -1,32 +0,0 @@
{
"version": "3",
"remote": {
"https://deno.land/std@0.180.0/media_types/_db.ts": "7606d83e31f23ce1a7968cbaee852810c2cf477903a095696cdc62eaab7ce570",
"https://deno.land/std@0.180.0/media_types/_util.ts": "916efbd30b6148a716f110e67a4db29d6949bf4048997b754415dd7e42c52378",
"https://deno.land/std@0.180.0/media_types/content_type.ts": "c682589a0aeb016bfed355cc1ed6fbb3ead2ea48fc0000ac5de6a5730613ad1c",
"https://deno.land/std@0.180.0/media_types/extension.ts": "7a4ef2813d7182f724a941f38161525996e4a67abc3cf6a0f9bc2168d73a0f0e",
"https://deno.land/std@0.180.0/media_types/extensions_by_type.ts": "4358023feac696e6e9d49c0f1e76a859f03ca254df57812f31f8536890c3a443",
"https://deno.land/std@0.180.0/media_types/format_media_type.ts": "1e35e16562e5c417401ffc388a9f8f421f97f0ee06259cbe990c51bae4e6c7a8",
"https://deno.land/std@0.180.0/media_types/get_charset.ts": "8be15a1fd31a545736b91ace56d0e4c66ea0d7b3fdc5c90760e8202e7b4b1fad",
"https://deno.land/std@0.180.0/media_types/mod.ts": "d3f0b99f85053bc0b98ecc24eaa3546dfa09b856dc0bbaf60d8956d2cdd710c8",
"https://deno.land/std@0.180.0/media_types/parse_media_type.ts": "bed260d868ea271445ae41d748e7afed9b5a7f407d2777ead08cecf73e9278de",
"https://deno.land/std@0.180.0/media_types/type_by_extension.ts": "6076a7fc63181d70f92ec582fdea2c927eb2cfc7f9c9bee9d6add2aca86f2355",
"https://deno.land/std@0.180.0/media_types/vendor/mime-db.v1.52.0.ts": "6925bbcae81ca37241e3f55908d0505724358cda3384eaea707773b2c7e99586",
"https://deno.land/std@0.194.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
"https://deno.land/std@0.194.0/collections/filter_values.ts": "5b9feaf17b9a6e5ffccdd36cf6f38fa4ffa94cff2602d381c2ad0c2a97929652",
"https://deno.land/std@0.194.0/collections/without_all.ts": "a89f5da0b5830defed4f59666e188df411d8fece35a5f6ca69be6ca71a95c185",
"https://deno.land/std@0.194.0/dotenv/mod.ts": "39e5d19e077e55d7e01ea600eb1c6d1e18a8dfdfc65d68826257a576788da3a4",
"https://deno.land/std@0.194.0/flags/mod.ts": "17f444ddbee43c5487568de0c6a076c7729cfe90d96d2ffcd2b8f8adadafb6e8",
"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/@swc/wasm-web@1.3.62": "b43fb5cde95beb7736182fa62250235dfa6b71717b9d38aa4e6077f05ec90e5e",
"https://esm.sh/preact@10.20.2/compat/jsx-runtime": "e3942a5ffd734d5eaf0790ada3ed4ad81c0c0c2ff56a8e4740247de259f7fb65",
"https://esm.sh/stable/preact@10.20.2/denonext/compat.js": "2e0564fd10e09b587503f9ecd4407ac8726c79beae80026ac89034a47b270c68",
"https://esm.sh/stable/preact@10.20.2/denonext/compat/jsx-runtime.js": "fbbbceb98af95d1c73181f9e5043fad6cdae30ef9e5fcf90d44ffd6fa6055c02",
"https://esm.sh/stable/preact@10.20.2/denonext/hooks.js": "91d64a217b2f2c9f724042d0ed1b87bf3edf721261e86358aa6fd55501ee915f",
"https://esm.sh/stable/preact@10.20.2/denonext/jsx-runtime.js": "2a5b981955e92e3ff86906ac0e5955ec0e6e5ca71032f3f063912cb85ae9a7f1",
"https://esm.sh/stable/preact@10.20.2/denonext/preact.mjs": "f418bc70c24b785703afb9d4dea8cdc1e315e43c8df620a0c52fd27ad9bd70eb",
"https://esm.sh/v135/@swc/wasm-web@1.3.62/denonext/wasm-web.mjs": "57046d46c9ef1398a294ba7447034f5966e48866a05c309cccec4bb4d6e7c61b"
}
}

20
deno__.json Normal file
View File

@ -0,0 +1,20 @@
{
"compilerOptions": { "lib": ["deno.window", "dom"],
"jsx": "react-jsx",
"jsxImportSource": "react"
},
"imports":
{
"react":"https://esm.sh/preact@10.15.1/compat",
"react/":"https://esm.sh/preact@10.15.1/compat/",
"react-original":"https://esm.sh/preact@10.15.1/compat",
},
"tasks":
{
"local": "deno run -A --reload=http://localhost:4507 --no-lock ./run-local.tsx --port=1234",
"serve": "deno run -A --reload=http://localhost:4507 --no-lock ./run-serve.tsx --port=1234",
"cloud": "deno run -A --reload=http://localhost:4507 --no-lock ./run-deploy.tsx",
"debug": "deno run -A --reload=http://localhost:4507 --no-lock --inspect-wait ./run-serve.tsx --port=1234",
}
}

View File

@ -1,5 +1,4 @@
import { HMR } from "./hmr-react.tsx"; import { type StateCapture } from "./hmr-react.tsx";
import { GroupSignal, GroupSignalHook } from "./hmr-signal.tsx";
const FileListeners = new Map() as Map<string, Array<(module:unknown)=>void>>; const FileListeners = new Map() as Map<string, Array<(module:unknown)=>void>>;
export const FileListen =(inPath:string, inHandler:()=>void)=> export const FileListen =(inPath:string, inHandler:()=>void)=>
@ -14,18 +13,58 @@ Socket.addEventListener('message', async(event:{data:string})=>
{ {
// When a file changes, dynamically re-import it to get the updated members // When a file changes, dynamically re-import it to get the updated members
// send the updated members to any listeners for that file // send the updated members to any listeners for that file
GroupSignal.reset();
const reImport = await import(document.location.origin+event.data+"?reload="+Math.random()); const reImport = await import(document.location.origin+event.data+"?reload="+Math.random());
FileListeners.get(event.data)?.forEach(reExport=>reExport(reImport)); FileListeners.get(event.data)?.forEach(reExport=>reExport(reImport));
GroupSignal.swap();
GroupSignalHook.reset();
HMR.update(); HMR.update();
GroupSignalHook.reset();
}); });
Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")}) Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")})
const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000);
/*
Each custom component is secretly modified to have an extra state and id.
When there is an HMR update, this state is changed, forcing it to re-render.
Each *user-created* React.useState is secretly modified and accompanied by an ID.
Every time its state is set, the HMR.statesNew map for this ID is set to contain the new state and updater.
When a component is removed, any of it's states in HMR.statesNew are also removed.
(HMR.statesNew is the "running total" of all states currently at play).
---
When a state is interacted with:
- statesNew for this id is set
- the internal state is also set in the traditional way
When there is an HMR update:
- All custom components are re-rendered...
for each useState(value) call that then happens in the re-render:
- if there is a "statesOld" value for this state, use that and ignore the passed value, otherwise use the passed value
- if this state has not been interacted with since the last reload (statesNew is empty at this id), set statesNew<id> with whatever is in statesOld<id>
- statesNew is moved into *statesOld*
- statesNew is cleared.
*/
const HMR =
{
reloads:1,
RegisteredComponents: new Map() as Map<string, ()=>void>,
statesNew: new Map() as Map<string, StateCapture>,
statesOld: new Map() as Map<string, StateCapture>,
wireframe: false,
RegisterComponent(reactID:string, value:()=>void):void
{
this.RegisteredComponents.set(reactID, value);
},
update()
{
this.reloads++;
this.RegisteredComponents.forEach(handler=>handler());
this.RegisteredComponents.clear();
this.statesOld = this.statesNew;
this.statesNew = new Map();
}
};
export {HMR};

View File

@ -1,51 +1,5 @@
import * as ReactParts from "react-original"; import * as ReactParts from "react-original";
import { HMR } from "./hmr-listen.tsx";
/*
Each custom component is secretly modified to have an extra state and id.
When there is an HMR update, this state is changed, forcing it to re-render.
Each *user-created* React.useState is secretly modified and accompanied by an ID.
Every time its state is set, the HMR.statesNew map for this ID is set to contain the new state and updater.
When a component is removed, any of it's states in HMR.statesNew are also removed.
(HMR.statesNew is the "running total" of all states currently at play).
---
When a state is interacted with:
- statesNew for this id is set
- the internal state is also set in the traditional way
When there is an HMR update:
- All custom components are re-rendered...
for each useState(value) call that then happens in the re-render:
- if there is a "statesOld" value for this state, use that and ignore the passed value, otherwise use the passed value
- if this state has not been interacted with since the last reload (statesNew is empty at this id), set statesNew<id> with whatever is in statesOld<id>
- statesNew is moved into *statesOld*
- statesNew is cleared.
*/
export const HMR =
{
reloads:1,
RegisteredComponents: new Map() as Map<string, ()=>void>,
statesNew: new Map() as Map<string, StateCapture>,
statesOld: new Map() as Map<string, StateCapture>,
wireframe: false,
RegisterComponent(reactID:string, value:()=>void):void
{
this.RegisteredComponents.set(reactID, value);
},
update()
{
this.reloads++;
this.RegisteredComponents.forEach(handler=>handler());
this.RegisteredComponents.clear();
this.statesOld = this.statesNew;
this.statesNew = new Map();
}
};
export type StateType = boolean|number|string|Record<string, string> export type StateType = boolean|number|string|Record<string, string>
export type StateCapture = {state:StateType, set:ReactParts.StateUpdater<StateType>, reload:number}; export type StateCapture = {state:StateType, set:ReactParts.StateUpdater<StateType>, reload:number};

View File

@ -1,47 +0,0 @@
import * as SignalsParts from "signals-original";
import DeepEqual from "https://esm.sh/deep-eql@4.1.3";
type Entry<T> = [signal:SignalsParts.Signal<T>, initArg:T];
function ProxyGroup<T>(inFunc:(initArg:T)=>SignalsParts.Signal<T>)
{
let recordEntry:Entry<T>[] = [];
let recordEntryNew:Entry<T>[] = [];
let recordIndex = 0;
const reset =()=> recordIndex = 0;
const swap =()=>
{
recordEntry = recordEntryNew;
recordEntryNew = [] as Entry<T>[];
};
const proxy =(arg:T)=>
{
const lookupOld = recordEntry[recordIndex];
if(lookupOld && DeepEqual(lookupOld[1], arg))
{
recordEntryNew[recordIndex] = lookupOld;
recordIndex++;
return lookupOld[0];
}
else
{
const sig = inFunc(arg);
recordEntryNew[recordIndex] = [sig, arg];
recordEntry[recordIndex] = [sig, arg];
recordIndex++;
return sig;
}
};
return {reset, swap, proxy};
}
export const GroupSignal = ProxyGroup(SignalsParts.signal);
export const GroupSignalHook = ProxyGroup(SignalsParts.useSignal);
const proxySignal = GroupSignal.proxy;
const proxySignalHook = GroupSignalHook.proxy;
export * from "signals-original";
export { proxySignal as signal, proxySignalHook as useSignal };
export default {...SignalsParts, signal:proxySignal, useSignal:proxySignalHook};

View File

@ -65,7 +65,7 @@ const findNextExport =(inFile:string, inIndex=0, inLocal:Array<string>, inForeig
} }
else else
{ {
const members = inFile.substring(nextCharInd+1, endBracketInd).replace(/\s/g, ''); const members = inFile.substring(nextCharInd+1, endBracketInd);
members.split(",").forEach(part=> members.split(",").forEach(part=>
{ {
const renamed = part.split(" as "); const renamed = part.split(" as ");

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import * as TW from "@twind/core"; import * as TW from "https://esm.sh/v126/@twind/core@1.1.3/es2022/core.mjs";
import TWPreTail from "https://esm.sh/v126/@twind/preset-tailwind@1.1.3/es2022/preset-tailwind.mjs"; import TWPreTail from "https://esm.sh/v126/@twind/preset-tailwind@1.1.3/es2022/preset-tailwind.mjs";
import TWPreAuto from "https://esm.sh/v126/@twind/preset-autoprefix@1.0.7/es2022/preset-autoprefix.mjs"; import TWPreAuto from "https://esm.sh/v126/@twind/preset-autoprefix@1.0.7/es2022/preset-autoprefix.mjs";
@ -10,25 +10,29 @@ const Configure =
hash: false hash: false
} as TW.TwindUserConfig; } as TW.TwindUserConfig;
export const Shadow =(inElement:HTMLElement, inConfig:TW.TwindUserConfig)=> export const Shadow =(inElement:HTMLElement, inConfig?:TW.TwindUserConfig)=>
{ {
const merge = inConfig ? {...Configure, ...inConfig} : Configure;
const ShadowDOM = inElement.attachShadow({ mode: "open" }); const ShadowDOM = inElement.attachShadow({ mode: "open" });
const ShadowDiv = document.createElement("div"); const ShadowDiv = document.createElement("div");
const ShadowCSS = document.createElement("style"); const ShadowCSS = document.createElement("style");
ShadowDOM.append(ShadowCSS); ShadowDOM.append(ShadowCSS);
ShadowDOM.append(ShadowDiv); ShadowDOM.append(ShadowDiv);
TW.observe(TW.twind(inConfig, TW.cssom(ShadowCSS)), ShadowDiv); TW.observe(TW.twind(merge, TW.cssom(ShadowCSS)), ShadowDiv);
return ShadowDiv; return ShadowDiv;
}; };
export default async(inSelector:string, inModulePath:string, inMemberApp="default", inMemberCSS="CSS", inShadow=false):Promise<(()=>void)|false>=> export default async(inSelector:string, inModulePath:string, inMemberApp="default", inMemberCSS="CSS"):Promise<(()=>void)|false>=>
{ {
if(!inModulePath) if(!inModulePath)
{ {
return false; return false;
} }
let dom = document.querySelector(inSelector); let dom = document.querySelector(inSelector);
if(!dom) if(!dom)
{ {
@ -37,17 +41,7 @@ export default async(inSelector:string, inModulePath:string, inMemberApp="defaul
} }
const module = await import(inModulePath); const module = await import(inModulePath);
dom = Shadow(dom as HTMLElement, module[inMemberCSS])
const merge = inMemberCSS ? {...Configure, ...module[inMemberCSS]} : Configure;
if(inShadow)
{
dom = Shadow(dom as HTMLElement, merge);
}
else
{
TW.install(merge);
}
const app = React.createElement(()=> React.createElement(module[inMemberApp], null), null); const app = React.createElement(()=> React.createElement(module[inMemberApp], null), null);
if(React.render) if(React.render)
@ -62,4 +56,5 @@ export default async(inSelector:string, inModulePath:string, inMemberApp="defaul
root.render(app); root.render(app);
return root.unmount; return root.unmount;
} }
}; };

View File

@ -1,5 +1,4 @@
import {Configure, Transpile, Extension} from "./run-serve.tsx"; import {Configure, Transpile, Extension, Root} from "./run-serve.tsx";
import { Root } from "./checker.tsx";
import * as Collect from "./hmr-static.tsx"; import * as Collect from "./hmr-static.tsx";
const SocketsLive:Set<WebSocket> = new Set(); const SocketsLive:Set<WebSocket> = new Set();
@ -28,10 +27,6 @@ Configure({
{ {
inImports["react-original"] = inImports["react"]; inImports["react-original"] = inImports["react"];
inImports["react"] = `/>able/hmr-react.tsx`; inImports["react"] = `/>able/hmr-react.tsx`;
inImports["signals-original"] = inImports["@preact/signals"];
inImports["@preact/signals"] = `/>able/hmr-signal.tsx`;
return inImports; return inImports;
}, },
async Extra(inReq, inURL, inExt, inMap, inConfig) async Extra(inReq, inURL, inExt, inMap, inConfig)

View File

@ -1,33 +1,49 @@
import * as MIME from "https://deno.land/std@0.180.0/media_types/mod.ts"; import * as MIME from "https://deno.land/std@0.180.0/media_types/mod.ts";
import * as SWCW from "https://esm.sh/@swc/wasm-web@1.3.62"; import * as SWCW from "https://esm.sh/@swc/wasm-web@1.3.62";
import { HuntConfig, Root } from "./checker.tsx"; import { HuntConfig } from "./checker.tsx";
import CustomServe from ">able/api.tsx"; import CustomServe from ">able/api.tsx";
export const Root = new URL(`file://${Deno.cwd().replaceAll("\\", "/")}`).toString();
type DenoConfig = {imports:Record<string, string>}; type DenoConfig = {imports:Record<string, string>};
const ImportMap:DenoConfig = {imports:{}}; const ImportMap:DenoConfig = {imports:{}};
let ImportMapProxies:Record<string, string> = {};
const ImportMapReload =async()=> const ImportMapReload =async()=>
{ {
const [, {json}] = await HuntConfig(); const [, {json, path}] = await HuntConfig();
const imports = (json as DenoConfig).imports; const imports = (json as DenoConfig).imports;
if(imports) if(imports)
{ {
ImportMapProxies = {};
Object.entries(imports).forEach(([key, value])=> Object.entries(imports).forEach(([key, value])=>
{ {
// re-write deno project-relative paths (e.g. "./app.tsx") to root-relative for the browser ("/app.tsx");
if(value.startsWith("./")) if(value.startsWith("./"))
{ {
imports[key] = value.substring(1); imports[key] = value.substring(1);
} }
if(key.startsWith(">"))
if(key.startsWith(">") && key.endsWith("/"))
{ {
if(value.startsWith("./"))
{
ImportMapProxies[encodeURI(key)] = value.substring(1);
imports[key] = value.substring(1);
}
else
{
ImportMapProxies["/"+encodeURI(key)] = value;
imports[key] = "/"+key; imports[key] = "/"+key;
} }
}
}); });
ImportMap.imports = Configuration.Remap(imports, Configuration); ImportMap.imports = Configuration.Remap(imports, Configuration);
}
else
{
} }
}; };
@ -58,7 +74,7 @@ let Configuration:Configuration =
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="importmap">${JSON.stringify(inMap, null, " ")}</script> <script type="importmap">${JSON.stringify(inMap)}</script>
<script type="module"> <script type="module">
import Mount from ">able/run-browser.tsx"; import Mount from ">able/run-browser.tsx";
Mount("#app", "${inConfig.Start}"); Mount("#app", "${inConfig.Start}");
@ -106,6 +122,85 @@ export const Transpile =
ImportMapReload(); ImportMapReload();
return size; return size;
}, },
/**
* DONT USE
* Converts dynamic module imports in to static, also can resolve paths with an import map
*/
async Patch(inPath:string, inKey:string, inMap?:DenoConfig)
{
const check = this.Cache.get(inKey);
if(check)
{
return check;
}
let file, text;
try
{
file = await fetch(inPath);
text = await file.text();
}
catch(e)
{
return false;
}
const remap = inMap ? (inPath:string)=>
{
const match = inMap.imports[inPath];
if(match)
{
return match;
}
else if(inPath.includes("/"))
{
let bestKey = "";
let bestLength = 0;
Object.keys(inMap.imports).forEach((key, i, arr)=>
{
if(key.endsWith("/") && inPath.startsWith(key) && key.length > bestLength)
{
bestKey = key;
bestLength = key.length;
}
});
if(bestKey)
{
return inMap.imports[bestKey]+inPath.substring(bestKey.length);
}
}
return inPath;
}
: (inPath:string)=>inPath;
let match, regex;
let convertedBody = text;
// remap static imports
regex = /from\s+(['"`])(.*?)\1/g;
while ((match = regex.exec(text)))
{
const importStatement = match[0];
const importPath = match[2];
convertedBody = convertedBody.replace(importStatement, `from "${remap(importPath)}"`);
}
// convert dynamic imports into static (to work around deno deploy)
const staticImports = [];
regex = /(?<![\w.])import\(([^)]+)(?!import\b)\)/g;
while ((match = regex.exec(text)))
{
const importStatement = match[0];
const importPath = remap(match[1].substring(1, match[1].length-1));
const moduleName = `_dyn_${staticImports.length}` as string;
staticImports.push(`import ${moduleName} from ${importPath};`);
convertedBody = convertedBody.replace(importStatement, `Promise.resolve(${moduleName})`);
}
convertedBody = staticImports.join("\n") + convertedBody;
inKey && this.Cache.set(inKey, convertedBody);
return convertedBody;
},
async Fetch(inPath:string, inKey:string, inCheckCache=true) async Fetch(inPath:string, inKey:string, inCheckCache=true)
{ {
const check = this.Cache.get(inPath); const check = this.Cache.get(inPath);
@ -177,9 +272,21 @@ export default async()=>
// proxy imports // proxy imports
if(url.pathname.startsWith(encodeURI("/>"))) if(url.pathname.startsWith(encodeURI("/>")))
{ {
/** pathname with no leading slash */ let bestMatch="";
const clippedPath = decodeURI(url.pathname.substring(1)); for(let key in ImportMapProxies)
proxy = import.meta.resolve(clippedPath); {
if(url.pathname.startsWith(key) && key.length > bestMatch.length)
{
bestMatch = key;
}
}
if(bestMatch.length)
{
const match = ImportMapProxies[bestMatch];
const path = url.pathname.substring(bestMatch.length);
proxy = path ? match + path : Root + match;
}
} }
// allow for custom handlers // allow for custom handlers
@ -197,6 +304,7 @@ export default async()=>
// transpileable files // transpileable files
if(Transpile.Check(ext)) if(Transpile.Check(ext))
{ {
console.log("transpiling:", proxy);
const code = await Transpile.Fetch(proxy, url.pathname); const code = await Transpile.Fetch(proxy, url.pathname);
if(code) if(code)
{ {

56
things.md Normal file
View File

@ -0,0 +1,56 @@
# Outpost
## Deno Deploy
```
accept: */*
accept-encoding: gzip, br
accept-language: *
cdn-loop: deno;s=deno;d=ah40t9m8n54g
host: bad-goat-66.deno.dev
user-agent: Deno/1.34.1
```
## Deno
```
accept: */*
accept-encoding: gzip, br
accept-language: *
host: bad-goat-66.deno.dev
user-agent: Deno/1.34.3
```
## Edge
```
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
host: bad-goat-66.deno.dev
referer: https://dash.deno.com/
sec-ch-ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-dest: iframe
sec-fetch-mode: navigate
sec-fetch-site: cross-site
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.0.0
```
## Firefox
```
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.5
host: bad-goat-66.deno.dev
sec-fetch-dest: document
sec-fetch-mode: navigate
sec-fetch-site: cross-site
te: trailers
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0
```
When a requet comes in:
- if its for a transpile-able document:
- if its a request from deno:
-