From 4441a6d8f243da973fa7d2da1dc8b99a34c3d188 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 28 Feb 2025 10:03:33 -0500 Subject: [PATCH 1/7] idk --- .vscode/launch.json | 33 +++++++++++++++++++++++++ mod.ts | 59 ++++++++++++++++++++++++++++++++++----------- serve.tsx | 3 +++ what/entry.ts | 9 +++++++ what/include.ts | 5 ++++ what/index.ts | 19 +++++++++++++++ 6 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 what/entry.ts create mode 100644 what/include.ts create mode 100644 what/index.ts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2247aa1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + // 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": [ + { + "name": "Attach", + "port": 9229, + "request": "attach", + "skipFiles": [ + "/**" + ], + "type": "node" + }, + { + "request": "launch", + "name": "Launch Program", + "type": "node", + "program": "${workspaceFolder}/main.ts", + "cwd": "${workspaceFolder}", + "env": {}, + "runtimeExecutable": "C:\\Users\\strowbridge\\.deno\\bin\\deno.EXE", + "runtimeArgs": [ + "run", + "--unstable", + "--inspect-wait", + "--allow-all" + ], + "attachSimplePort": 9229 + } + ] +} \ No newline at end of file diff --git a/mod.ts b/mod.ts index a957ea1..8ad3c6f 100644 --- a/mod.ts +++ b/mod.ts @@ -1,29 +1,59 @@ -import * as ESBuild from "https://deno.land/x/esbuild@v0.19.2/wasm.js"; +import * as ESBuild from "https://deno.land/x/esbuild@v0.25.0/wasm.js"; import * as Mapper from "https://esm.sh/esbuild-plugin-importmaps@1.0.0"; // https://github.com/andstellar/esbuild-plugin-importmaps import Introspect from "./introspect.ts"; +const REG = { + pathRel: RegExp("/^(\.\/|\.\.\/).*/"), + pathAbs: RegExp("^\/.*"), + pathHTTP: RegExp("/^https?:\/\//"), + wildcard: RegExp("/.*/") +}; + + const prefix = "/_dot_importer_/"; const resolvePlugin =(fullPathDir:string):ESBuild.Plugin=>({ name: "resolve-plugin", setup(build) { - build.onResolve({ filter: /^(\.\/|\.\.\/).*/ }, (args)=> + + build.onResolve({ filter: REG.pathHTTP }, args => { + console.log(`LOCAL RESOLVE`, args); + return { path: args.path, namespace:"remote", pluginData:{origin:new URL(args.path).origin} }; + }); + build.onLoad({ filter: REG.wildcard, namespace:"remote" }, async(args)=> { + + + + console.log(`REMOTE LOADING`, {args}, "\n"); + const contents = await fetch(args.path).then(r=>r.text()); + + return { contents, loader: `tsx` }; + }); + + + build.onResolve({ filter: REG.pathRel }, (args)=> { let resolveRoot = args.importer||fullPathDir; if(resolveRoot.startsWith(prefix)) { resolveRoot = resolveRoot.substring(prefix.length); } + + const url = new URL(args.path, resolveRoot).href; const output:ESBuild.OnResolveResult = { - path:prefix + new URL(args.path, resolveRoot).href, - namespace:"http", + path:prefix + url, + namespace:"local", } + console.log(`LOCAL RESOLVE`, {args, resolveRoot, output}, "\n"); return output; }); - build.onLoad({ filter: /.*/, namespace:"http" }, async(args)=> { + build.onLoad({ filter: REG.wildcard, namespace:"local" }, async(args)=> { + const fetchPath = args.path.substring(prefix.length); - console.log("fetch path", fetchPath); - const result = await fetch(fetchPath); - const contents = await result.text(); + + console.log(`LOCAL LOADING`, {args, fetchPath}, "\n"); + + const contents = await fetch(fetchPath).then(r=>r.text()); + return { contents, loader: `tsx` }; }); }, @@ -32,14 +62,15 @@ const resolvePlugin =(fullPathDir:string):ESBuild.Plugin=>({ await ESBuild.initialize({ worker: false }); export type ImportMap = Parameters[0]; 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 importMap An object to act as the import map ({imports:Record}). If this is left blank, a configuration will be scanned for in the "directory" - * @returns build result + * @param {string} directory Full file:// or http(s):// path to the directory containing assets you want to build (needed to resolve relative imports) + * @param {ESBuild.BuildOptions} buildOptions ESBuild "build" options (will be merged with "reasonable defaults") for docs: https://esbuild.github.io/api/#general-options + * @param {ImportMap|null} importMap An object to act as the import map ({imports:Record}). If this is left blank, a configuration will be scanned for in the "directory" + * @returns {Promise>} build result */ -export default async function(directory:string, buildOptions={} as BuildOptions, importMap?:ImportMap):Promise> +export default async function(directory, buildOptions={}, importMap) { if(!importMap) { @@ -56,7 +87,7 @@ export default async function(directory:string, buildOptions={} as BuildOptions, ...buildOptions, plugins: [ resolvePlugin(directory), - Mapper.importmapPlugin(importMap) as ESBuild.Plugin, + //Mapper.importmapPlugin(importMap) as ESBuild.Plugin, ...buildOptions.plugins||[] ] }; diff --git a/serve.tsx b/serve.tsx index 283ec83..501c0cf 100644 --- a/serve.tsx +++ b/serve.tsx @@ -105,6 +105,9 @@ async function serve(settings:ServeArgs):Promise const url = new URL(req.url); const {path, type} = PathExtensionType(url.pathname); + + console.log(req.url); + const headers = {"content-type":type||"", ETag, "Cache-Control":"max-age=3600"} // serve javascript diff --git a/what/entry.ts b/what/entry.ts new file mode 100644 index 0000000..d7c60d4 --- /dev/null +++ b/what/entry.ts @@ -0,0 +1,9 @@ +import Message from "./include.ts"; + +type Person = {name:string, age:number}; + +const me:Person = {name:"seth", age:41}; + +console.log(Message); + +export default me; \ No newline at end of file diff --git a/what/include.ts b/what/include.ts new file mode 100644 index 0000000..b0e3f76 --- /dev/null +++ b/what/include.ts @@ -0,0 +1,5 @@ +import * as LO from "https://esm.sh/lodash@4.17.21"; + +console.log(LO); + +export default "HELLO"; \ No newline at end of file diff --git a/what/index.ts b/what/index.ts new file mode 100644 index 0000000..6c66f88 --- /dev/null +++ b/what/index.ts @@ -0,0 +1,19 @@ +import Bundle from "../mod.ts"; +const {outputFiles} = await Bundle( + // directory to work from + import.meta.resolve('./'), + + // ESBuild configuration (entry points are relative to "directory"): + {entryPoints:["./entry.ts"]}, + + // import map (if omitted, will scan for a deno configuration within "directory" and use that) + {"imports": { + "react": "https://esm.sh/preact@10.22.0/compat", + "react/": "https://esm.sh/preact@10.22.0/compat/" + }} +); + +for(const item of outputFiles){ + // do something with the output... + console.log(item.text); +} From 6a0a11024293721fb6cce65f0c2b2a5f679e7bde Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 28 Feb 2025 10:38:40 -0500 Subject: [PATCH 2/7] fixes still broken --- mod.ts | 55 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/mod.ts b/mod.ts index 8ad3c6f..81fea1a 100644 --- a/mod.ts +++ b/mod.ts @@ -2,35 +2,13 @@ import * as ESBuild from "https://deno.land/x/esbuild@v0.25.0/wasm.js"; import * as Mapper from "https://esm.sh/esbuild-plugin-importmaps@1.0.0"; // https://github.com/andstellar/esbuild-plugin-importmaps import Introspect from "./introspect.ts"; -const REG = { - pathRel: RegExp("/^(\.\/|\.\.\/).*/"), - pathAbs: RegExp("^\/.*"), - pathHTTP: RegExp("/^https?:\/\//"), - wildcard: RegExp("/.*/") -}; - const prefix = "/_dot_importer_/"; const resolvePlugin =(fullPathDir:string):ESBuild.Plugin=>({ name: "resolve-plugin", setup(build) { - build.onResolve({ filter: REG.pathHTTP }, args => { - console.log(`LOCAL RESOLVE`, args); - return { path: args.path, namespace:"remote", pluginData:{origin:new URL(args.path).origin} }; - }); - build.onLoad({ filter: REG.wildcard, namespace:"remote" }, async(args)=> { - - - - console.log(`REMOTE LOADING`, {args}, "\n"); - const contents = await fetch(args.path).then(r=>r.text()); - - return { contents, loader: `tsx` }; - }); - - - build.onResolve({ filter: REG.pathRel }, (args)=> + build.onResolve({ /*path relative*/ filter: /^(\.\/|\.\.\/).*/ }, (args)=> { let resolveRoot = args.importer||fullPathDir; if(resolveRoot.startsWith(prefix)) @@ -46,7 +24,7 @@ const resolvePlugin =(fullPathDir:string):ESBuild.Plugin=>({ console.log(`LOCAL RESOLVE`, {args, resolveRoot, output}, "\n"); return output; }); - build.onLoad({ filter: REG.wildcard, namespace:"local" }, async(args)=> { + build.onLoad({ /*wildcard*/ filter: /.*/, namespace:"local" }, async(args)=> { const fetchPath = args.path.substring(prefix.length); @@ -56,6 +34,35 @@ const resolvePlugin =(fullPathDir:string):ESBuild.Plugin=>({ return { contents, loader: `tsx` }; }); + + + build.onResolve({/* path abs */ filter:/^\/.*/}, args=>{ + console.log(`ABS RESOLVE`, {args}, "\n"); + + return { + path:args.path, + namespace:"ABS", + } + }); + build.onLoad({/*wildcard*/ filter: /.*/, namespace:"ABS"}, async(args)=>{ + console.log(`ABS LOADING`, {args}, "\n"); + return { contents:"export default 'IDK';", loader: `tsx` }; + }); + + + build.onResolve({ /* path HTTP */ filter: /^https?:\/\// }, args => { + console.log(`REMOTE RESOLVE`, args); + return { path: args.path, namespace:"REMOTE", pluginData:{origin:new URL(args.path).origin} }; + }); + build.onLoad({ /*wildcard*/ filter: /.*/, namespace:"REMOTE" }, async(args)=> { + console.log(`REMOTE LOADING`, {args}, "\n"); + + const contents = await fetch(args.path).then(r=>r.text()); + + return { contents, loader: `tsx` }; + }); + + }, }); From a57988ada31b6d9adfd18f580bbfe9e5267b2059 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 28 Feb 2025 11:30:02 -0500 Subject: [PATCH 3/7] it works?? --- mod.ts | 74 ++++++++++++++----------------------------------- what/include.ts | 4 +-- 2 files changed, 23 insertions(+), 55 deletions(-) diff --git a/mod.ts b/mod.ts index 81fea1a..7efe48d 100644 --- a/mod.ts +++ b/mod.ts @@ -2,67 +2,35 @@ import * as ESBuild from "https://deno.land/x/esbuild@v0.25.0/wasm.js"; import * as Mapper from "https://esm.sh/esbuild-plugin-importmaps@1.0.0"; // https://github.com/andstellar/esbuild-plugin-importmaps import Introspect from "./introspect.ts"; - -const prefix = "/_dot_importer_/"; const resolvePlugin =(fullPathDir:string):ESBuild.Plugin=>({ name: "resolve-plugin", setup(build) { - build.onResolve({ /*path relative*/ filter: /^(\.\/|\.\.\/).*/ }, (args)=> - { - let resolveRoot = args.importer||fullPathDir; - if(resolveRoot.startsWith(prefix)) - { - resolveRoot = resolveRoot.substring(prefix.length); - } + build.onResolve( {/* `/`, `./`, and `../` */ filter:/^(\/|\.\/|\.\.\/).*/}, args=>{ + const resolveRoot = args.importer||fullPathDir; const url = new URL(args.path, resolveRoot).href; - const output:ESBuild.OnResolveResult = { - path:prefix + url, - namespace:"local", - } - console.log(`LOCAL RESOLVE`, {args, resolveRoot, output}, "\n"); - return output; - }); - build.onLoad({ /*wildcard*/ filter: /.*/, namespace:"local" }, async(args)=> { - const fetchPath = args.path.substring(prefix.length); - - console.log(`LOCAL LOADING`, {args, fetchPath}, "\n"); - - const contents = await fetch(fetchPath).then(r=>r.text()); - - return { contents, loader: `tsx` }; - }); - - - build.onResolve({/* path abs */ filter:/^\/.*/}, args=>{ - console.log(`ABS RESOLVE`, {args}, "\n"); - - return { - path:args.path, - namespace:"ABS", - } - }); - build.onLoad({/*wildcard*/ filter: /.*/, namespace:"ABS"}, async(args)=>{ - console.log(`ABS LOADING`, {args}, "\n"); - return { contents:"export default 'IDK';", loader: `tsx` }; - }); - - - build.onResolve({ /* path HTTP */ filter: /^https?:\/\// }, args => { - console.log(`REMOTE RESOLVE`, args); - return { path: args.path, namespace:"REMOTE", pluginData:{origin:new URL(args.path).origin} }; - }); - build.onLoad({ /*wildcard*/ filter: /.*/, namespace:"REMOTE" }, async(args)=> { - console.log(`REMOTE LOADING`, {args}, "\n"); - - const contents = await fetch(args.path).then(r=>r.text()); - - return { contents, loader: `tsx` }; - }); + const out = { path:url, namespace:"FULLPATH" }; + console.log(`SLASHPATH=>FULLPATH RESOLVE`, {args, out}, "\n"); + return out; + } ); + build.onResolve( {/* `file://`, `http://`, and `https://` */ filter:/^(file:\/\/|http:\/\/|https:\/\/).*/}, args=>{ + const out = { path:args.path, namespace:"FULLPATH" }; + console.log(`FULLPATH RESOLVE`, {args, out}, "\n"); + return out; + } ); + build.onLoad( + {/* `file://`, `http://`, and `https://` */ filter:/^(file:\/\/|http:\/\/|https:\/\/).*/}, + async(args)=> + { + const contents = await fetch(args.path).then(r=>r.text()); + const out = { contents, loader: `tsx` }; + console.log(`FULLPATH LOAD`, {args, out:{...out, contents:contents.substring(0, 100)}}, "\n"); + return out; + } ); }, }); @@ -94,7 +62,7 @@ export default async function(directory, buildOptions={}, importMap) ...buildOptions, plugins: [ resolvePlugin(directory), - //Mapper.importmapPlugin(importMap) as ESBuild.Plugin, + Mapper.importmapPlugin(importMap) as ESBuild.Plugin, ...buildOptions.plugins||[] ] }; diff --git a/what/include.ts b/what/include.ts index b0e3f76..79dff95 100644 --- a/what/include.ts +++ b/what/include.ts @@ -1,5 +1,5 @@ -import * as LO from "https://esm.sh/lodash@4.17.21"; +import * as React from "react"; -console.log(LO); +console.log(React); export default "HELLO"; \ No newline at end of file From 71684968f4a7e904ea95e4cae15a4d629825a00e Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 28 Feb 2025 13:23:17 -0500 Subject: [PATCH 4/7] misc edits --- import_map_resolver.js | 83 ++++++++++++++++++++++++++++++++++++++++++ mod.ts | 2 +- what/entry.ts | 5 ++- what/include.ts | 5 --- what/include_a.ts | 6 +++ what/include_b.ts | 1 + what/index.ts | 5 ++- 7 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 import_map_resolver.js delete mode 100644 what/include.ts create mode 100644 what/include_a.ts create mode 100644 what/include_b.ts diff --git a/import_map_resolver.js b/import_map_resolver.js new file mode 100644 index 0000000..d5aea4e --- /dev/null +++ b/import_map_resolver.js @@ -0,0 +1,83 @@ +/** + * Resolves a module specifier against an import map + * @param {string} specifier - The module specifier to resolve (bare specifier or absolute URL) + * @param {Object} importMap - The import map object containing imports and scopes + * @param {string} baseURL - The base URL for context (especially for scopes) + * @returns {string} The resolved URL + */ +export default function resolveImportMap(specifier, importMap, baseURL) { + + // Check for exact matches first (most common case for bare specifiers) + const lookup = importMap.imports[specifier]; + if (lookup) { return lookup; } + + const Scanner =(obj)=> + { + // Check for path prefix matches (longest first) + const keys = Object.keys(obj) + .filter(key => key.endsWith('/')) + .sort((a, b) => b.length - a.length); + for (const key of keys) + { + if (specifier.startsWith(key)) + { + const remainder = specifier.slice(key.length); + return obj[key] + remainder; + } + } + } + + const scan = Scanner(importMap.imports); + if (scan) {return scan} + + // Handle scopes if available + if (importMap.scopes) { + const scopeKeys = Object.keys(importMap.scopes).sort((a, b) => b.length - a.length); + for (const scopeKey of scopeKeys) + { + if (specifier.startsWith(scopeKey)) + { + const scopeImports = importMap.scopes[scopeKey]; + const scan = Scanner(scopeImports); + if (scan) {return scan} + } + } + } + + return specifier; + } + + // Example usage for bare specifiers: + function demonstrateOptimizedResolver() { + const importMap = { + "imports": { + "lodash": "https://cdn.skypack.dev/lodash", + "react": "https://cdn.skypack.dev/react", + "lib/": "/node_modules/lib/", + "components/": "/components/" + }, + "scopes": { + "/admin/": { + "components/": "/admin-components/", + "admin-utils": "scoped!/js/admin-utils.js" + } + } + }; + + // Examples with bare specifiers + console.log("Resolving 'lodash':", resolveImportMap("lodash", importMap, "https://example.com/admin/dashboard/")); + console.log("Resolving 'components/button':", resolveImportMap("components/button", importMap, "https://example.com/admin/dashboard/")); + + // Example with scope + console.log("Resolving 'admin-utils':", + resolveImportMap("admin-utils", importMap, "https://example.com/admin/dashboard/")); + + console.log("Resolving '/admin/admin-utils':", + resolveImportMap("admin-utils", importMap, "https://example.com/admin/dashboard/")); + + // Example with URL + console.log("Resolving 'https://example.com/external.js':", + resolveImportMap("https://example.com/external.js", importMap, "https://example.com/admin/dashboard/")); + } + + demonstrateOptimizedResolver(); diff --git a/mod.ts b/mod.ts index 7efe48d..629de59 100644 --- a/mod.ts +++ b/mod.ts @@ -61,8 +61,8 @@ export default async function(directory, buildOptions={}, importMap) jsxImportSource: "react", ...buildOptions, plugins: [ - resolvePlugin(directory), Mapper.importmapPlugin(importMap) as ESBuild.Plugin, + resolvePlugin(directory), ...buildOptions.plugins||[] ] }; diff --git a/what/entry.ts b/what/entry.ts index d7c60d4..6ac258d 100644 --- a/what/entry.ts +++ b/what/entry.ts @@ -1,9 +1,10 @@ -import Message from "./include.ts"; +import React from "react"; +import Message from "./include_a.ts"; type Person = {name:string, age:number}; const me:Person = {name:"seth", age:41}; -console.log(Message); +console.log(Message, React); export default me; \ No newline at end of file diff --git a/what/include.ts b/what/include.ts deleted file mode 100644 index 79dff95..0000000 --- a/what/include.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as React from "react"; - -console.log(React); - -export default "HELLO"; \ No newline at end of file diff --git a/what/include_a.ts b/what/include_a.ts new file mode 100644 index 0000000..8e9c0a9 --- /dev/null +++ b/what/include_a.ts @@ -0,0 +1,6 @@ +import * as B from "./include_b.ts"; +import * as React from "react"; + +console.log(React, B); + +export default "HELLO"; \ No newline at end of file diff --git a/what/include_b.ts b/what/include_b.ts new file mode 100644 index 0000000..7c645e4 --- /dev/null +++ b/what/include_b.ts @@ -0,0 +1 @@ +export default {}; \ No newline at end of file diff --git a/what/index.ts b/what/index.ts index 6c66f88..77b200b 100644 --- a/what/index.ts +++ b/what/index.ts @@ -4,10 +4,11 @@ const {outputFiles} = await Bundle( import.meta.resolve('./'), // ESBuild configuration (entry points are relative to "directory"): - {entryPoints:["./entry.ts"]}, + {entryPoints:["entry"]}, // import map (if omitted, will scan for a deno configuration within "directory" and use that) {"imports": { + "entry": "./entry.ts", "react": "https://esm.sh/preact@10.22.0/compat", "react/": "https://esm.sh/preact@10.22.0/compat/" }} @@ -15,5 +16,5 @@ const {outputFiles} = await Bundle( for(const item of outputFiles){ // do something with the output... - console.log(item.text); + console.log(item.text.substring(0, 200)); } From f15e3928062f7cab0b45965df6bd61e82a21ace5 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 28 Feb 2025 13:47:21 -0500 Subject: [PATCH 5/7] cleanup 1 --- import_map_resolver.js | 83 ------------------------------------ mod.ts | 96 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 78 insertions(+), 101 deletions(-) delete mode 100644 import_map_resolver.js diff --git a/import_map_resolver.js b/import_map_resolver.js deleted file mode 100644 index d5aea4e..0000000 --- a/import_map_resolver.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Resolves a module specifier against an import map - * @param {string} specifier - The module specifier to resolve (bare specifier or absolute URL) - * @param {Object} importMap - The import map object containing imports and scopes - * @param {string} baseURL - The base URL for context (especially for scopes) - * @returns {string} The resolved URL - */ -export default function resolveImportMap(specifier, importMap, baseURL) { - - // Check for exact matches first (most common case for bare specifiers) - const lookup = importMap.imports[specifier]; - if (lookup) { return lookup; } - - const Scanner =(obj)=> - { - // Check for path prefix matches (longest first) - const keys = Object.keys(obj) - .filter(key => key.endsWith('/')) - .sort((a, b) => b.length - a.length); - for (const key of keys) - { - if (specifier.startsWith(key)) - { - const remainder = specifier.slice(key.length); - return obj[key] + remainder; - } - } - } - - const scan = Scanner(importMap.imports); - if (scan) {return scan} - - // Handle scopes if available - if (importMap.scopes) { - const scopeKeys = Object.keys(importMap.scopes).sort((a, b) => b.length - a.length); - for (const scopeKey of scopeKeys) - { - if (specifier.startsWith(scopeKey)) - { - const scopeImports = importMap.scopes[scopeKey]; - const scan = Scanner(scopeImports); - if (scan) {return scan} - } - } - } - - return specifier; - } - - // Example usage for bare specifiers: - function demonstrateOptimizedResolver() { - const importMap = { - "imports": { - "lodash": "https://cdn.skypack.dev/lodash", - "react": "https://cdn.skypack.dev/react", - "lib/": "/node_modules/lib/", - "components/": "/components/" - }, - "scopes": { - "/admin/": { - "components/": "/admin-components/", - "admin-utils": "scoped!/js/admin-utils.js" - } - } - }; - - // Examples with bare specifiers - console.log("Resolving 'lodash':", resolveImportMap("lodash", importMap, "https://example.com/admin/dashboard/")); - console.log("Resolving 'components/button':", resolveImportMap("components/button", importMap, "https://example.com/admin/dashboard/")); - - // Example with scope - console.log("Resolving 'admin-utils':", - resolveImportMap("admin-utils", importMap, "https://example.com/admin/dashboard/")); - - console.log("Resolving '/admin/admin-utils':", - resolveImportMap("admin-utils", importMap, "https://example.com/admin/dashboard/")); - - // Example with URL - console.log("Resolving 'https://example.com/external.js':", - resolveImportMap("https://example.com/external.js", importMap, "https://example.com/admin/dashboard/")); - } - - demonstrateOptimizedResolver(); diff --git a/mod.ts b/mod.ts index 629de59..edfc803 100644 --- a/mod.ts +++ b/mod.ts @@ -1,8 +1,65 @@ import * as ESBuild from "https://deno.land/x/esbuild@v0.25.0/wasm.js"; -import * as Mapper from "https://esm.sh/esbuild-plugin-importmaps@1.0.0"; // https://github.com/andstellar/esbuild-plugin-importmaps -import Introspect from "./introspect.ts"; -const resolvePlugin =(fullPathDir:string):ESBuild.Plugin=>({ +/** + * Resolves a module specifier against an import map + * @param {string} specifier - The module specifier to resolve (bare specifier or absolute URL) + * @param {Object} importMap - The import map object containing imports and scopes + * @param {string} baseURL - The base URL for context (especially for scopes) + * @returns {string} The resolved URL + */ +function resolveImportMap(specifier, importMap, baseURL) { + + // Check for prefix matches in the main imports + const result = checkPrefixMatch(specifier, importMap.imports); + if (result) { + return result + } + + // First check scopes that match the baseURL (scope applies based on baseURL, not specifier) + if (importMap.scopes) { + const scopeKeys = Object.keys(importMap.scopes).sort((a, b) => b.length - a.length); + + for (const scopeKey of scopeKeys) { + // Convert scope key to absolute URL and check if baseURL starts with it + const scopeURL = new URL(scopeKey, baseURL).href; + + if (baseURL.startsWith(scopeURL)) { + const scopeImports = importMap.scopes[scopeKey]; + + // Check for prefix match in the scope + const result = checkPrefixMatch(specifier, scopeImports); + if (result) { + return result; + } + } + } + } +} + +/** + * Helper function to check for prefix matches + * @param {string} specifier - The specifier to check + * @param {Object} mappings - Object with prefix mappings + * @returns {string|null} The resolved path or null if no match + */ +function checkPrefixMatch(specifier, mappings) { + + const check = mappings[specifier]; + if(check){ return check; } + + const prefixes = Object.keys(mappings) + .filter(key => key.endsWith('/') && specifier.startsWith(key)) + .sort((a, b) => b.length - a.length); + + for (const prefix of prefixes) { + const remainder = specifier.slice(prefix.length); + return mappings[prefix] + remainder; + } + + return null; +} + +const resolvePlugin =(fullPathDir:string, importMap):ESBuild.Plugin=>({ name: "resolve-plugin", setup(build) { @@ -16,11 +73,19 @@ const resolvePlugin =(fullPathDir:string):ESBuild.Plugin=>({ return out; } ); - build.onResolve( {/* `file://`, `http://`, and `https://` */ filter:/^(file:\/\/|http:\/\/|https:\/\/).*/}, args=>{ - const out = { path:args.path, namespace:"FULLPATH" }; - console.log(`FULLPATH RESOLVE`, {args, out}, "\n"); - return out; - } ); + build.onResolve({filter:/.*/}, args=>{ + + const check = resolveImportMap(args.path, importMap, args.importer||fullPathDir); + console.log("pth??", check); + if(check) + { + const resolveRoot = args.importer||fullPathDir; + const out = { path:new URL(check, resolveRoot).href, namespace:"FULLPATH" }; + console.log(`IMPORTMAP RESOLVE`, {args, out}, "\n"); + return out; + } + + }) build.onLoad( {/* `file://`, `http://`, and `https://` */ filter:/^(file:\/\/|http:\/\/|https:\/\/).*/}, @@ -35,22 +100,18 @@ const resolvePlugin =(fullPathDir:string):ESBuild.Plugin=>({ }); await ESBuild.initialize({ worker: false }); -export type ImportMap = Parameters[0]; +export type ImportMap = {imports?:Record, scopes?:Record>} export type BuildOptions = ESBuild.BuildOptions; /** * * @param {string} directory Full file:// or http(s):// path to the directory containing assets you want to build (needed to resolve relative imports) - * @param {ESBuild.BuildOptions} buildOptions ESBuild "build" options (will be merged with "reasonable defaults") for docs: https://esbuild.github.io/api/#general-options - * @param {ImportMap|null} importMap An object to act as the import map ({imports:Record}). If this is left blank, a configuration will be scanned for in the "directory" + * @param {ESBuild.BuildOptions} [buildOptions={}] ESBuild "build" options (will be merged with "reasonable defaults") for docs: https://esbuild.github.io/api/#general-options + * @param {ImportMap|null} [importMap={}] An object to act as the import map ({imports:Record}). If this is left blank, a configuration will be scanned for in the "directory" * @returns {Promise>} build result */ -export default async function(directory, buildOptions={}, importMap) +export default async function Build(directory, buildOptions={}, importMap = {}) { - if(!importMap) - { - importMap = await Introspect(directory) as ImportMap - } console.log("using import map", importMap); const configuration:ESBuild.BuildOptions = { entryPoints: ["entry"], @@ -61,8 +122,7 @@ export default async function(directory, buildOptions={}, importMap) jsxImportSource: "react", ...buildOptions, plugins: [ - Mapper.importmapPlugin(importMap) as ESBuild.Plugin, - resolvePlugin(directory), + resolvePlugin(directory, importMap), ...buildOptions.plugins||[] ] }; From 296516b8442ee285834d91cba2353cca05d59742 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 28 Feb 2025 16:39:49 -0500 Subject: [PATCH 6/7] in-browser working --- clean/bundle.js | 1 + clean/index.html | 39 ++++++++++++++ clean/mod.js | 136 +++++++++++++++++++++++++++++++++++++++++++++++ jsapi.test.tsx | 22 ++++++-- mod.ts | 4 +- 5 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 clean/bundle.js create mode 100644 clean/index.html create mode 100644 clean/mod.js diff --git a/clean/bundle.js b/clean/bundle.js new file mode 100644 index 0000000..f8be7bf --- /dev/null +++ b/clean/bundle.js @@ -0,0 +1 @@ +var V,f,Hn,he,U,Pn,Dn,tn,ln,_n,rn,Tn,L={},On=[],ve=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Z=Array.isArray;function w(n,e){for(var t in e)n[t]=e[t];return n}function Mn(n){var e=n.parentNode;e&&e.removeChild(n)}function x(n,e,t){var _,r,o,i={};for(o in e)o=="key"?_=e[o]:o=="ref"?r=e[o]:i[o]=e[o];if(arguments.length>2&&(i.children=arguments.length>3?V.call(arguments,2):t),typeof n=="function"&&n.defaultProps!=null)for(o in n.defaultProps)i[o]===void 0&&(i[o]=n.defaultProps[o]);return I(n,i,_,r,null)}function I(n,e,t,_,r){var o={type:n,props:e,key:t,ref:_,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,constructor:void 0,__v:r??++Hn,__i:-1,__u:0};return r==null&&f.vnode!=null&&f.vnode(o),o}function an(){return{current:null}}function S(n){return n.children}function k(n,e){this.props=n,this.context=e}function H(n,e){if(e==null)return n.__?H(n.__,n.__i+1):null;for(var t;ee&&U.sort(tn));Y.__r=0}function In(n,e,t,_,r,o,i,l,c,a,p){var u,d,s,y,E,g=_&&_.__k||On,v=e.length;for(t.__d=c,me(t,e,g),c=t.__d,u=0;u0?I(r.type,r.props,r.key,r.ref?r.ref:null,r.__v):r)!=null?(r.__=n,r.__b=n.__b+1,l=ye(r,t,i,p),r.__i=l,o=null,l!==-1&&(p--,(o=t[l])&&(o.__u|=131072)),o==null||o.__v===null?(l==-1&&u--,typeof r.type!="function"&&(r.__u|=65536)):l!==i&&(l===i+1?u++:l>i?p>c-i?u+=l-i:u--:l(c!=null&&!(131072&c.__u)?1:0))for(;i>=0||l=0){if((c=e[i])&&!(131072&c.__u)&&r==c.key&&o===c.type)return i;i--}if(l2&&(l.children=arguments.length>3?V.call(arguments,2):t),I(n.type,l,_||n.key,r||n.ref,null)}function pn(n,e){var t={__c:e="__cC"+Tn++,__:n,Consumer:function(_,r){return _.children(r)},Provider:function(_){var r,o;return this.getChildContext||(r=[],(o={})[e]=this,this.getChildContext=function(){return o},this.shouldComponentUpdate=function(i){this.props.value!==i.value&&r.some(function(l){l.__e=!0,on(l)})},this.sub=function(i){r.push(i);var l=i.componentWillUnmount;i.componentWillUnmount=function(){r.splice(r.indexOf(i),1),l&&l.call(i)}}),_.children}};return t.Provider.__=t.Consumer.contextType=t}V=On.slice,f={__e:function(n,e,t,_){for(var r,o,i;e=e.__;)if((r=e.__c)&&!r.__)try{if((o=r.constructor)&&o.getDerivedStateFromError!=null&&(r.setState(o.getDerivedStateFromError(n)),i=r.__d),r.componentDidCatch!=null&&(r.componentDidCatch(n,_||{}),i=r.__d),i)return r.__E=r}catch(l){n=l}throw n}},Hn=0,he=function(n){return n!=null&&n.constructor==null},k.prototype.setState=function(n,e){var t;t=this.__s!=null&&this.__s!==this.state?this.__s:this.__s=w({},this.state),typeof n=="function"&&(n=n(w({},t),this.props)),n&&w(t,n),n!=null&&this.__v&&(e&&this._sb.push(e),on(this))},k.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),on(this))},k.prototype.render=S,U=[],Dn=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,tn=function(n,e){return n.__v.__b-e.__v.__b},Y.__r=0,ln=0,_n=Un(!1),rn=Un(!0),Tn=0;var P,h,dn,Wn,O=0,Gn=[],G=[],m=f,$n=m.__b,Bn=m.__r,zn=m.diffed,jn=m.__c,qn=m.unmount,Yn=m.__;function D(n,e){m.__h&&m.__h(h,n,O||e),O=0;var t=h.__H||(h.__H={__:[],__h:[]});return n>=t.__.length&&t.__.push({__V:G}),t.__[n]}function A(n){return O=1,K(Jn,n)}function K(n,e,t){var _=D(P++,2);if(_.t=n,!_.__c&&(_.__=[t?t(e):Jn(void 0,e),function(l){var c=_.__N?_.__N[0]:_.__[0],a=_.t(c,l);c!==a&&(_.__N=[a,_.__[1]],_.__c.setState({}))}],_.__c=h,!h.u)){var r=function(l,c,a){if(!_.__c.__H)return!0;var p=_.__c.__H.__.filter(function(d){return!!d.__c});if(p.every(function(d){return!d.__N}))return!o||o.call(this,l,c,a);var u=!1;return p.forEach(function(d){if(d.__N){var s=d.__[0];d.__=d.__N,d.__N=void 0,s!==d.__[0]&&(u=!0)}}),!(!u&&_.__c.props===l)&&(!o||o.call(this,l,c,a))};h.u=!0;var o=h.shouldComponentUpdate,i=h.componentWillUpdate;h.componentWillUpdate=function(l,c,a){if(this.__e){var p=o;o=void 0,r(l,c,a),o=p}i&&i.call(this,l,c,a)},h.shouldComponentUpdate=r}return _.__N||_.__}function Q(n,e){var t=D(P++,3);!m.__s&&kn(t.__H,e)&&(t.__=n,t.i=e,h.__H.__h.push(t))}function M(n,e){var t=D(P++,4);!m.__s&&kn(t.__H,e)&&(t.__=n,t.i=e,h.__h.push(t))}function vn(n){return O=5,W(function(){return{current:n}},[])}function mn(n,e,t){O=6,M(function(){return typeof n=="function"?(n(e()),function(){return n(null)}):n?(n.current=e(),function(){return n.current=null}):void 0},t==null?t:t.concat(n))}function W(n,e){var t=D(P++,7);return kn(t.__H,e)?(t.__V=n(),t.i=e,t.__h=n,t.__V):t.__}function yn(n,e){return O=8,W(function(){return n},e)}function gn(n){var e=h.context[n.__c],t=D(P++,9);return t.c=n,e?(t.__==null&&(t.__=!0,e.sub(h)),e.props.value):n.__}function bn(n,e){m.useDebugValue&&m.useDebugValue(e?e(n):n)}function _t(n){var e=D(P++,10),t=A();return e.__=n,h.componentDidCatch||(h.componentDidCatch=function(_,r){e.__&&e.__(_,r),t[1](_)}),[t[0],function(){t[1](void 0)}]}function Cn(){var n=D(P++,11);if(!n.__){for(var e=h.__v;e!==null&&!e.__m&&e.__!==null;)e=e.__;var t=e.__m||(e.__m=[0,0]);n.__="P"+t[0]+"-"+t[1]++}return n.__}function Ce(){for(var n;n=Gn.shift();)if(n.__P&&n.__H)try{n.__H.__h.forEach(J),n.__H.__h.forEach(hn),n.__H.__h=[]}catch(e){n.__H.__h=[],m.__e(e,n.__v)}}m.__b=function(n){h=null,$n&&$n(n)},m.__=function(n,e){n&&e.__k&&e.__k.__m&&(n.__m=e.__k.__m),Yn&&Yn(n,e)},m.__r=function(n){Bn&&Bn(n),P=0;var e=(h=n.__c).__H;e&&(dn===h?(e.__h=[],h.__h=[],e.__.forEach(function(t){t.__N&&(t.__=t.__N),t.__V=G,t.__N=t.i=void 0})):(e.__h.forEach(J),e.__h.forEach(hn),e.__h=[],P=0)),dn=h},m.diffed=function(n){zn&&zn(n);var e=n.__c;e&&e.__H&&(e.__H.__h.length&&(Gn.push(e)!==1&&Wn===m.requestAnimationFrame||((Wn=m.requestAnimationFrame)||ke)(Ce)),e.__H.__.forEach(function(t){t.i&&(t.__H=t.i),t.__V!==G&&(t.__=t.__V),t.i=void 0,t.__V=G})),dn=h=null},m.__c=function(n,e){e.some(function(t){try{t.__h.forEach(J),t.__h=t.__h.filter(function(_){return!_.__||hn(_)})}catch(_){e.some(function(r){r.__h&&(r.__h=[])}),e=[],m.__e(_,t.__v)}}),jn&&jn(n,e)},m.unmount=function(n){qn&&qn(n);var e,t=n.__c;t&&t.__H&&(t.__H.__.forEach(function(_){try{J(_)}catch(r){e=r}}),t.__H=void 0,e&&m.__e(e,t.__v))};var Zn=typeof requestAnimationFrame=="function";function ke(n){var e,t=function(){clearTimeout(_),Zn&&cancelAnimationFrame(e),setTimeout(n)},_=setTimeout(t,100);Zn&&(e=requestAnimationFrame(t))}function J(n){var e=h,t=n.__c;typeof t=="function"&&(n.__c=void 0,t()),h=e}function hn(n){var e=h;n.__c=n.__(),h=e}function kn(n,e){return!n||n.length!==e.length||e.some(function(t,_){return t!==n[_]})}function Jn(n,e){return typeof e=="function"?e(n):e}function oe(n,e){for(var t in e)n[t]=e[t];return n}function xn(n,e){for(var t in n)if(t!=="__source"&&!(t in e))return!0;for(var _ in e)if(_!=="__source"&&n[_]!==e[_])return!0;return!1}function Sn(n,e){this.props=n,this.context=e}function Ee(n,e){function t(r){var o=this.props.ref,i=o==r.ref;return!i&&o&&(o.call?o(null):o.current=null),e?!e(this.props,r)||!i:xn(this.props,r)}function _(r){return this.shouldComponentUpdate=t,x(n,r)}return _.displayName="Memo("+(n.displayName||n.name)+")",_.prototype.isReactComponent=!0,_.__f=!0,_}(Sn.prototype=new k).isPureReactComponent=!0,Sn.prototype.shouldComponentUpdate=function(n,e){return xn(this.props,n)||xn(this.state,e)};var Kn=f.__b;f.__b=function(n){n.type&&n.type.__f&&n.ref&&(n.props.ref=n.ref,n.ref=null),Kn&&Kn(n)};var xe=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.forward_ref")||3911;function Se(n){function e(t){var _=oe({},t);return delete _.ref,n(_,t.ref||null)}return e.$$typeof=xe,e.render=e,e.prototype.isReactComponent=e.__f=!0,e.displayName="ForwardRef("+(n.displayName||n.name)+")",e}var Qn=function(n,e){return n==null?null:N(N(n).map(e))},Ne={map:Qn,forEach:Qn,count:function(n){return n?N(n).length:0},only:function(n){var e=N(n);if(e.length!==1)throw"Children.only";return e[0]},toArray:N},we=f.__e;f.__e=function(n,e,t,_){if(n.then){for(var r,o=e;o=o.__;)if((r=o.__c)&&r.__c)return e.__e==null&&(e.__e=t.__e,e.__k=t.__k),r.__c(n,e)}we(n,e,t,_)};var Xn=f.unmount;function ue(n,e,t){return n&&(n.__c&&n.__c.__H&&(n.__c.__H.__.forEach(function(_){typeof _.__c=="function"&&_.__c()}),n.__c.__H=null),(n=oe({},n)).__c!=null&&(n.__c.__P===t&&(n.__c.__P=e),n.__c=null),n.__k=n.__k&&n.__k.map(function(_){return ue(_,e,t)})),n}function ie(n,e,t){return n&&t&&(n.__v=null,n.__k=n.__k&&n.__k.map(function(_){return ie(_,e,t)}),n.__c&&n.__c.__P===e&&(n.__e&&t.appendChild(n.__e),n.__c.__e=!0,n.__c.__P=t)),n}function X(){this.__u=0,this.t=null,this.__b=null}function le(n){var e=n.__.__c;return e&&e.__a&&e.__a(n)}function Pe(n){var e,t,_;function r(o){if(e||(e=n()).then(function(i){t=i.default||i},function(i){_=i}),_)throw _;if(!t)throw e;return x(t,o)}return r.displayName="Lazy",r.__f=!0,r}function $(){this.u=null,this.o=null}f.unmount=function(n){var e=n.__c;e&&e.__R&&e.__R(),e&&32&n.__u&&(n.type=null),Xn&&Xn(n)},(X.prototype=new k).__c=function(n,e){var t=e.__c,_=this;_.t==null&&(_.t=[]),_.t.push(t);var r=le(_.__v),o=!1,i=function(){o||(o=!0,t.__R=null,r?r(l):l())};t.__R=i;var l=function(){if(!--_.__u){if(_.state.__a){var c=_.state.__a;_.__v.__k[0]=ie(c,c.__c.__P,c.__c.__O)}var a;for(_.setState({__a:_.__b=null});a=_.t.pop();)a.forceUpdate()}};_.__u++||32&e.__u||_.setState({__a:_.__b=_.__v.__k[0]}),n.then(i,i)},X.prototype.componentWillUnmount=function(){this.t=[]},X.prototype.render=function(n,e){if(this.__b){if(this.__v.__k){var t=document.createElement("div"),_=this.__v.__k[0].__c;this.__v.__k[0]=ue(this.__b,t,_.__O=_.__P)}this.__b=null}var r=e.__a&&x(S,null,n.fallback);return r&&(r.__u&=-33),[x(S,null,e.__a?null:n.children),r]};var ne=function(n,e,t){if(++t[1]===t[0]&&n.o.delete(e),n.props.revealOrder&&(n.props.revealOrder[0]!=="t"||!n.o.size))for(t=n.u;t;){for(;t.length>3;)t.pop()();if(t[1]>>1,1),e.i.removeChild(_)}}),T(x(Re,{context:e.context},n.__v),e.l)}function He(n,e){var t=x(Ue,{__v:n,i:e});return t.containerInfo=e,t}($.prototype=new k).__a=function(n){var e=this,t=le(e.__v),_=e.o.get(n);return _[0]++,function(r){var o=function(){e.props.revealOrder?(_.push(r),ne(e,n,_)):r()};t?t(o):o()}},$.prototype.render=function(n){this.u=null,this.o=new Map;var e=N(n.children);n.revealOrder&&n.revealOrder[0]==="b"&&e.reverse();for(var t=e.length;t--;)this.o.set(e[t],this.u=[1,0,this.u]);return n.children},$.prototype.componentDidUpdate=$.prototype.componentDidMount=function(){var n=this;this.o.forEach(function(e,t){ne(n,t,e)})};var ae=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.element")||60103,De=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,Te=/^on(Ani|Tra|Tou|BeforeInp|Compo)/,Oe=/[A-Z0-9]/g,Me=typeof document<"u",Fe=function(n){return(typeof Symbol<"u"&&typeof Symbol()=="symbol"?/fil|che|rad/:/fil|che|ra/).test(n)};function Ie(n,e,t){return e.__k==null&&(e.textContent=""),T(n,e),typeof t=="function"&&t(),n?n.__c:null}function Le(n,e,t){return fn(n,e),typeof t=="function"&&t(),n?n.__c:null}k.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(n){Object.defineProperty(k.prototype,n,{configurable:!0,get:function(){return this["UNSAFE_"+n]},set:function(e){Object.defineProperty(this,n,{configurable:!0,writable:!0,value:e})}})});var ee=f.event;function Ve(){}function Ae(){return this.cancelBubble}function We(){return this.defaultPrevented}f.event=function(n){return ee&&(n=ee(n)),n.persist=Ve,n.isPropagationStopped=Ae,n.isDefaultPrevented=We,n.nativeEvent=n};var Nn,$e={enumerable:!1,configurable:!0,get:function(){return this.class}},te=f.vnode;f.vnode=function(n){typeof n.type=="string"&&function(e){var t=e.props,_=e.type,r={};for(var o in t){var i=t[o];if(!(o==="value"&&"defaultValue"in t&&i==null||Me&&o==="children"&&_==="noscript"||o==="class"||o==="className")){var l=o.toLowerCase();o==="defaultValue"&&"value"in t&&t.value==null?o="value":o==="download"&&i===!0?i="":l==="translate"&&i==="no"?i=!1:l==="ondoubleclick"?o="ondblclick":l!=="onchange"||_!=="input"&&_!=="textarea"||Fe(t.type)?l==="onfocus"?o="onfocusin":l==="onblur"?o="onfocusout":Te.test(o)?o=l:_.indexOf("-")===-1&&De.test(o)?o=o.replace(Oe,"-$&").toLowerCase():i===null&&(i=void 0):l=o="oninput",l==="oninput"&&r[o=l]&&(o="oninputCapture"),r[o]=i}}_=="select"&&r.multiple&&Array.isArray(r.value)&&(r.value=N(t.children).forEach(function(c){c.props.selected=r.value.indexOf(c.props.value)!=-1})),_=="select"&&r.defaultValue!=null&&(r.value=N(t.children).forEach(function(c){c.props.selected=r.multiple?r.defaultValue.indexOf(c.props.value)!=-1:r.defaultValue==c.props.value})),t.class&&!t.className?(r.class=t.class,Object.defineProperty(r,"className",$e)):(t.className&&!t.class||t.class&&t.className)&&(r.class=r.className=t.className),e.props=r}(n),n.$$typeof=ae,te&&te(n)};var _e=f.__r;f.__r=function(n){_e&&_e(n),Nn=n.__c};var re=f.diffed;f.diffed=function(n){re&&re(n);var e=n.props,t=n.__e;t!=null&&n.type==="textarea"&&"value"in e&&e.value!==t.value&&(t.value=e.value==null?"":e.value),Nn=null};var Be={ReactCurrentDispatcher:{current:{readContext:function(n){return Nn.__n[n.__c].props.value},useCallback:yn,useContext:gn,useDebugValue:bn,useDeferredValue:se,useEffect:Q,useId:Cn,useImperativeHandle:mn,useInsertionEffect:pe,useLayoutEffect:M,useMemo:W,useReducer:K,useRef:vn,useState:A,useSyncExternalStore:de,useTransition:fe}}},lt="17.0.2";function ze(n){return x.bind(null,n)}function nn(n){return!!n&&n.$$typeof===ae}function je(n){return nn(n)&&n.type===S}function qe(n){return!!n&&!!n.displayName&&(typeof n.displayName=="string"||n.displayName instanceof String)&&n.displayName.startsWith("Memo(")}function Ye(n){return nn(n)?An.apply(null,arguments):n}function Ze(n){return!!n.__k&&(T(null,n),!0)}function Ge(n){return n&&(n.base||n.nodeType===1&&n)||null}var Je=function(n,e){return n(e)},Ke=function(n,e){return n(e)},Qe=S;function ce(n){n()}function se(n){return n}function fe(){return[!1,ce]}var pe=M,Xe=nn;function de(n,e){var t=e(),_=A({h:{__:t,v:e}}),r=_[0].h,o=_[1];return M(function(){r.__=t,r.v=e,En(r)&&o({h:r})},[n,t,e]),Q(function(){return En(r)&&o({h:r}),n(function(){En(r)&&o({h:r})})},[n]),t}function En(n){var e,t,_=n.v,r=n.__;try{var o=_();return!((e=r)===(t=o)&&(e!==0||1/e==1/t)||e!=e&&t!=t)}catch{return!0}}var nt={useState:A,useId:Cn,useReducer:K,useEffect:Q,useLayoutEffect:M,useInsertionEffect:pe,useTransition:fe,useDeferredValue:se,useSyncExternalStore:de,startTransition:ce,useRef:vn,useImperativeHandle:mn,useMemo:W,useCallback:yn,useContext:gn,useDebugValue:bn,version:"17.0.2",Children:Ne,render:Ie,hydrate:Le,unmountComponentAtNode:Ze,createPortal:He,createElement:x,createContext:pn,createFactory:ze,cloneElement:Ye,createRef:an,Fragment:S,isValidElement:nn,isElement:Xe,isFragment:je,isMemo:qe,findDOMNode:Ge,Component:k,PureComponent:Sn,memo:Ee,forwardRef:Se,flushSync:Ke,unstable_batchedUpdates:Je,StrictMode:Qe,Suspense:X,SuspenseList:$,lazy:Pe,__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:Be};export{Ne as Children,k as Component,S as Fragment,Sn as PureComponent,Qe as StrictMode,X as Suspense,$ as SuspenseList,Be as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,Ye as cloneElement,pn as createContext,x as createElement,ze as createFactory,He as createPortal,an as createRef,nt as default,Ge as findDOMNode,Ke as flushSync,Se as forwardRef,Le as hydrate,Xe as isElement,je as isFragment,qe as isMemo,nn as isValidElement,Pe as lazy,Ee as memo,Ie as render,ce as startTransition,Ze as unmountComponentAtNode,Je as unstable_batchedUpdates,yn as useCallback,gn as useContext,bn as useDebugValue,se as useDeferredValue,Q as useEffect,_t as useErrorBoundary,Cn as useId,mn as useImperativeHandle,pe as useInsertionEffect,M as useLayoutEffect,W as useMemo,K as useReducer,vn as useRef,A as useState,de as useSyncExternalStore,fe as useTransition,lt as version}; diff --git a/clean/index.html b/clean/index.html new file mode 100644 index 0000000..ff8a38f --- /dev/null +++ b/clean/index.html @@ -0,0 +1,39 @@ + + + + + Document + + + + + + + + + \ No newline at end of file diff --git a/clean/mod.js b/clean/mod.js new file mode 100644 index 0000000..945d5d1 --- /dev/null +++ b/clean/mod.js @@ -0,0 +1,136 @@ +import * as ESBuild from "https://deno.land/x/esbuild@v0.25.0/wasm.js"; +await ESBuild.initialize({ worker: false }); + +/** @typedef {{imports?:Record, scopes?:Record>}} ImportMap */ +/** @typedef {{fileSystem:string, esBuild:ESBuild.BuildOptions, importMap:ImportMap}} BuildOptions */ + +/** + * Resolves a module specifier against an import map + * @param {string} specifier - The module specifier to resolve (bare specifier or absolute URL) + * @param {ImportMap} importMap - The import map object containing imports and scopes + * @param {string} baseURL - The base URL for context (especially for scopes) + * @returns {string} The resolved URL + */ +function resolveImportMap(specifier, importMap, baseURL) { + + // Check for prefix matches in the main imports + const result = checkPrefixMatch(specifier, importMap.imports); + if (result) { + return result + } + + // First check scopes that match the baseURL (scope applies based on baseURL, not specifier) + if (importMap.scopes) { + const scopeKeys = Object.keys(importMap.scopes).sort((a, b) => b.length - a.length); + + for (const scopeKey of scopeKeys) { + // Convert scope key to absolute URL and check if baseURL starts with it + const scopeURL = new URL(scopeKey, baseURL).href; + + if (baseURL.startsWith(scopeURL)) { + const scopeImports = importMap.scopes[scopeKey]; + + // Check for prefix match in the scope + const result = checkPrefixMatch(specifier, scopeImports); + if (result) { + return result; + } + } + } + } +} + +/** + * Helper function to check for prefix matches + * @param {string} specifier - The specifier to check + * @param {Object} mappings - Object with prefix mappings + * @returns {string|null} The resolved path or null if no match + */ +function checkPrefixMatch(specifier, mappings) { + + const check = mappings[specifier]; + if(check){ return check; } + + const prefixes = Object.keys(mappings) + .filter(key => key.endsWith('/') && specifier.startsWith(key)) + .sort((a, b) => b.length - a.length); + + for (const prefix of prefixes) { + const remainder = specifier.slice(prefix.length); + return mappings[prefix] + remainder; + } + + return null; +} + +/** + * + * @param {string} fullPathDir + * @param {ImportMap} importMap + * @returns {ESBuild.Plugin} + */ +export function resolvePlugin(fullPathDir, importMap) +{ + return { + name: "resolve-plugin", + setup(build) { + + build.onResolve( {/* `/`, `./`, and `../` */ filter:/^(\/|\.\/|\.\.\/).*/}, args=> + { + const resolveRoot = args.importer||fullPathDir; + const out = { path:new URL(args.path, resolveRoot).href, namespace:"FULLPATH" }; + return out; + } ); + + build.onResolve({filter:/.*/}, args=> + { + const resolveRoot = args.importer||fullPathDir; + const check = resolveImportMap(args.path, importMap, resolveRoot); + if(check) + { + console.log("import remap", args.path, "=>", check); + const out = { path:new URL(check, resolveRoot).href, namespace:"FULLPATH" }; + return out; + } + + }) + + build.onLoad( + {/* `file://`, `http://`, and `https://` */ filter:/^(file:\/\/|http:\/\/|https:\/\/).*/}, + async(args)=> + { + const contents = await fetch(args.path).then(r=>r.text()); + const out = { contents, loader: `tsx` }; + return out; + } ); + }, + } +}; + + +/** + * Perform a bundle + * @param {Partial} options + * @returns {Promise>} build result + */ +export default async function Build(options) +{ + const configuration = { + entryPoints: ["entry"], + bundle: true, + minify: true, + format: "esm", + jsx: "automatic", + jsxImportSource: "react", + ...options.esBuild||{}, + plugins: [ + resolvePlugin(options.fileSystem||import.meta.resolve("./"), options.importMap||{}), + ...options.esBuild.plugins||[] + ] + }; + + const result = await ESBuild.build(configuration); + return result; +} + +export { ESBuild } \ No newline at end of file diff --git a/jsapi.test.tsx b/jsapi.test.tsx index c3b0571..a035af7 100644 --- a/jsapi.test.tsx +++ b/jsapi.test.tsx @@ -1,9 +1,25 @@ import * as Test from "https://deno.land/std@0.224.0/assert/mod.ts"; -import Bundle, {ESBuild} from "./mod.ts"; +import Bundle from "./mod.ts"; +const {outputFiles} = await Bundle( + // directory to work from + import.meta.resolve('./test/'), -const path = await import.meta.resolve("./test/"); -const {outputFiles} = await Bundle(path, {entryPoints:["./app.tsx"]}); + // ESBuild configuration (entry points are relative to "directory"): + {entryPoints:["entry"]}, + + // import map (if omitted, will scan for a deno configuration within "directory" and use that) + {"imports": { + "entry": "./app.tsx", + "react": "https://esm.sh/preact@10.22.0/compat", + "react/": "https://esm.sh/preact@10.22.0/compat/" + }} +); + +for(const item of outputFiles){ + // do something with the output... + console.log(item.text.substring(0, 200)); +} Deno.test("check", ()=>{ Test.assert(outputFiles?.length == 1); diff --git a/mod.ts b/mod.ts index edfc803..855ebdf 100644 --- a/mod.ts +++ b/mod.ts @@ -7,7 +7,7 @@ import * as ESBuild from "https://deno.land/x/esbuild@v0.25.0/wasm.js"; * @param {string} baseURL - The base URL for context (especially for scopes) * @returns {string} The resolved URL */ -function resolveImportMap(specifier, importMap, baseURL) { +function resolveImportMap(specifier:string, importMap:ImportMap, baseURL) { // Check for prefix matches in the main imports const result = checkPrefixMatch(specifier, importMap.imports); @@ -107,7 +107,7 @@ export type BuildOptions = ESBuild.BuildOptions; * * @param {string} directory Full file:// or http(s):// path to the directory containing assets you want to build (needed to resolve relative imports) * @param {ESBuild.BuildOptions} [buildOptions={}] ESBuild "build" options (will be merged with "reasonable defaults") for docs: https://esbuild.github.io/api/#general-options - * @param {ImportMap|null} [importMap={}] An object to act as the import map ({imports:Record}). If this is left blank, a configuration will be scanned for in the "directory" + * @param {ImportMap} [importMap={}] An object to act as the import map ({imports:Record}). If this is left blank, a configuration will be scanned for in the "directory" * @returns {Promise>} build result */ export default async function Build(directory, buildOptions={}, importMap = {}) From 22ca93fc9a102695ba5c630e98026c513da17533 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 28 Feb 2025 17:09:55 -0500 Subject: [PATCH 7/7] shuffle things around --- README.md | 51 +++------- {clean => browser_example}/bundle.js | 0 {clean => browser_example}/index.html | 2 +- clean/mod.js => mod.js | 0 jsapi.test.tsx => mod.test.tsx | 17 ++-- mod.ts | 133 ------------------------- introspect.ts => server/introspect.ts | 0 serve.tsx => server/serve.tsx | 2 +- {test => test_project}/app-dynamic.tsx | 0 {test => test_project}/app.tsx | 0 {test => test_project}/deep/deep.tsx | 0 {test => test_project}/deno.json | 0 {test => test_project}/index.html | 0 {test => test_project}/leaf.ts | 0 {test => test_project}/other.tsx | 0 what/entry.ts | 10 -- what/include_a.ts | 6 -- what/include_b.ts | 1 - what/index.ts | 20 ---- 19 files changed, 24 insertions(+), 218 deletions(-) rename {clean => browser_example}/bundle.js (100%) rename {clean => browser_example}/index.html (93%) rename clean/mod.js => mod.js (100%) rename jsapi.test.tsx => mod.test.tsx (56%) delete mode 100644 mod.ts rename introspect.ts => server/introspect.ts (100%) rename serve.tsx => server/serve.tsx (98%) rename {test => test_project}/app-dynamic.tsx (100%) rename {test => test_project}/app.tsx (100%) rename {test => test_project}/deep/deep.tsx (100%) rename {test => test_project}/deno.json (100%) rename {test => test_project}/index.html (100%) rename {test => test_project}/leaf.ts (100%) rename {test => test_project}/other.tsx (100%) delete mode 100644 what/entry.ts delete mode 100644 what/include_a.ts delete mode 100644 what/include_b.ts delete mode 100644 what/index.ts diff --git a/README.md b/README.md index 3dabe8d..95c9946 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,27 @@ # ESBuild Typescript Bunder -## Supports import maps and runs on Deno Deploy +## Supports import maps and runs on Deno Deploy and the browser Uses the WASM version of ESBuild for use on Deno Deploy and adds some plugins to allow for import maps. ***Unfortunately, `npm:*` and `jsr:*` specifiers are not supported currently***. ### Manual Bundle API -```typescript -import Bundle from "./mod.ts"; -Bundle( - // directory to work from - "./some_folder", +```javascript +import Bundle from "./mod.js"; - // ESBuild configuration (entry points are relative to "directory"): - {entry:["./app.tsx"]}, - - // import map (if omitted, will scan for a deno configuration within "directory" and use that) - {"imports": { +const {outputFiles} = await Bundle({ + fileSystem:import.meta.resolve('./test_project/'), + esBuild:{entryPoints:["entry"]}, + importMap:{"imports": { + "entry": "./app.tsx", "react": "https://esm.sh/preact@10.22.0/compat", "react/": "https://esm.sh/preact@10.22.0/compat/" }} -); -if(outputFiles){ - for(const item of outputFiles){ - // do something with the output... - console.log(item.text); - } +}); + + +for(const item of outputFiles){ + // do something with the output... + console.log(item.text.substring(0, 200)); } ``` -### Static bundle server - -#### API -```typescript -import Serve from "./serve.tsx"; -Serve({path:"./some_folder", html:"index.html", port:8080}); -``` - -#### CLI -``` -deno run -A /serve.tsx --path=./some_folder --html=index.html --port=8080 -``` - -Accepts three optional settings: -- `path` A directory to serve content from. Defaults to the current working directory. -- `html` A path to an html file or actual html string to *always use* when the server would serve up a non-file route. If `html` is a path, it is assumed to be relative to `path`. If not specified, routes that don't match files will 404. -- `port` Port number (default is 8000). - -When requests to typescript files are made to the server, it will treat that file as an entrypoint and return the corresponding plain javascript bundle. +(dev server is being reworked) \ No newline at end of file diff --git a/clean/bundle.js b/browser_example/bundle.js similarity index 100% rename from clean/bundle.js rename to browser_example/bundle.js diff --git a/clean/index.html b/browser_example/index.html similarity index 93% rename from clean/index.html rename to browser_example/index.html index ff8a38f..8c2ab52 100644 --- a/clean/index.html +++ b/browser_example/index.html @@ -14,7 +14,7 @@