Skip to content

esm-dev/esm.sh

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

esm.sh

Docker Discord Sponsors

esm.sh

A global fast and smart CDN(aka content delivery network) for modern(es2015+) web development.

How to Use

esm.sh allows you to import es6 modules from a URL directly in the browser or Deno. No install/build step needed.

import * as mod from "https://esm.sh/PKG[@SEMVER][/PATH]";

or use bare specifier instead of URL with import maps:

<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/[email protected]"
  }
}
</script>
<script type="module">
  import React from "react" // alias to https://esm.sh/[email protected]
</script>

More details check out here.

Supported Registries

  • NPM(default):
    // Examples
    import React from "https://esm.sh/react"; // 18.3.0 (latest)
    import React from "https://esm.sh/react@17"; // 17.0.2
    import React from "https://esm.sh/react@beta"; // 19.0.0-beta-94eed63c49-20240425
    import { renderToString } from "https://esm.sh/[email protected]/server"; // submodules
  • Github (starts with /gh/):
    // Examples
    import tslib from "https://esm.sh/gh/microsoft/[email protected]"; // '2.6.0' is the git tag name
    // or fetch an asset file from github
    fetch("https://esm.sh/gh/microsoft/fluentui-emoji/assets/Party%20popper/Color/party_popper_color.svg");
  • JSR (starts with /jsr/):
    // Examples
    import * as mod from "https://esm.sh/jsr/@std/[email protected]/base64";
    import { html } from "https://esm.sh/jsr/@mark/html@1";

Specifying Dependencies

By default, esm.sh rewrites import specifiers based on the package dependencies. To specify the version of these dependencies, you can add the ?deps=PACKAGE@VERSION query. To specify multiple dependencies, separate them with a comma, like this: [email protected],[email protected].

import React from "https://esm.sh/[email protected]";
import useSWR from "https://esm.sh/[email protected]";

Aliasing Dependencies

import useSWR from "https://esm.sh/swr?alias=react:preact/compat";

in combination with ?deps:

import useSWR from "https://esm.sh/swr?alias=react:preact/compat&[email protected]";

The original idea came from @lucacasonato.

Tree Shaking

By default, esm.sh exports a module with all its exported members. However, if you want to import only a specific set of members, you can specify them by adding a ?exports=foo,bar query to the import statement.

import { __await, __rest } from "https://esm.sh/tslib"; // 7.3KB
import { __await, __rest } from "https://esm.sh/tslib?exports=__await,__rest"; // 489B

By using this feature, you can take advantage of tree shaking with esbuild and achieve a smaller bundle size. Note that this feature is only supported for ESM modules and not CJS modules.

Bundling Strategy

By default, esm.sh bundles sub-modules that ain't declared in the exports field.

Bundling sub-modules can reduce the number of network requests for performance. However, it may bundle shared modules repeatedly. In extreme case, it may break the side effects of the package, or change the import.meta.url semantics. To avoid this, you can add ?bundle=false to disable the default bundling behavior:

import "https://esm.sh/@pyscript/core?bundle=false";

For package authors, you can override the bundling strategy by adding the esm.sh field to package.json:

{
  "name": "foo",
  "esm.sh": {
    "bundle": false // disables the default bundling behavior
  }
}

esm.sh also supports ?standalone query to bundle the module with all external dependencies(except in peerDependencies) into a single JS file.

import { Button } from "https://esm.sh/antd?standalone";

Development Mode

import React from "https://esm.sh/react?dev";

With the ?dev option, esm.sh builds a module with process.env.NODE_ENV set to "development" or based on the condition development in the exports field. This is useful for libraries that have different behavior in development and production. For example, React uses a different warning message in development mode.

ESBuild Options

By default, esm.sh checks the User-Agent header to determine the build target. You can also specify the target by adding ?target, available targets are: es2015 - es2022, esnext, deno, denonext, node and bun.

import React from "https://esm.sh/react?target=es2020";

Other supported options of esbuild:

  • Conditions
    import foo from "https://esm.sh/foo?conditions=custom1,custom2";
  • Keep names
    import foo from "https://esm.sh/foo?keep-names";
  • Ignore annotations
    import foo from "https://esm.sh/foo?ignore-annotations";

Web Worker

esm.sh supports ?worker query to load the module as a web worker:

import createWorker from "https://esm.sh/monaco-editor/esm/vs/editor/editor.worker?worker";

// create a worker
const worker = createWorker();
// rename the worker by adding the `name` option
const worker = createWorker({ name: "editor.worker" });
// inject code into the worker
const worker = createWorker({ inject: "self.onmessage = (e) => self.postMessage(e.data)" });

