feature/npm
Import Any npm Package in the Browser, With No node_modules
Type a package name, click install, and it is importable on the next render — resolved through esm.sh and written into a native import map you can read on the page.
<!-- written for you on install, never by hand -->
<script type="importmap">
{
"imports": {
"nanoid": "https://esm.sh/[email protected]",
"canvas-confetti": "https://esm.sh/[email protected]"
}
}
</script>
<script type="module">
import { nanoid } from 'nanoid';
import confetti from 'canvas-confetti';
confetti(); // resolved natively
console.log(nanoid()); // no node_modules
</script>
01 platform
What package management looks like with no install step
Most in-browser editors either block npm or fake a curated handful. XCODX uses the real registry — whether you are wiring a dependency into a project or using a playground to try a package before you add it locally.
-
Search, don't configure
Open the package panel, search the real npm registry, click install. The package is usable on the next render. There is no
package.jsonto hand-edit and no version string to look up first. -
No node_modules, ever
Nothing is written to a filesystem, because there is no install step in the Node sense. The dependency lives at a URL, so there is no
node_modulesfolder to grow, cache or delete. -
A native import map, not a bundler
XCODX writes a standard
<script type="importmap">. The browser does the resolution itself —import 'name'works because the map tells it wherenameis. No webpack, no Vite, no build. -
Private and offline-friendly
The import map is part of your project, stored locally. Once the browser has fetched a module it stays cached, so the project keeps resolving offline, and nothing about it is uploaded to a server.
02 deep dive
How a package name becomes a working import
The mechanism is a web standard, not a build tool — which is why it needs no toolchain to run.
A bare specifier needs a map. import { create } from 'zustand' uses a *bare specifier*, and a browser has no idea where zustand lives. Native import maps are the standard that tells it: a small JSON block mapping the name to a URL. XCODX writes that block for you and keeps it in sync as you install.
esm.sh turns npm into ES modules. The registry ships mostly CommonJS; a browser only runs ES modules. esm.sh sits between them — it takes the package from npm, converts it to a browser-ready ES module, bundles the package's own dependencies, and serves it over HTTP from one origin.
Version pinning is explicit. Each entry carries a version — [email protected], not a floating nanoid — so a reload resolves the exact code you installed, and a project you hand to someone else resolves to the same bytes on their machine.
- specifier
- bare name → URL
- registry
- esm.sh (from npm)
- format
- CommonJS → ES module
- versions
- pinned per package
import { create } from 'zustand';
import { nanoid } from 'nanoid';
import dayjs from 'dayjs';
// each bare name is resolved by the import map,
// not by a bundler and not at build time.
export const useStore = create((set) => ({
id: nanoid(),
at: dayjs().format('HH:mm:ss'),
tick: () => set({ at: dayjs().format('HH:mm:ss') }),
}));
03 pipeline
What happens when you install a package
Four steps, all client-side, between clicking install and calling the package. Knowing the order is what makes a resolution error readable.
-
Search
You type a name into the package panel. It queries the npm registry and lists matching packages with their current versions, so you install the one you meant rather than a typo-squat next to it.
-
Resolve
On install, XCODX asks esm.sh for that exact version. esm.sh builds a browser-ready ES module — converting CommonJS, bundling the package's own dependencies, and serving it all from a single origin.
-
Map
The resolved URL is written into your project's native import map under the bare name, so
import 'name'now has somewhere to point. The map is saved as part of the project. -
Import
The browser reads the import map and fetches the module itself. There is no bundling pass and no node_modules — the running page imports straight from the URL, on demand.
04 features
What you get with npm packages here
Everything below is on the moment you open the npm demo — which starts with four packages already resolving, so you can read a working import map before writing one.
-
The real registry
Search runs against npm itself, so the package you would install locally is the package you get here. The demo opens with
nanoid,dayjs,lodashandcanvas-confettialready mapped. -
Works with any framework file
The same import map serves a React
.jsx, a Vue SFC or a plain.jsmodule. A package is a package regardless of what imports it — there is no per-framework install. -
Types come along
esm.sh serves a package's TypeScript definitions, so an imported package still autocompletes and type-checks in the Monaco editor exactly as it would in a local project.
-
Read the map yourself
The import map is an ordinary file in your project. Open it to see which URL each name resolves to, and pin or change a version by hand when you want to.
-
CSS and assets resolve too
Packages that ship stylesheets map the same way the JavaScript does, so
import 'package/dist/style.css'pulls the file in without a loader to configure. -
Export takes it with you
Export the project as a zip and the import map goes with it. The same names resolve when you open it elsewhere, with no install to run again.
05 deep dive
What esm.sh can and cannot serve
A browser is not Node, and pretending otherwise would only waste your time. Here is the honest boundary, and what to reach for on the far side of it.
Pure-JavaScript packages work. Anything built on web-platform APIs — state libraries, date and validation utilities, UI kits, charting, canvas and animation tools, most of the front-end ecosystem — resolves and runs unchanged.
Node built-ins mostly do not. A package that reaches for fs, child_process or a raw socket has no equivalent in a browser tab. esm.sh polyfills a few (path, buffer), but a library whose job is to touch the filesystem cannot do it here, and no browser editor can change that.
Native add-ons never will. Anything that compiles a .node binary — sharp, bcrypt, better-sqlite3 — needs a build toolchain on a server. Reach instead for a WebAssembly build of the same capability, which does run in the tab.
Server frameworks run elsewhere. The server half of Express or Next expects a Node process listening on a port, and the browser has none to give it. The client half of an isomorphic library imports and runs normally.
- pure JS
- works
- path, buffer
- polyfilled
- fs, sockets
- no browser equivalent
- native .node
- needs a server
install better-sqlite3
esm.sh: native add-on (better_sqlite3.node)
→ needs a build toolchain, unavailable in a browser
try: sql.js — SQLite compiled to WebAssembly
06 workflow
From package search to running code
The whole loop, with nothing installed to your machine and no dependency folder left behind.
- search confetti
- canvas-confetti 1.9.3 · 2 more results
- install canvas-confetti
- resolved via esm.sh · import map updated · 0 files written
- import confetti from 'canvas-confetti'
- resolved → https://esm.sh/[email protected]
- save main.js
- preview reloaded · confetti() fired
- export project.zip
- import map bundled · resolves the same anywhere
-
Nothing to install locally
There is no
npm installto wait on and no node_modules to delete later — the dependency is a URL your project remembers. -
The same loop on a phone
Resolution happens in the browser and on esm.sh, so an iPad installs a package the same way a laptop does, with no local runtime involved.
-
Shareable by link or zip
Send a live preview URL or export the zip; either way the import map travels with the project and resolves identically on the other end.
07 ecosystem
One registry, every framework
A package installed here imports the same way whether the file beside it is React, Vue, Svelte or plain JavaScript — the import map does not care what consumes it.
08 languages
What the editor compiles and runs around your packages
Packages sit alongside everything else the editor understands — the languages it compiles in the browser, the ones it renders natively, and the 70+ it can execute.
Compiled in your browser11
Zero build step. Transform runs on a Web Worker, so typing never blocks.
- JSX
- TSX
- TypeScript
- Vue SFC
- Svelte
- Angular (JIT)
- SolidJS
- SCSS
- Sass
- Less
- Pug
Rendered natively8
Straight to the live preview, no transform in the way.
- HTML
- CSS
- JavaScript
- JSON
- Markdown
- Jupyter (.ipynb)
- SVG
- YAML
Executed on demand22
70+ languages run through a self-hosted Piston engine. stdout, stderr, images and tracebacks land in the console.
- Python
- Java
- C
- C++
- C#
- Go
- Rust
- Ruby
- PHP
- Kotlin
- Swift
- Lua
- Scala
- Perl
- Haskell
- Elixir
- Dart
- R
- Julia
- SQLite
- Bash
- PowerShell
09 questions
npm in the browser — common questions
Can I use npm packages without installing Node or a bundler?
Yes — that is the whole point. XCODX resolves each package through esm.sh and writes a native import map, so the browser does the loading itself. There is no local Node, no webpack or Vite config, and no node_modules folder anywhere.
Is this the real npm registry, or a curated subset?
The real registry. Package search queries npm directly, and esm.sh builds the module from the same published package you would install locally, so the version you run here matches the one you would get from a terminal.
Why is there no node_modules folder?
Because nothing is downloaded to a filesystem. A native import map points each package name at a URL on esm.sh, and the browser fetches it on demand, so a local install directory has nothing to hold and does not exist.
How do I pin or change a package version?
Every entry in the import map carries an explicit version, such as [email protected]. Open the map — it is an ordinary file in your project — and edit the version, or install a different one from the package panel and it rewrites the entry for you.
What about packages that only ship CommonJS?
esm.sh converts CommonJS to a browser-ready ES module on the fly, so most CommonJS-only packages import normally. The rare exception is a package that inspects its own module format at runtime, which is uncommon in front-end code.
Do packages that need Node APIs or native add-ons work?
No. A package that calls fs, opens a socket or compiles a native .node binary needs a server rather than a browser tab, and no in-browser editor can run those. For databases or image work, a WebAssembly build of the same tool does run here.
Can I import a package's CSS or its TypeScript types?
Yes. Stylesheets shipped inside a package resolve through the same import map, and esm.sh serves the package's type definitions, so imports still autocomplete and type-check in the Monaco editor.
How is this different from adding a script tag from a CDN?
A CDN script tag drops a global onto the page. An import map lets you write standard import statements with bare names, pin a version per package, and carry the resolution inside the project — the same code you would write in a bundled app.
Does an installed package still work offline?
Once the browser has fetched a module it is cached, so a project keeps resolving offline as a Progressive Web App. Only the first install of a brand-new package needs a connection.
Do I need to install anything to use XCODX Studio?
No. XCODX Studio is a browser-based IDE — the editor, the compilers and the preview all run client-side. There is no download, no package manager to set up and no configuration file to write. Opening the editor is the whole setup step.
Where is my code stored?
In your own browser. Projects, file history and Git objects live in local storage on your device and are never uploaded to a server. That also means XCODX works offline: it is a Progressive Web App, so you can install it and keep coding with no connection.
Is XCODX Studio free?
Yes. The editor, the live preview, the 70+ language runtimes, npm installs, Git and the integrated terminal are all free to use.
Can I use XCODX on a tablet or Chromebook?
Yes. The editor is a responsive web app, so a Chromebook, an iPad or an Android tablet gets the same compilers and the same preview a laptop does — there is no desktop-only build, because there is no build to install at all. The layout adapts to the screen rather than shrinking a desktop UI into it.
Can I export a project or move it to my own machine?
Yes. Export the whole project as a zip with its folder structure intact and open it locally, or commit it and push. It is ordinary source on disk from that point on — no proprietary format and no lock-in, which is also why importing an existing repository from GitHub works the same way in reverse.
Nothing to install
Try npm packages in the browser now
Four packages are already resolving in the demo — install a fifth and watch the import map rewrite itself.
