Generate import maps for browser-based execution of code that relies on Node.js module resolution
This package generates import maps that implement Node.js ESM resolution algorithm. With these import maps, code dependent on Node.js module resolution becomes executable in browsers.
Example of code relying on Node.js module resolution:
import lodash from "lodash";
- Use
package.json
andnode_modules/**/package.json
to generate mappings corresponding to node esm resolution algorithm - (Optional) Test importmap against all import found in js module files. This step allow to remove unused mappings to keep only thoose actually used in the codebase
- Write mappings into a file
The simplest way to use this project is with npx
:
npx @jsenv/importmap-node-module index.html
This writes mappings in index.html
inside a <script type="importmap">
tag.
You can also write mappings to a separate file:
npx @jsenv/importmap-node-module demo.importmap --entrypoint index.html
The CLI supports the following options:
--entrypoint <file>
: Confirm the specified file and its transitive dependencies can be resolved using the generated import map. Auto-enabled when importmap is written into an HTML file. Can be specified multiple times.--dev
: Include devDependencies frompackage.json
. Also favor"development"
in package exports↗.--keep-unused
: Keep all mappings, even if they are not currently used by entry file or its transitive dependencies.
The API supports more options than the CLI.
1 - Create a script file (e.g., generate_importmap.mjs
):
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
"./index.html": {}, // Will embed importmap in index.html
},
});
2 - Install dependencies
npm install --save-dev @jsenv/importmap-node-module
3 - Run the script:
node ./generate_importmap.mjs
# <script type="importmap"> content updated into "/demo/index.html"
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
// Generate a development importmap
"./dev.importmap": {
nodeMappings: {
devDependencies: true,
packageUserConditions: ["development"],
},
importResolution: {
entryPoints: ["index.js"],
},
},
// Generate a production importmap
"./prod.importmap": {
nodeMappings: {
devDependencies: false,
packageUserConditions: ["production"],
},
importResolution: {
entryPoints: ["index.js"],
},
},
},
});
The writeImportmaps function generates and writes one or more import maps to files.
Path to a folder containing a package.json
file.
An object where keys are file paths (relative URLs) and values are configuration objects for the mappings to be written in those files.
Each entry in the importmaps
object can have the following configuration:
Configuration for mappings generated to implement Node.js module resolution. Set to false to disable Node.js mappings entirely.
Note: Node modules must be present on your filesystem as the structure is used to generate the importmap. Run
npm install
before generating importmaps.
When true
, mappings for devDependencies
declared in your package.json
are generated.
Controls which package.json conditions are favored.
nodeMappings.packageUserConditions is optional.
Conditions are picked in this order:
- conditions passed in
packageUserConditions
"import"
"browser"
"default"
User
packageUserConditions: ["node"]
if generating an importmap for Node.js instead of browsers.
When provided, the generated mappings will be used to resolve JS imports found in entryPoints and their transitive dependencies. Set to false
to disable entirely.
When the importmap is written to a file ending with .html
, import resolution automatically starts from the HTML file (unless disabled).
Array of file paths (relative URLs) to use as entry points for imports.
Array of extensions to try when an import cannot be resolved to a file.
Use "inherit"
to try the same extension as the importing file:
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
"./demo.importmap": {
importResolution: {
entryPoints: ["./demo.js"],
magicExtensions: ["inherit", ".js"], // Try importer's extension, then .js
},
},
},
});
Example of extension inheritance:
importer path | Path tried when importing "./helper" |
---|---|
/Users/dmail/file.js | /Users/dmail/helper.js |
/Users/dmail/file.ts | /Users/dmail/helper.ts |
All other values in magicExtensions are file extensions that will be tried one after an other.
Specifies the runtime environment to determine how to resolve JS imports. Defaults to "browser"
.
For example, import { writeFile } from "node:fs"
is valid when runtime is "node"
but would log a warning with "browser"
.
When true
, mappings will be kept even if they aren't used by any imports found in JS files.
An object containing mappings to add to the importmap. Useful for providing additional mappings or overriding node mappings.
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
"./demo.importmap": {
manualImportmap: {
imports: {
"#env": "./env.js",
},
},
},
},
});
An object to override package.json
files of dependencies.
Useful when dependencies use non-standard fields to configure entry points instead of the standard "exports"
field described in the Node.js documentation
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
"./demo.importmap": {},
},
// overrides "react-redux" package because it uses a non-standard "module" field
// to expose "es/index.js" entry point
// see https://github.com/reduxjs/react-redux/blob/9021feb9ff573b01b73084f1a7d10b322e6f0201/package.json#L18
packageManualOverrides: {
"react-redux": {
exports: {
import: "./es/index.js",
},
},
},
});
As of this documentation's writing, browsers don't support external import maps:
External import maps are not yet supported
For browser usage, instruct @jsenv/importmap-node-module
to inline importmap into an HTML file as shown in CLI section.
This tool can generate import maps to make TypeScript-compiled code executable in browsers.
You need your package.json
and node_modules
in the directory where TypeScript outputs JS files. Add these scripts to your package.json
:
{
"scripts": {
"prebuild": "rm -rf dist",
"build": "tsc",
"postbuild": "cp package.json dist && ln -sf ../node_modules ./dist/node_modules"
}
}
Then generate the importmap with:
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./dist/", import.meta.url),
importmaps: {
"./index.html": {
importResolution: {
magicExtensions: ["inherit"],
},
},
},
});