You can import any module as a worker from esm.sh with the ?worker query. Plus, you can access the module's exports in the inject code. For example, uing the xxhash-wasm to hash strings in a worker:

import createWorker from "https://esm.sh/[email protected]?worker";

// variable '$module' is the imported 'xxhash-wasm' module
const inject = `
const xxhash = $module.default
self.onmessage = async (e) => {
  const hasher = await xxhash()
  self.postMessage(hasher.h64ToString(e.data))
}
`;
const worker = createWorker({ inject });
worker.onmessage = (e) => console.log("hash is", e.data);
worker.postMessage("The string that is being hashed");

Note: The inject must be a valid JavaScript code, and it will be executed in the worker context.

Package CSS

<link rel="stylesheet" href="https://esm.sh/monaco-editor?css">

This only works when the package imports CSS files in JS directly.

Importing WASM as Module

esm.sh supports importing wasm modules in JS directly, to do that, you need to add ?module query to the import URL:

import wasm from "https://esm.sh/@dqbd/[email protected]/tiktoken_bg.wasm?module";

const { exports } = new WebAssembly.Instance(wasm, imports);

Note: The *.wasm?module pattern requires the top-level-await feature to be supported by the browser.

Using Import Maps

Import Maps has been supported by most modern browsers and Deno natively. This allows bare import specifiers, such as import React from "react", to work.

esm.sh introduces the ?external=foo,bar query for specifying external dependencies. By employing this query, esm.sh maintains the import specifier intact, leaving it to the browser/Deno to resolve based on the import map. For example:

{
  "imports": {
    "preact": "https://esm.sh/[email protected]",
    "preact-render-to-string": "https://esm.sh/[email protected]?external=preact"
  }
}

Alternatively, you can mark all dependencies as external by adding a * prefix before the package name:

{
  "imports": {
    "preact": "https://esm.sh/[email protected]",
    "preact-render-to-string": "https://esm.sh/*[email protected]",
    "swr": "https://esm.sh/*[email protected]",
    "react": "https://esm.sh/[email protected]/compat"
  }
}

Import maps supports trailing slash that can not work with URL search params friendly. To fix this issue, esm.sh provides a special format for import URL that allows you to use query params with trailing slash: change the query prefix ? to & and put it after the package version.

{
  "imports": {
    "react-dom": "https://esm.sh/[email protected]?dev",
    "react-dom/": "https://esm.sh/[email protected]&dev/"
  }
}

Escape Hatch: Raw Source Files

In rare cases, you may want to request JS source files from packages, as-is, without transformation into ES modules. To do so, you need to add a ?raw query to the request URL.

For example, you might need to register a package's source script as a service worker in a browser that does not yet support the type: "module" option:

await navigator.serviceWorker.register(
  new URL(
    "https://esm.sh/playground-elements/playground-service-worker.js?raw",
    import.meta.url.href,
  ),
  { scope: "/" },
);

You may alternatively use raw.esm.sh as the origin, which is equivalent to esm.sh/<PATH>?raw:

<playground-project sandbox-base-url="https://raw.esm.sh/playground-elements/"></playground-project>

so that transitive references in the raw assets will also be raw requests.

Deno Compatibility

esm.sh is a Deno-friendly CDN that resolves Node's built-in modules (such as fs, os, net, etc.), making it compatible with Deno.

import express from "https://esm.sh/express";

const app = express();
app.get("/", (req, res) => {
  res.send("Hello World");
});
app.listen(3000);

For users using deno < 1.33.2, esm.sh uses deno.land/[email protected]/node as the node compatibility layer. You can specify a different version by adding the ?deno-std=$VER query:

import postcss from "https://esm.sh/express?deno-std=0.128.0";

Deno supports type definitions for modules with a types field in their package.json file through the X-TypeScript-Types header. This makes it possible to have type checking and auto-completion when using those modules in Deno. (link).

Figure #1

In case the type definitions provided by the X-TypeScript-Types header is incorrect, you can disable it by adding the ?no-dts query to the module import URL:

import unescape from "https://esm.sh/lodash/unescape?no-dts";

This will prevent the X-TypeScript-Types header from being included in the network request, and you can manually specify the types for the imported module.

Supporting Nodejs/Bun

Nodejs(18+) supports http importing under the --experimental-network-imports flag. Bun doesn't support http modules yet.

We highly recommend Reejs as the runtime with esm.sh that works both in Nodejs and Bun.

Global CDN

The Global CDN of esm.sh is provided by Cloudflare, one of the world's largest and fastest cloud network platforms.

Self-Hosting

To host esm.sh by yourself, check the hosting documentation.