Skip to content

docs: add Deno to the package manager tabs#2395

Open
bartlomieju wants to merge 7 commits into
expressjs:mainfrom
bartlomieju:add-deno-package-manager
Open

docs: add Deno to the package manager tabs#2395
bartlomieju wants to merge 7 commits into
expressjs:mainfrom
bartlomieju:add-deno-package-manager

Conversation

@bartlomieju

@bartlomieju bartlomieju commented Jun 18, 2026

Copy link
Copy Markdown

👋 Bartek from the Deno team here.

This adds Deno as a fifth option to the <PackageManagerCommand> tabs, alongside npm / yarn / pnpm / bun. Since the equivalents are derived at build time from the authored npm command (via convertPackageManagerCommand), Deno now appears on every install snippet across the site and in every language, with no per-page changes.

Mappings added (Deno is a drop-in package manager for the npm ecosystem):

  • npm install <pkg>deno add <pkg> (e.g. deno add express)
  • npm install (no packages) → deno install
  • npm uninstall <pkg>deno remove <pkg>
  • npm initdeno init
  • npm create <starter>deno create --npm <starter>
  • npm run <script>deno task <script>
  • npm exec <pkg> / npx <pkg>deno x <pkg>

Following the existing pattern, commands with no clean Deno equivalent return null, so the Deno tab is simply omitted there (e.g. npm install <pkg> --no-save, global installs).

I verified locally with astro dev: the install page renders a Deno tab showing deno add express (and deno init for npm init), the --no-save snippet correctly omits Deno, and prettier --check passes. Every Deno command was checked against the Deno 2.8 CLI.

Two files changed: the converter (src/utils/package-manager.ts) and the MANAGERS list in the component's tab-switching script.

Totally fine to close this if you'd prefer not to add another tab. Happy to adjust any of the mappings.

Disclosure: I work on Deno, so I have an interest here. The goal is parity with the package managers you already list, not promotion. This PR was prepared with AI assistance under human supervision: I review every change myself and personally respond to any comments or review feedback.

@bartlomieju bartlomieju requested a review from a team as a code owner June 18, 2026 09:43
@netlify

netlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Deploy Preview for expressjscom-preview ready!

Name Link
🔨 Latest commit 1d743b8
🔍 Latest deploy log https://app.netlify.com/projects/expressjscom-preview/deploys/6a39ed1583a7600008c51e77
😎 Deploy Preview https://deploy-preview-2395--expressjscom-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 99 (🟢 up 2 from production)
Accessibility: 100 (no change from production)
Best Practices: 100 (no change from production)
SEO: 100 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

- npx <pkg> -> deno x <pkg> (was deno run -A npm:<pkg>)
- npm create <starter> -> deno create --npm <starter> (was omitted)
Comment thread src/utils/package-manager.ts Outdated
Comment on lines +109 to +111
// Deno adds to deno.json/package.json; it has no `--no-save`, and global
// tool installs use a different verb (`deno install -g`), so skip those.
deno: hasNoSave || isGlobal ? null : join('deno add', isDev && '--dev', pkgs, extras),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If deno has no --no-save then why do you return true instead of null (like you wrote in the description)1? For consistency it would be better to change this to chained ternary operators to match the structure used for other package managers (it would also fix the problem described in the previous sentence).

According to your docs deno add is an alias to deno install - using deno install would allow you to use this way of "assembling" the command using join to include global installs. And nothing prevents you from using deno install when isGlobal is true and deno add when it's false.

Footnotes

  1. LLMs generating code frequently write one thing in description and something completely else in code.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review, I did another pass on this one and corrected the mistake here and in deno x invocation. I also removed the comment here since it's not really needed.

- Use chained ternary like pnpm/bun; global installs -> deno install -g <pkg>
  (deno add is an alias of deno install), instead of returning null
