esbuild-deno-loader

repository·main·Indexed 41 days ago

https://github.com/lucacasonato/esbuild_deno_loader

Provides Deno-compatible module resolution and loading for esbuild. It enables esbuild to understand Deno-specific specifiers such as npm:, jsr:, and file/https/data URLs, as well as import maps. The package includes the denoPlugins() helper, a denoResolverPlugin for absolutization and import maps, and a denoLoaderPlugin that supports both native (via Deno binary) and portable (via Web APIs) loading strategies.

Tokens
4.5K
Snippets
13
Records
20
Agent score
52%

What's inside esbuild-deno-loader

  1. How the Deno integration works

    main

    The Deno integration is composed of two primary plugins that work together to handle Deno's unique module resolution and loading logic:

    1. The Resolver: Handles absolutization of specifiers (making relative paths absolute) and processes import maps (including those embedded in deno.json).
    2. The Loader: Takes fully resolved specifiers and handles the actual loading of the module, including processing redirects until a final module is found.

    Most users should use denoPlugins() to instantiate both plugins automatically in the correct order.

  2. Integrate with custom plugins

    main

    If you need to add custom plugins that handle specific file extensions or URL schemes, you should manually instantiate the Deno plugins instead of using denoPlugins().

    To ensure correct behavior, the denoResolverPlugin should be the first plugin in your plugin array. If you have a custom loader plugin, insert it between the Deno resolver and the Deno loader.

    Plugin Order for Custom Schemes:

    1. denoResolverPlugin (Resolves relative paths/import maps to absolute URLs)
    2. Your Custom Plugin (Handles specific schemes/extensions)
    3. denoLoaderPlugin (Handles http, https, data, and file schemes)
  3. Use esbuild-deno-loader to bundle Deno modules

    main

    To bundle Deno projects using esbuild, use the denoPlugins() function to provide support for file:, https:, data:, npm:, and jsr: specifiers, as well as import maps. This function returns an array of esbuild.Plugin instances that should be spread into the plugins array of your esbuild.build configuration.

    Note: If you are running in an environment where subprocesses are not permitted (like Deno Deploy) or you are not using the --allow-run flag, you must use the Wasm build of esbuild instead of the default one.

    import * as esbuild from "npm:esbuild@0.20.2";
    import { denoPlugins } from "jsr:@luca/esbuild-deno-loader@^0.11.1";
    
    const result = await esbuild.build({
      plugins: [...denoPlugins()],
      entryPoints: ["https://deno.land/std@0.185.0/bytes/mod.ts"],
      outfile: "./dist/bytes.esm.js",
      bundle: true,
      format: "esm",
    });
    
    console.log(result.outputFiles);
    
    esbuild.stop();
  4. How to use denoResolverPlugin with denoLoaderPlugin

    main

    To correctly resolve and load Deno modules in esbuild, you must follow a specific plugin order. The denoResolverPlugin handles the logic of turning relative paths and import map specifiers into absolute URLs. The denoLoaderPlugin then handles the actual loading of those files.

    Crucial Requirement: The denoResolverPlugin must appear in your plugins array before the denoLoaderPlugin.

    Mental Model:

    1. denoResolverPlugin intercepts the resolution request.
    2. It resolves the specifier (e.g., ./utils.ts or an import map alias) to a fully qualified absolute URL.
    3. It passes that resolved URL back to esbuild, which then triggers the next plugin in the chain (the loader) to handle the file content.
  5. Understand the difference between Native and Portable loaders

    main

    The denoLoaderPlugin supports two distinct loading strategies depending on your environment and permissions.

    Native Loader

    • Mechanism: Shells out to the Deno executable.
    • Permissions Required: --allow-read and --allow-run.
    • Caching: Shares the Deno download cache; files are not re-downloaded on subsequent builds.
    • Configuration: Respects deno.lock, DENO_DIR, DENO_AUTH_TOKENS, and deno.json settings.
    • NPM/JSR: NPM packages are resolved/cached via Deno's global mechanism. JSR specifiers work without restriction.

    Portable Loader

    • Mechanism: Uses Web APIs for downloading and caching.
    • Permissions Required: --allow-read and/or --allow-net.
    • Caching: Does not cache downloaded files; it re-downloads files on every build.
    • Configuration: Does not respect deno.lock, DENO_DIR, or DENO_AUTH_TOKENS.
    • NPM/JSR: NPM specifiers require a local node_modules directory (created via Deno's --node-modules-dir flag). JSR specifiers require a lockfile to be present to resolve.
  6. Configure denoLoaderPlugin options

    main

    The denoLoaderPlugin accepts a DenoLoaderPluginOptions object to customize how modules are loaded and resolved.

    Options

    OptionTypeDescription
    loader'native' | 'portable'Specifies the loading mechanism. Defaults to native if --allow-run permission is granted, otherwise portable.
    configPathstringAbsolute path to a deno.json config file. Used with loader: 'native' to load modules from the cache efficiently. Must be in sync with denoResolverPlugin's configPath.
    importMapURLstringURL to an import map file. Used with loader: 'native' to load modules from cache. Must be in sync with denoResolverPlugin's importMapURL.
    lockPathstringAbsolute path to a lock file. Required for resolving jsr: specifiers with the portable loader. Recommended for native loader to ensure de-duplication.
    nodeModulesDir'auto' | 'manual' | 'none'Controls NPM package handling. none uses the global cache. manual uses a specific node_modules directory. auto uses a local node_modules directory. Note: This is ignored when using the portable loader (which always uses manual).
  7. Configure InfoCache with InfoOptions

    main

    When instantiating InfoCache, you can provide an InfoOptions object to control how deno info is executed. Supported options include:

    • cwd: The current working directory for the command.
    • config: Path to a Deno configuration file.
    • importMap: Path to an import map file.
    • lock: Path to a Deno lock file.
    • nodeModulesDir: Controls how node_modules are handled. Supported values are 'auto', 'manual', or 'none'. Note that behavior for 'auto' and 'manual' varies depending on whether the Deno version starts with 1..
    const options: InfoOptions = {
      cwd: '/path/to/project',
      importMap: '/path/to/import_map.json',
      nodeModulesDir: 'auto'
    };
    const cache = new InfoCache(options);
  8. Limitations of the portable loader

    main

    The "portable" loader mode has specific constraints compared to the native loader:

    • No Cache: It does not use the Deno module cache; all remote specifiers are downloaded on every run.
    • npm Dependencies: All npm: dependencies must be pre-downloaded into a local node_modules/ directory.
    • jsr Dependencies: When using jsr: specifiers, a lockfile must be present and passed to the loader via configPath or lockPath.
    • Wasm Restriction: npm: specifiers are not supported when using the Wasm build of esbuild due to filesystem access limitations.
  9. Required Deno permissions

    main

    Depending on your usage, you may need to grant the following permissions to your Deno process:

    • --allow-read: Required if you need to resolve local files.
    • --allow-net: Required if you need to resolve remote files.
    • --allow-run: If provided, the plugin uses the deno binary to resolve remote files, allowing it to re-use the Deno module cache.
  10. Use InfoCache to resolve and cache Deno module entries

    main

    The InfoCache class provides an efficient way to resolve Deno module specifiers into detailed ModuleEntry objects. It uses deno info --json under the hood to fetch module metadata and caches the results to avoid redundant CLI calls. It also handles module redirects and provides access to npmPackage metadata.

    To use it, instantiate InfoCache with optional InfoOptions and call .get(specifier) to retrieve the module information.

    import { InfoCache } from './deno.ts';
    
    const cache = new InfoCache({
      cwd: './',
      importMap: './import_map.json'
    });
    
    // Get module entry details
    const moduleEntry = await cache.get('https://deno.land/std/http/server.ts');
    
    // If it's an npm module, you can get package details
    if (moduleEntry.kind === 'npm') {
      const pkg = cache.getNpmPackage(moduleEntry.npmPackage);
      console.log(pkg?.version);
    }
  11. Use the denoLoaderPlugin in esbuild

    main

    The denoLoaderPlugin is an esbuild plugin designed to load fully qualified file, http, https, and data URLs.

    Important Requirement: This plugin does not handle relative-to-absolute resolution or import map resolution. You must use the denoResolverPlugin before the denoLoaderPlugin in your esbuild configuration to ensure specifiers are correctly resolved to absolute URLs.

    import { denoLoaderPlugin } from "./path/to/plugin";
    
    // In your esbuild configuration:
    // plugins: [denoResolverPlugin(), denoLoaderPlugin()]
  12. Configure the denoResolverPlugin

    main

    The denoResolverPlugin performs relative-to-absolute specifier resolution and import map resolution for Deno projects. It is designed to work alongside the denoLoaderPlugin; if you are using both, you must use the denoResolverPlugin before the loader plugin in your esbuild configuration.

    Options

    OptionTypeDescription
    configPathstring (optional)An absolute path to a deno.json config file. This is equivalent to the --config flag in Deno. If omitted, the plugin searches for the nearest deno.json or workspace root.
    importMapURLstring (optional)A URL (local file URL or remote) to an import map file. This is equivalent to the --import-map flag in Deno. If omitted, the plugin uses the import map defined in the deno.json file.
    import { denoResolverPlugin } from "./path_to_plugin";
    
    // Example configuration
    const plugin = denoResolverPlugin({
      configPath: "/absolute/path/to/deno.json",
      importMapURL: "https://example.com/import-map.json"
    });
    
    // Usage in esbuild
    // Note: Must be placed BEFORE the denoLoaderPlugin
    await esbuild.build({
      plugins: [plugin, /* denoLoaderPlugin here */],
      // ...
    });