import { esbuild, ImportMap, resolveImportMap, resolveModuleSpecifier, toFileUrl, } from "./deps.ts"; import { load as nativeLoad } from "./src/native_loader.ts"; import { load as portableLoad } from "./src/portable_loader.ts"; import { ModuleEntry } from "./src/deno.ts"; export interface DenoPluginOptions { /** * Specify the URL to an import map to use when resolving import specifiers. * The URL must be fetchable with `fetch`. */ importMapURL?: URL; /** * Specify which loader to use. By default this will use the `native` loader, * unless `Deno.run` is not available. * * - `native`: Shells out to the Deno execuatble under the hood to load * files. Requires --allow-read and --allow-run. * - `portable`: Do module downloading and caching with only Web APIs. * Requires --allow-net. */ loader?: "native" | "portable"; } /** The default loader to use. */ export const DEFAULT_LOADER: "native" | "portable" = typeof Deno.run === "function" ? "native" : "portable"; export function denoPlugin(options: DenoPluginOptions = {}): esbuild.Plugin { const loader = options.loader ?? DEFAULT_LOADER; return { name: "deno", setup(build) { const infoCache = new Map(); let importMap: ImportMap | null = null; build.onStart(async function onStart() { if (options.importMapURL !== undefined) { const resp = await fetch(options.importMapURL.href); const txt = await resp.text(); importMap = resolveImportMap(JSON.parse(txt), options.importMapURL); } else { importMap = null; } }); build.onResolve( { filter: /.*/ }, function onResolve( args: esbuild.OnResolveArgs, ): esbuild.OnResolveResult | null | undefined { // console.log("To resolve", args.path); const resolveDir = args.resolveDir ? `${toFileUrl(args.resolveDir).href}/` : ""; const referrer = args.importer || resolveDir; let resolved: URL; if (importMap !== null) { const res = resolveModuleSpecifier( args.path, importMap, new URL(referrer) || undefined, ); resolved = new URL(res); } else { resolved = new URL(args.path, referrer); } // console.log("Resolved", resolved.href); if (build.initialOptions.external) { for (const external of build.initialOptions.external) { if (resolved.href.startsWith(external)) { // console.log("Got external", args.path, resolved.href); return { path: resolved.href, external: true }; } } } const href = resolved.href; // Don't use the deno loader for any of the specific loader file extensions const loaderExts = Object.keys(build.initialOptions.loader || {}); for (const ext of loaderExts) { if (href.endsWith(ext)) { console.log("Skipping", href); return { path: resolved.href.substring("file://".length), }; } } return { path: resolved.href, namespace: "deno" }; }, ); build.onLoad( { filter: /.*/ }, function onLoad( args: esbuild.OnLoadArgs, ): Promise { if (args.path.endsWith(".css")) { return Promise.resolve(null); } const url = new URL(args.path); switch (loader) { case "native": return nativeLoad(infoCache, url, options); case "portable": return portableLoad(url, options); } }, ); }, }; }