- Drop the unnecessary exec ? ... : null conditional on the deno x mapping
Comment thread src/utils/package-manager.ts Outdated
yarn: isGlobal ? join('yarn global remove', pkgs) : join('yarn remove', pkgs),
pnpm: join('pnpm remove', isGlobal && '--global', pkgs),
bun: join('bun remove', isGlobal && '--global', pkgs),
deno: isGlobal ? null : join('deno remove', pkgs),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure that Deno allows uninstalling packages installed globally.

BTW Deno docs say that add is an alias of install and remove is an alias of uninstall, but aliases usually accept the same parameters.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch, actually it looks like deno remove doesn't accept --global. I'll fix it for Deno v2.9 and can open a follow up PR once it lands if that works for you.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks! Fixed — global removals now map to deno uninstall --global <pkg> (mirroring the yarn global remove branch) instead of being dropped. You're right that remove/uninstall are aliases, but only uninstall takes --global, so I used that for the global case.

@bjohansebas bjohansebas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Deno now actually install the package into node_modules so it can be executed with Node? I'm asking because Bun does, and that's why it's listed there. I wasn't sure about Deno because I didn't know whether it does that now, and officially Express doesn't support it (even though we all know it runs fine).

@bartlomieju

Copy link
Copy Markdown
Author

Does Deno now actually install the package into node_modules so it can be executed with Node? I'm asking because Bun does, and that's why it's listed there. I wasn't sure about Deno because I didn't know whether it does that now, and officially Express doesn't support it (even though we all know it runs fine).

@bjohansebas yes! You can use deno as a standalone package manager, it can set up node_modules/ that is compatible with npm output, it's pretty fast too: https://deno.com/blog/v2.8#deno-now-defaults-to-npm.

@krzysdz

krzysdz commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

it can set up node_modules/ that is compatible with npm output

If I'm reading this correctly it can, but doesn't by default:

By default, Deno instead resolves npm packages from a central global cache and does not create a node_modules directory.

https://docs.deno.com/runtime/fundamentals/node/#control-node_modules

@bartlomieju

Copy link
Copy Markdown
Author

it can set up node_modules/ that is compatible with npm output

If I'm reading this correctly it can, but doesn't by default:

By default, Deno instead resolves npm packages from a central global cache and does not create a node_modules directory.

https://docs.deno.com/runtime/fundamentals/node/#control-node_modules

Yes I wasn't precise. Currently node_modules/ is set up automatically if you have package.json (which Deno understands and can add it), or if you use deno.json for dependencies then you need nodeModulesDir: "auto" (or "manual"). These options are just causing confusion and will be phased out in Deno 3 to always create node_modules/ by default.

@bjohansebas bjohansebas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. An Express project naturally has a package.json, and if that automatically installs node_modules and can be run with Node, I'm more than happy with that.

I'll wait a bit to see if @expressjs/express-tc @expressjs/docs-wg has any thoughts on this, because right now I feel like I probably shouldn't have added Bun in the first place. That said, Bun is also a package manager, which is why I added it, and Deno is a package manager as well, so I feel comfortable adding it too.

@krzysdz

krzysdz commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

An Express project naturally has a package.json, and if that automatically installs node_modules and can be run with Node, I'm more than happy with that.

If someone uses deno init to initialise a project, then there will be no package.json, so no node_modules/ and the project won't be able to run with Node. I don't see any option in deno init docs that would create package.json instead of/in addition to deno.json.

It works as a package manager only in already existing projects and can't be used to initialise one.

@bartlomieju

Copy link
Copy Markdown
Author

Good distinction. The Deno tab here is the equivalent of the npm install express step, not npm init — and in a project that has a package.json (which any Express app does), deno install express writes to package.json and produces an npm-compatible node_modules/, so node app.js runs exactly like the npm/yarn/pnpm/bun tabs. You're right that deno init scaffolds a Deno-native project instead, but that's the init step, which this tab doesn't replace. Happy to add a one-line note that Deno users in a fresh dir should start from a package.json (or just deno run it) if you think that's clearer.

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.

3 participants