diff --git a/checker.tsx b/checker.tsx index a5f1100..c44c7bd 100644 --- a/checker.tsx +++ b/checker.tsx @@ -167,9 +167,10 @@ export async function Check() const importMap = imports.json.imports as Record; 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.17.1/compat`; - importMap["react/"] = `https://esm.sh/preact@10.17.1/compat/`; + importMap["react"] = `https://esm.sh/preact@10.18.1/compat`; + importMap["react/"] = `https://esm.sh/preact@10.18.1/compat/`; importMap["@preact/signals"] = `https://esm.sh/@preact/signals@1.2.1`; + importMap["@twind/core"] = `https://esm.sh/@twind/core@1.1.3`; importMap[">able/"] = `${RootHost}`; if(!importMap[">able/app.tsx"]) { @@ -195,18 +196,30 @@ export async function Check() const confTasks = (config.json.tasks || {}) as Record; config.json.tasks = {...confTasks, ...tasks}; - const options = + const optionsRequired = { - "lib": ["deno.window", "dom", "dom.asynciterable"], + "lib": ["deno.window", "dom", "dom.iterable", "dom.asynciterable"], "jsx": "react-jsx", "jsxImportSource": "react" } - const compOpts = config.json.compilerOptions as Record || {}; - const compLib:string[] = compOpts.lib as string[] || []; - compOpts.jsx = options.jsx; - compOpts.jsxImportSource = options.jsxImportSource; - compOpts.lib = [...compLib, ...options.lib]; - config.json.compilerOptions = compOpts; + const optionsCurrent = config.json.compilerOptions as Record || {}; + //const compLib:string[] = compOpts.lib as string[] || []; + + if(!optionsCurrent.lib) + { + optionsCurrent.lib = []; + } + optionsRequired.lib.forEach(s=> + { + if(!optionsCurrent.lib.includes(s)) + { + (optionsCurrent.lib as string[]).push(s); + } + }); + + optionsCurrent.jsx = optionsRequired.jsx; + optionsCurrent.jsxImportSource = optionsRequired.jsxImportSource; + config.json.compilerOptions = optionsCurrent; await bake(imports); await bake(config); diff --git a/cli.tsx b/cli.tsx index 13b9f3e..0bc3741 100644 --- a/cli.tsx +++ b/cli.tsx @@ -99,8 +99,8 @@ if(arg._.length) } case "cloud" : { - let useToken = await collect("DENO_DEPLOY_TOKEN", arg, env); - let useProject = await collect("DENO_DEPLOY_PROJECT", arg, env); + const useToken = await collect("DENO_DEPLOY_TOKEN", arg, env); + const useProject = await collect("DENO_DEPLOY_PROJECT", arg, env); let scanProd:string[]|string|null = prompt(`Do you want to deploy to *production*?`); if(scanProd) @@ -113,7 +113,7 @@ if(arg._.length) scanProd = []; } - await SubProcess([ + const command = [ "run", "-A", "--no-lock", @@ -123,9 +123,13 @@ if(arg._.length) `--project=${useProject}`, `--token=${useToken}`, `--import-map=${imports.path}`, - RootHost+"run.tsx", + `--exclude=.*,.*/,`, ...scanProd, - ...Deno.args]); + RootHost+"run.tsx"]; + + await SubProcess(command); + + break; } case "upgrade" : { diff --git a/deno.jsonc b/deno.jsonc index 95b17a3..1837ba3 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,10 +1,13 @@ { "imports": { "react": "https://esm.sh/preact@10.17.1/compat", + "react-original": "https://esm.sh/preact@10.17.1/compat", "react/": "https://esm.sh/preact@10.17.1/compat/", + "@preact/signals": "https://esm.sh/@preact/signals@1.2.1", + "signals-original": "https://esm.sh/@preact/signals@1.2.1", + "@twind/core": "https://esm.sh/@twind/core@1.1.3", ">able/": "http://localhost:4507/", - ">able/app.tsx": "./app.tsx", - "@preact/signals": "https://esm.sh/@preact/signals@1.2.1" + ">able/app.tsx": "./app.tsx" }, "tasks": { "check": "deno run -A --no-lock http://localhost:4507/cli.tsx check", @@ -12,7 +15,7 @@ "debug": "deno run -A --no-lock http://localhost:4507/cli.tsx debug", "serve": "deno run -A --no-lock http://localhost:4507/cli.tsx serve", "cloud": "deno run -A --no-lock http://localhost:4507/cli.tsx cloud", - "install": "deno install -A -r -f http://localhost:4507/cli.tsx" + "install": "deno install -A -r -f -n able http://localhost:4507/cli.tsx" }, "compilerOptions": { "jsx": "react-jsx", diff --git a/hmr-listen.tsx b/hmr-listen.tsx index 4ec21be..cd029b3 100644 --- a/hmr-listen.tsx +++ b/hmr-listen.tsx @@ -1,4 +1,5 @@ -import { type StateCapture } from "./hmr-react.tsx"; +import { HMR } from "./hmr-react.tsx"; +import { GroupSignal, GroupSignalHook } from "./hmr-signal.tsx"; const FileListeners = new Map() as Mapvoid>>; export const FileListen =(inPath:string, inHandler:()=>void)=> @@ -13,58 +14,18 @@ Socket.addEventListener('message', async(event:{data:string})=> { // When a file changes, dynamically re-import it to get the updated members // send the updated members to any listeners for that file + + GroupSignal.reset(); + const reImport = await import(document.location.origin+event.data+"?reload="+Math.random()); FileListeners.get(event.data)?.forEach(reExport=>reExport(reImport)); + + GroupSignal.swap(); + + GroupSignalHook.reset(); HMR.update(); + GroupSignalHook.reset(); + }); Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")}) -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 with whatever is in statesOld -- statesNew is moved into *statesOld* -- statesNew is cleared. - -*/ - -const HMR = -{ - reloads:1, - RegisteredComponents: new Map() as Mapvoid>, - statesNew: new Map() as Map, - statesOld: new Map() as Map, - 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}; \ No newline at end of file +const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); \ No newline at end of file diff --git a/hmr-react.tsx b/hmr-react.tsx index b47bb8a..9f1f6a9 100644 --- a/hmr-react.tsx +++ b/hmr-react.tsx @@ -1,5 +1,51 @@ 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 with whatever is in statesOld +- statesNew is moved into *statesOld* +- statesNew is cleared. + +*/ +export const HMR = +{ + reloads:1, + RegisteredComponents: new Map() as Mapvoid>, + statesNew: new Map() as Map, + statesOld: new Map() as Map, + 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 export type StateCapture = {state:StateType, set:ReactParts.StateUpdater, reload:number}; diff --git a/hmr-signal.tsx b/hmr-signal.tsx new file mode 100644 index 0000000..28703a9 --- /dev/null +++ b/hmr-signal.tsx @@ -0,0 +1,46 @@ +import * as SignalsParts from "signals-original"; + +type Entry = [signal:SignalsParts.Signal, initArg:T]; + +function ProxyGroup(inFunc:(initArg:T)=>SignalsParts.Signal) +{ + let recordEntry:Entry[] = []; + let recordEntryNew:Entry[] = []; + let recordIndex = 0; + const reset =()=> recordIndex = 0; + const swap =()=> + { + recordEntry = recordEntryNew; + recordEntryNew = [] as Entry[]; + }; + const proxy =(arg:T)=> + { + const lookupOld = recordEntry[recordIndex]; + if(lookupOld && 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}; \ No newline at end of file diff --git a/run-browser.tsx b/run-browser.tsx index 018f55d..cf064c4 100644 --- a/run-browser.tsx +++ b/run-browser.tsx @@ -1,5 +1,5 @@ import React from "react"; -import * as TW from "https://esm.sh/v126/@twind/core@1.1.3/es2022/core.mjs"; +import * as TW from "@twind/core"; 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"; diff --git a/run-local.tsx b/run-local.tsx index 94553e5..a845712 100644 --- a/run-local.tsx +++ b/run-local.tsx @@ -27,6 +27,10 @@ Configure({ { inImports["react-original"] = inImports["react"]; inImports["react"] = `/>able/hmr-react.tsx`; + + inImports["signals-original"] = inImports["@preact/signals"]; + inImports["@preact/signals"] = `/>able/hmr-signal.tsx`; + return inImports; }, async Extra(inReq, inURL, inExt, inMap, inConfig) diff --git a/run-serve.tsx b/run-serve.tsx index 74e81a5..cb61263 100644 --- a/run-serve.tsx +++ b/run-serve.tsx @@ -74,7 +74,7 @@ let Configuration:Configuration =
- +