dreaded-fixes #7
							
								
								
									
										33
									
								
								checker.tsx
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								checker.tsx
									
									
									
									
									
								
							@ -167,9 +167,10 @@ export async function Check()
 | 
			
		||||
            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")); 
 | 
			
		||||
 | 
			
		||||
            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<string, string>;
 | 
			
		||||
            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<string, string|string[]> || {};
 | 
			
		||||
            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<string, string|string[]> || {};
 | 
			
		||||
            //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);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								cli.tsx
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								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" :
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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 Map<string, Array<(module:unknown)=>void>>;
 | 
			
		||||
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<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};
 | 
			
		||||
const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000);
 | 
			
		||||
@ -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<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 StateCapture = {state:StateType, set:ReactParts.StateUpdater<StateType>, reload:number};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										46
									
								
								hmr-signal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								hmr-signal.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
import * as SignalsParts from "signals-original";
 | 
			
		||||
 | 
			
		||||
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 && 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};
 | 
			
		||||
@ -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";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -74,7 +74,7 @@ let Configuration:Configuration =
 | 
			
		||||
                </head>
 | 
			
		||||
                <body>
 | 
			
		||||
                    <div id="app"></div>
 | 
			
		||||
                    <script type="importmap">${JSON.stringify(inMap)}</script>
 | 
			
		||||
                    <script type="importmap">${JSON.stringify(inMap, null, " ")}</script>
 | 
			
		||||
                    <script type="module">
 | 
			
		||||
                        import Mount from ">able/run-browser.tsx";
 | 
			
		||||
                        Mount("#app", "${inConfig.Start}");
 | 
			
		||||
@ -122,85 +122,6 @@ export const Transpile =
 | 
			
		||||
        ImportMapReload();
 | 
			
		||||
        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)
 | 
			
		||||
    {
 | 
			
		||||
        const check = this.Cache.get(inPath);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user