diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e63f709..16f7c64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,10 @@ jobs: - name: Setup Deno uses: denoland/setup-deno@main with: - # TODO: revert back to actually using latest stable once https://github.com/denoland/deno/issues/22926 is released - deno-version: ${{ matrix.deno == 'v1.x' && 'v1.40.3' || matrix.deno}} + deno-version: "1.x" + + - name: Install Rust + uses: dsherret/rust-toolchain-file@v1 - run: deno --version @@ -38,6 +40,9 @@ jobs: if: runner.os == 'Linux' run: deno fmt --check + - name: Build Wasm + run: deno task wasmbuild + - name: Lint if: runner.os == 'Linux' run: deno lint diff --git a/.gitignore b/.gitignore index d759f19..172974d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ dist/ node_modules/ dist/ - +/target +src/wasm/loader.generated.d.ts +src/wasm/loader.generated.js diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..9bb8d9d --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,3 @@ +max_width = 80 +tab_spaces = 2 +edition = "2021" diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a2dfb60 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,293 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "deno_lockfile" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579117d5815aa9bae0212637d6f4d5f45f9649bb2c8988dca434077545535039" +dependencies = [ + "deno_semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "deno_semver" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6657fecb9ac6a7a71f552c95e8cc492466a75f5660224577e2226bcf30db9768" +dependencies = [ + "monch", + "once_cell", + "serde", + "thiserror", + "url", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "loader" +version = "0.0.0" +dependencies = [ + "deno_lockfile", + "deno_semver", + "thiserror", + "wasm-bindgen", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "monch" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52c1b33ff98142aecea13138bd399b68aa7ab5d9546c300988c345004001eea" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8111c76 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +resolver = "2" +members = [ + "src/wasm", +] + +[profile.release] +codegen-units = 1 +incremental = true +lto = true +opt-level = "z" diff --git a/README.md b/README.md index 97ffefc..2490d64 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This example bundles an entrypoint into a single ESM output. ```js import * as esbuild from "npm:esbuild@0.20.2"; -// Import the WASM build on platforms where running subprocesses is not +// Import the Wasm build on platforms where running subprocesses is not // permitted, such as Deno Deploy, or when running without `--allow-run`. // import * as esbuild from "https://deno.land/x/esbuild@0.20.2/wasm.js"; @@ -42,7 +42,7 @@ esbuild.stop(); pre-downloaded into a local `node_modules/` directory. - When using the `"portable"` loader with `jsr:` specifiers, a lockfile must be present and passed to the loader (either using `configPath` or `lockPath`). -- `npm:` specifiers are not supported on WASM esbuild builds due to FS access +- `npm:` specifiers are not supported on Wasm esbuild builds due to FS access limitations (see https://github.com/evanw/esbuild/pull/2968). ## Documentation diff --git a/deno.json b/deno.json index aca13bc..067e319 100644 --- a/deno.json +++ b/deno.json @@ -17,11 +17,19 @@ }, "lock": false, "tasks": { - "test": "deno test -A --parallel --trace-ops", + "test": "deno test -A --parallel --trace-leaks", "check:types": "deno check **/*.ts", - "ok": "deno fmt --check && deno lint && deno task check:types && deno task test" + "ok": "deno fmt --check && deno lint && deno task check:types && deno task test", + "wasmbuild": "deno run -A jsr:@deno/wasmbuild@0.17.2 --sync --out src/wasm" }, - "fmt": { "exclude": ["dist"] }, - "lint": { "exclude": ["dist"] }, - "publish": { "exclude": ["testdata/", "*_test.ts"] } + "fmt": { "exclude": ["dist", "target"] }, + "lint": { "exclude": ["dist", "target", "testdata"] }, + "publish": { + "exclude": [ + "testdata/", + "*_test.ts", + "!src/wasm/loader.generated.d.ts", + "!src/wasm/loader.generated.js" + ] + } } diff --git a/mod.ts b/mod.ts index 57ee02f..766b5ec 100644 --- a/mod.ts +++ b/mod.ts @@ -4,14 +4,14 @@ import { denoResolverPlugin, type DenoResolverPluginOptions, } from "./src/plugin_deno_resolver.ts"; -export { denoResolverPlugin, DenoResolverPluginOptions }; +export { denoResolverPlugin, type DenoResolverPluginOptions }; import { DEFAULT_LOADER, denoLoaderPlugin, type DenoLoaderPluginOptions, } from "./src/plugin_deno_loader.ts"; -export { DEFAULT_LOADER, denoLoaderPlugin, DenoLoaderPluginOptions }; +export { DEFAULT_LOADER, denoLoaderPlugin, type DenoLoaderPluginOptions }; export { type EsbuildResolution, diff --git a/mod_test.ts b/mod_test.ts index 36eb3fe..2c658c6 100644 --- a/mod_test.ts +++ b/mod_test.ts @@ -739,7 +739,9 @@ Deno.test("bundle config ref import map", async (t) => { }); }); -Deno.test("bundle config inline import map with expansion", async (t) => { +Deno.test("bundle config inline import map with expansion", { + ignore: Deno.version.deno.startsWith("1."), +}, async (t) => { await testLoader(t, LOADERS, async (esbuild, loader) => { const configPath = join( Deno.cwd(), @@ -874,7 +876,9 @@ Deno.test("externals", async (t) => { }); }); -Deno.test("jsr specifiers - auto discovered lock file", async (t) => { +Deno.test("jsr specifiers - auto discovered lock file", { + ignore: Deno.version.deno.startsWith("1."), +}, async (t) => { await testLoader(t, LOADERS, async (esbuild, loader) => { const configPath = join(Deno.cwd(), "testdata", "jsr", "deno.json"); const res = await esbuild.build({ @@ -899,7 +903,9 @@ Deno.test("jsr specifiers - auto discovered lock file", async (t) => { }); }); -Deno.test("jsr specifiers - lock file referenced in deno.json", async (t) => { +Deno.test("jsr specifiers - lock file referenced in deno.json", { + ignore: Deno.version.deno.startsWith("1."), +}, async (t) => { await testLoader(t, LOADERS, async (esbuild, loader) => { const configPath = join(Deno.cwd(), "testdata", "jsr_deno.json"); const res = await esbuild.build({ @@ -924,6 +930,31 @@ Deno.test("jsr specifiers - lock file referenced in deno.json", async (t) => { }); }); +Deno.test("jsr specifiers - lock file v3 referenced in deno.json", async (t) => { + await testLoader(t, ["portable"], async (esbuild, loader) => { + const configPath = join(Deno.cwd(), "testdata", "jsr_deno_lockv3.json"); + const res = await esbuild.build({ + ...DEFAULT_OPTS, + plugins: [...denoPlugins({ loader, configPath })], + bundle: true, + platform: "neutral", + entryPoints: ["jsr:@std/path@^0.213"], + }); + assertEquals(res.warnings, []); + assertEquals(res.errors, []); + assertEquals(res.outputFiles.length, 1); + const output = res.outputFiles[0]; + assertStringIncludes( + output.text, + "https://jsr.io/@std/path/0.213.1/mod.ts", + ); + const ns = await import( + `data:application/javascript;base64,${btoa(output.text)}` + ); + assertEquals(ns.join("a", "b"), join("a", "b")); + }); +}); + Deno.test("jsr specifiers - no lockfile", async (t) => { await testLoader(t, ["native"], async (esbuild, loader) => { const tmp = Deno.makeTempDirSync(); diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..95c5b61 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.81.0" +components = [ "clippy", "rustfmt" ] diff --git a/src/deno.ts b/src/deno.ts index 5558d25..a118f01 100644 --- a/src/deno.ts +++ b/src/deno.ts @@ -111,8 +111,12 @@ async function info( specifier: string, options: InfoOptions, ): Promise { + const args = ["info", "--json"]; + if (!Deno.version.deno.startsWith("1.")) { + args.push("--allow-import"); + } const opts = { - args: ["info", "--json"], + args, cwd: undefined as string | undefined, env: { DENO_NO_PACKAGE_JSON: "true" } as Record, stdout: "piped", @@ -187,7 +191,14 @@ export class InfoCache { } #resolve(specifier: string): string { - return this.#redirects.get(specifier) ?? specifier; + const original = specifier; + let counter = 0; + while (counter++ < 10) { + const redirect = this.#redirects.get(specifier); + if (redirect === undefined) return specifier; + specifier = redirect; + } + throw new Error(`Too many redirects for '${original}'`); } #getCached(specifier: string): ModuleEntry | undefined { @@ -260,8 +271,3 @@ export class InfoCache { } } } - -export interface Lockfile { - version: string; - packages?: { specifiers?: Record }; -} diff --git a/src/loader_native.ts b/src/loader_native.ts index a3f46df..3fadc9d 100644 --- a/src/loader_native.ts +++ b/src/loader_native.ts @@ -2,10 +2,10 @@ import type * as esbuild from "./esbuild_types.ts"; import { dirname, fromFileUrl, join } from "@std/path"; import { encodeBase32 } from "@std/encoding/base32"; import * as deno from "./deno.ts"; -import { rootInfo, RootInfoOutput } from "./deno.ts"; +import { rootInfo, type RootInfoOutput } from "./deno.ts"; import { - Loader, - LoaderResolution, + type Loader, + type LoaderResolution, mapContentType, mediaTypeToLoader, parseNpmSpecifier, diff --git a/src/loader_portable.ts b/src/loader_portable.ts index 18b87b1..18021d4 100644 --- a/src/loader_portable.ts +++ b/src/loader_portable.ts @@ -1,14 +1,15 @@ import type * as esbuild from "./esbuild_types.ts"; import { fromFileUrl } from "@std/path"; -import * as deno from "./deno.ts"; +import type * as deno from "./deno.ts"; import { - Loader, - LoaderResolution, + type Loader, + type LoaderResolution, mapContentType, mediaTypeToLoader, parseJsrSpecifier, parseNpmSpecifier, } from "./shared.ts"; +import { instantiate, type WasmLockfile } from "./wasm/loader.generated.js"; interface Module { specifier: string; @@ -16,12 +17,13 @@ interface Module { data: Uint8Array; } -const JSR_REGISTRY_URL = Deno.env.get("DENO_REGISTRY_URL") ?? "https://jsr.io"; +const JSR_URL = Deno.env.get("JSR_URL") ?? "https://jsr.io"; -async function readLockfile(path: string): Promise { +async function readLockfile(path: string): Promise { try { const data = await Deno.readTextFile(path); - return JSON.parse(data); + const instance = instantiate(); + return new instance.WasmLockfile(path, data); } catch (err) { if (err instanceof Deno.errors.NotFound) { return null; @@ -34,10 +36,10 @@ interface PortableLoaderOptions { lock?: string; } -export class PortableLoader implements Loader { +export class PortableLoader implements Loader, Disposable { #options: PortableLoaderOptions; #fetchOngoing = new Map>(); - #lockfile: Promise | deno.Lockfile | null | undefined; + #lockfile: Promise | WasmLockfile | null | undefined; #fetchModules = new Map(); #fetchRedirects = new Map(); @@ -46,6 +48,12 @@ export class PortableLoader implements Loader { this.#options = options; } + [Symbol.dispose]() { + if (this.#lockfile != null && "free" in this.#lockfile) { + this.#lockfile.free(); + } + } + async resolve(specifier: URL): Promise { switch (specifier.protocol) { case "file:": { @@ -97,27 +105,19 @@ export class PortableLoader implements Loader { ); } const lockfile = this.#lockfile; - if (lockfile.version !== "3") { - throw new Error( - "Unsupported lockfile version: " + lockfile.version, - ); - } - // Look up the package + constraint in the lockfile. const id = `jsr:${jsrSpecifier.name}${ jsrSpecifier.version ? `@${jsrSpecifier.version}` : "" }`; - const lockfileEntry = lockfile.packages?.specifiers?.[id]; - if (!lockfileEntry) { + const resolvedVersion = lockfile.package_version(id); + if (!resolvedVersion) { throw new Error(`Specifier not found in lockfile: ${id}`); } - const lockfileEntryParsed = parseJsrSpecifier(new URL(lockfileEntry)); // Load the JSR manifest to find the export path. const manifestUrl = new URL( - `./${lockfileEntryParsed.name}/${lockfileEntryParsed - .version!}_meta.json`, - JSR_REGISTRY_URL, + `./${jsrSpecifier.name}/${resolvedVersion}_meta.json`, + JSR_URL, ); const manifest = await this.#loadRemote(manifestUrl.href); if (manifest.mediaType !== "Json") { @@ -133,15 +133,14 @@ export class PortableLoader implements Loader { const exportPath = manifestJson.exports[exportEntry]; if (!exportPath) { throw new Error( - `Package '${lockfileEntry}' has no export named '${exportEntry}'`, + `Package 'jsr:${jsrSpecifier.name}@${resolvedVersion}' has no export named '${exportEntry}'`, ); } // Return the resolved URL. return new URL( - `./${lockfileEntryParsed.name}/${lockfileEntryParsed - .version!}/${exportPath}`, - JSR_REGISTRY_URL, + `./${jsrSpecifier.name}/${resolvedVersion}/${exportPath}`, + JSR_URL, ); } diff --git a/src/plugin_deno_loader.ts b/src/plugin_deno_loader.ts index 54d2f20..fb38616 100644 --- a/src/plugin_deno_loader.ts +++ b/src/plugin_deno_loader.ts @@ -6,7 +6,7 @@ import { isInNodeModules } from "./shared.ts"; import { esbuildResolutionToURL, isNodeModulesResolution, - Loader, + type Loader, readDenoConfig, urlToEsbuildResolution, } from "./shared.ts"; @@ -194,11 +194,13 @@ export function denoLoaderPlugin( nodeModulesDir = join(cwd, "node_modules"); } - let loaderImpl: Loader; + let loaderImpl: Loader | undefined; const packageIdByNodeModules = new Map(); build.onStart(async function onStart() { + loaderImpl?.[Symbol.dispose]?.(); + loaderImpl = undefined; packageIdByNodeModules.clear(); switch (loader) { case "native": @@ -245,8 +247,8 @@ export function denoLoaderPlugin( if (nodeModulesDir) { return undefined; } else if ( - loaderImpl.nodeModulesDirForPackage && - loaderImpl.packageIdFromNameInPackage + loaderImpl!.nodeModulesDirForPackage && + loaderImpl!.packageIdFromNameInPackage ) { let parentPackageId: string | undefined; let path = args.importer; @@ -279,12 +281,12 @@ export function denoLoaderPlugin( packageName = name; pathParts = rest; } - const packageId = loaderImpl.packageIdFromNameInPackage( + const packageId = loaderImpl!.packageIdFromNameInPackage( packageName, parentPackageId, ); const id = packageId ?? parentPackageId; - const resolveDir = await loaderImpl.nodeModulesDirForPackage(id); + const resolveDir = await loaderImpl!.nodeModulesDirForPackage(id); packageIdByNodeModules.set(resolveDir, id); const path = [packageName, ...pathParts].join("/"); return await build.resolve(path, { @@ -303,7 +305,7 @@ export function denoLoaderPlugin( // Once we have an absolute path, let the loader resolver figure out // what to do with it. - const res = await loaderImpl.resolve(specifier); + const res = await loaderImpl!.resolve(specifier); switch (res.kind) { case "esm": { @@ -314,8 +316,8 @@ export function denoLoaderPlugin( let resolveDir: string; if (nodeModulesDir) { resolveDir = nodeModulesDir; - } else if (loaderImpl.nodeModulesDirForPackage) { - resolveDir = await loaderImpl.nodeModulesDirForPackage( + } else if (loaderImpl!.nodeModulesDirForPackage) { + resolveDir = await loaderImpl!.nodeModulesDirForPackage( res.packageId, ); packageIdByNodeModules.set(resolveDir, res.packageId); @@ -355,7 +357,7 @@ export function denoLoaderPlugin( return undefined; } const specifier = esbuildResolutionToURL(args); - return loaderImpl.loadEsm(specifier); + return loaderImpl!.loadEsm(specifier); } // TODO(lucacasonato): once https://github.com/evanw/esbuild/pull/2968 is fixed, remove the catch all "file" handler build.onLoad({ filter: /.*/, namespace: "file" }, onLoad); diff --git a/src/plugin_deno_resolver.ts b/src/plugin_deno_resolver.ts index d9f0f49..ca12e87 100644 --- a/src/plugin_deno_resolver.ts +++ b/src/plugin_deno_resolver.ts @@ -1,11 +1,11 @@ import type * as esbuild from "./esbuild_types.ts"; import { toFileUrl } from "@std/path"; import { - ImportMap, + type ImportMap, resolveImportMap, resolveModuleSpecifier, } from "x/importmap"; -import { Scopes, SpecifierMap } from "x/importmap/_util.ts"; +import type { Scopes, SpecifierMap } from "x/importmap/_util.ts"; import { expandEmbeddedImportMap, isNodeModulesResolution, diff --git a/src/shared.ts b/src/shared.ts index 3f8e183..b0b6d23 100644 --- a/src/shared.ts +++ b/src/shared.ts @@ -1,7 +1,7 @@ import { extname, fromFileUrl, SEPARATOR, toFileUrl } from "@std/path"; import * as JSONC from "@std/jsonc"; -import { ImportMap } from "x/importmap"; -import { MediaType } from "./deno.ts"; +import type { ImportMap } from "x/importmap"; +import type { MediaType } from "./deno.ts"; import type * as esbuild from "./esbuild_types.ts"; export interface Loader { @@ -13,6 +13,8 @@ export interface Loader { parentPackageId: string, ): string | null; nodeModulesDirForPackage?(npmPackageId?: string): Promise; + + [Symbol.dispose]?(): void; } export type LoaderResolution = diff --git a/src/shared_test.ts b/src/shared_test.ts index 169d018..ba2f170 100644 --- a/src/shared_test.ts +++ b/src/shared_test.ts @@ -1,12 +1,12 @@ import { expandEmbeddedImportMap, - NpmSpecifier, + type NpmSpecifier, parseJsrSpecifier, parseNpmSpecifier, } from "./shared.ts"; import { assertEquals, assertThrows } from "@std/assert"; -import { JsrSpecifier } from "./shared.ts"; -import { ImportMap } from "x/importmap"; +import type { JsrSpecifier } from "./shared.ts"; +import type { ImportMap } from "x/importmap"; interface NpmSpecifierTestCase extends NpmSpecifier { specifier: string; diff --git a/src/wasm/Cargo.toml b/src/wasm/Cargo.toml new file mode 100644 index 0000000..842e7fe --- /dev/null +++ b/src/wasm/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "loader" +version = "0.0.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +# deno_config = { git = "https://github.com/denoland/deno_config.git", rev = "59e578eaea0b720b4d79d5617b1f2a1c076b0d2b" } +deno_lockfile = "0.23.1" +deno_semver = "0.5.13" +thiserror = "1.0.64" +wasm-bindgen = "=0.2.92" diff --git a/src/wasm/src/lib.rs b/src/wasm/src/lib.rs new file mode 100644 index 0000000..4f362c8 --- /dev/null +++ b/src/wasm/src/lib.rs @@ -0,0 +1,34 @@ +use std::path::PathBuf; + +use deno_lockfile::NewLockfileOptions; +use deno_semver::jsr::JsrDepPackageReq; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct WasmLockfile { + inner: deno_lockfile::Lockfile, +} + +#[wasm_bindgen] +impl WasmLockfile { + #[wasm_bindgen(constructor)] + pub fn new( + file_path: String, + content: String, + ) -> Result { + let lockfile = deno_lockfile::Lockfile::new(NewLockfileOptions { + file_path: PathBuf::from(file_path), + content: &content, + overwrite: false, + })?; + Ok(Self { inner: lockfile }) + } + + pub fn package_version( + &self, + specifier: &str, + ) -> Result, JsError> { + let dep = JsrDepPackageReq::from_str(specifier)?; + Ok(self.inner.content.packages.specifiers.get(&dep).cloned()) + } +} diff --git a/testdata/deno_v3.lock b/testdata/deno_v3.lock new file mode 100644 index 0000000..697c501 --- /dev/null +++ b/testdata/deno_v3.lock @@ -0,0 +1,21 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@std/assert@^0.213.1": "jsr:@std/assert@0.213.1", + "jsr:@std/path@^0.213": "jsr:@std/path@0.213.1" + }, + "jsr": { + "@std/assert@0.213.1": { + "integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe" + }, + "@std/path@0.213.1": { + "integrity": "f187bf278a172752e02fcbacf6bd78a335ed320d080a7ed3a5a59c3e88abc673", + "dependencies": [ + "jsr:@std/assert@^0.213.1" + ] + } + } + }, + "remote": {} +} \ No newline at end of file diff --git a/testdata/jsr/deno.lock b/testdata/jsr/deno.lock index a59b44b..7482e05 100644 --- a/testdata/jsr/deno.lock +++ b/testdata/jsr/deno.lock @@ -1,21 +1,18 @@ { - "version": "3", - "packages": { - "specifiers": { - "jsr:@std/assert@^0.213.1": "jsr:@std/assert@0.213.1", - "jsr:@std/path@^0.213": "jsr:@std/path@0.213.1" + "version": "4", + "specifiers": { + "jsr:@std/assert@~0.213.1": "0.213.1", + "jsr:@std/path@0.213": "0.213.1" + }, + "jsr": { + "@std/assert@0.213.1": { + "integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe" }, - "jsr": { - "@std/assert@0.213.1": { - "integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe" - }, - "@std/path@0.213.1": { - "integrity": "f187bf278a172752e02fcbacf6bd78a335ed320d080a7ed3a5a59c3e88abc673", - "dependencies": [ - "jsr:@std/assert@^0.213.1" - ] - } + "@std/path@0.213.1": { + "integrity": "f187bf278a172752e02fcbacf6bd78a335ed320d080a7ed3a5a59c3e88abc673", + "dependencies": [ + "jsr:@std/assert" + ] } - }, - "remote": {} + } } diff --git a/testdata/jsr_deno_lockv3.json b/testdata/jsr_deno_lockv3.json new file mode 100644 index 0000000..0e15320 --- /dev/null +++ b/testdata/jsr_deno_lockv3.json @@ -0,0 +1,3 @@ +{ + "lock": "./deno_v3.lock" +} diff --git a/vendor/x/importmap/mod.ts b/vendor/x/importmap/mod.ts index dfde522..6ae12ba 100644 --- a/vendor/x/importmap/mod.ts +++ b/vendor/x/importmap/mod.ts @@ -1,13 +1,13 @@ import { - ImportMap, + type ImportMap, isImportMap, isImports, isScopes, isSpecifierMap, isURL, - Scopes, + type Scopes, sortObject, - SpecifierMap, + type SpecifierMap, } from "./_util.ts"; export type { ImportMap } from "./_util.ts";