Skip to content

Conversation

@dsherret
Copy link
Member

@dsherret dsherret commented Oct 22, 2025

This is still controversial internally. Just starting a PR.

In my opinion, we need a way to represent dev dependencies in Deno. Right now it's not possible to tell what are dev dependencies and what is actually used in the distributed code.

  1. It's something developers are familiar with from npm.
  2. Statically analyzable dev deps are useful.
    1. Deno can error when a dev dependency is used in production code (ex. you're publishing a package to JSR and accidentally have an import to a dev dependency).
    2. We can exclude unused dev deps from deno compile
    3. deno audit can figure out what are dev deps with this. Right now it has no clue.
    4. It's clear documentation when working on a project which deps are used only for development.
  3. It can be a way to easily include npm phantom deps in production or optional peer dependencies without having to import the package in the production code in order to include it.

Idea from Nayeem is to introduce first-class support for dependencies and devDependencies. The "imports" key remains as Deno having support for import maps, but this new dependencies/devDependencies would be Deno specific.

  • This would be an optional feature.
    • Users can continue using the import map standard, but have a non-optimal experience (the current experience)
    • It wouldn't be used for resolving dependencies within jsr packages. It would only be for the top level.
  • Ideally we should also provide a lint rule and deno lint --fix to change imports into dependencies when able.

Proposal

  1. Represent dev dependencies in the config file
  2. Get rid of the duplication that happens in an import map where the package name can be repeated twice
  3. Still support aliasing
  4. Still be able to specify whether using npm or jsr
// deno.json
{
  "dependencies": {
    "chalk": "npm:^5"
  },
  "devDependencies": {
    "@david/dax": "jsr:^0.43.2",
    // will use the tag name because no version req
    "package": "npm:tag",
    // has a version req, so it's the package and not a tag
    "alias": "npm:code-block-writer@11"
  }
}

Imported as:

import "chalk";
import "@david/dax";
import "package";
import "alias";

Why not devImports in addition to imports?

This wouldn't be too bad, but it would be nice to move away from the import map standard for describing dependencies so the description can be more terse (not repeat the package name twice in the entry) and also to make it clear this is different than the import map standard. Perhaps we should still do this to reduce confusion with how imports are supposed to be done in Deno and make this more of an additive change.

Closes #26865
Closes #26865

@Hajime-san
Copy link
Contributor

I agree with the goal of optimizing deno compile. At the same time, I believe import maps are an excellent feature and one of Deno's key characteristics.

Therefore, I would like to propose adding a new feature to the import map standard. I share the concern about creating a Deno-specific extension, so I have tried to shape this proposal in a way that also offers clear benefits to browser runtimes.

(Please note that I used Gemini to help formulate this proposal.)


Proposal: "Conditional Imports" for Import Maps

This proposal applies the concept of Node.js "Conditional Exports" to the Import Map specification by introducing a new top-level key: "conditions".

The runtime (Deno or the browser) activates a set of host-defined conditions based on its current state (e.g., "development", "browser", "worker"). If a definition exists inside the "conditions" block that matches an active condition, its contents are merged with (and override) the default "imports" and "scopes" blocks.

Example import_map.json

{
  "imports": {
    // --- Default (production, window) imports ---
    "react": "httpsGas://cdn.example/react.production.min.js",
    "storage": "./storage-main-thread.js" // Uses localStorage
  },

  "conditions": {
    
    // A host-defined condition, e.g., for development
    "development": {
      "imports": {
        "react": "httpsGas://cdn.example/react.development.js"
      }
    },
    
    // Browser context for Dedicated/Shared Workers (type: "module")
    "worker": {
      "imports": {
        "storage": "./storage-worker.js" // Uses IndexedDB
      }
    },

    // Browser context for ServiceWorkers (type: "module")
    "serviceworker": {
      "imports": {
        "storage": "./storage-serviceworker.js" // Uses Cache API
      }
    }
  }
}

This proposal becomes realistic for web standardization because it solves significant problems for browsers, not just for Deno.

Key Browser Use Case 1: Execution Context (Window vs. Worker vs. ServiceWorker)

This is likely the most compelling use case for browsers.

  • The Problem: The main thread (Window), Dedicated/Shared Workers (WorkerGlobalScope), and Service Workers (ServiceWorkerGlobalScope) have different capabilities and available APIs. A module might rely on document or localStorage, which are unavailable in workers. Today, this requires branching inside the module (e.g., if (typeof window !== 'undefined')), which complicates code and can pull unnecessary dependencies into worker contexts.
  • The Solution: The browser would automatically activate the "worker" or "serviceworker" condition when importing a module from within that context, assuming it was loaded as a module (e.g., new Worker("...", { type: "module" }) or navigator.serviceWorker.register("...", { type: "module" })).
  • Example:
    • import "storage" in main.js (Window context) resolves to ./storage-main-thread.js.
    • import "storage" in a module-based Dedicated Worker resolves to ./storage-worker.js.
    • import "storage" in a module-based ServiceWorker resolves to ./storage-serviceworker.js.
    • The application code remains clean (import "storage") in all contexts.

Key Browser Use Case 2: Development vs. Production Builds

This mirrors the Deno use case and is a common requirement for web development.

  • The Problem: Developers want to use un-minified, debug-friendly builds of libraries (e.g., react.development.js) during development but switch to optimized, production-only builds (react.production.min.js) for deployment.
  • How it would work: The "development" condition is a host-defined condition. A host (like Deno or a browser) could activate it in several ways:
    1. Deno (Host): Activated via a flag like deno test or deno run --dev.
    2. Browser (Server-side): The server sends a specific HTTP response header (e.g., Import-Map-Conditions: "development") with the main HTML document.
    3. Browser (Client-side): The user enables a setting directly within the browser's Developer Tools (e.g., "Enable Development Conditions for Import Maps").

Advantages of this Approach

  1. Web Standard Alignment: It extends the existing standard rather than creating a proprietary fork, benefiting the entire ecosystem.
  2. Solves Browser Problems: It provides a clean solution for the Window-vs-Worker-vs-ServiceWorker context problem, a challenge that the Deno-specific proposal in this PR does not address.
  3. Flexibility: It's a generic mechanism. While Deno is interested in "development", browsers can add "worker", "serviceworker", or other future contexts as needed, and Deno can also benefit from these (e.g., Deno's own worker contexts).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

question: confusion about adding developer/dev dependencies

2 participants