Generate import map with mappings corresponding to node esm resolution algorithm. The importmap can be used to make code dependent on node module resolution executable in a browser.
Example of code relying on node 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
It will write mappings in index.html, inside a <script type="importmap">
.
It is also possible to write mappings into a separate file as follow:
npx @jsenv/importmap-node-module demo.importmap --entrypoint index.html
The CLI supports the following options:
--entrypoint
: Confirm the specified file and its transitive dependencies can be resolved using the generated import map. Auto enabled when importmap is written into .html file. Can be specified multiple times.--dev
: Include devDependencies from package.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 a few more options than the CLI.
1 - Create generate_importmap.mjs
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
"./index.html": {},
},
});
2 - Install dependencies
npm install --save-dev @jsenv/importmap-node-module
3 - Generate project.importmap
node ./generate_importmap.mjs
<script type="importmap"> content updated into "/demo/index.html"
writeImportmaps is an async function generating one or many importmap and writing them into files.
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
"./dev.importmap": {
nodeMappings: {
devDependencies: true,
packageUserConditions: ["development"],
},
importResolution: {
entryPoints: ["index.js"],
},
},
"./prod.importmap": {
nodeMappings: {
devDependencies: false,
packageUserConditions: ["production"],
},
importResolution: {
entryPoints: ["index.js"],
},
},
},
});
It supports the following options:
directoryUrl is a string/url leading to a folder with a package.json.
directoryUrl is required.
importmaps is an object where keys are file relative urls and value are objects configuring which mappings will be written in the files.
importmaps is required.
nodeMappings is an object configuring the mappings generated to implement node module resolution.
nodeMappings is optional.
Be sure node modules are on your filesystem because we'll use the filesystem structure to generate the importmap. For that reason, you must use it after
npm install
or anything that is responsible to generate the node_modules folder and its content on your filesystem.
In case you don't need to mappings corresponding to node resolution, they can be disabled:
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
"./demo.importmap": {
nodeMappings: false,
},
},
});
nodeMappings.devDependencies is a boolean. When enabled, mappings for "devDependencies"
declared in your package.json are generated.
nodeMappings.devDependencies is optional.
nodeMappings.packageUserConditions is an array controlling which conditions are favored in package.json conditions.
nodeMappings.packageUserConditions is optional.
The following conditions will be picked:
- conditions passed in nodeMappings.packageUserConditions
"import"
"browser"
"default"
Be sure to use
packageUserConditions: ["node"]
if the importmap is generated for node and not for the browser.
importResolution is an object. When passed the generated mappings will be used to resolve js imports found in entryPoints and their transitive dependencies. When a js import cannot be resolved a warning is logged. It is recommended to use importResolution as it gives confidence in the generated importmap.
importResolution is optional. When the importmap file is written inside a file ending with .html
the import resolution starts from the .html
file. Otherwise importResolution.entryPoints must be configured.
It is possible to disable importResolution entirely:
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
"./index.html": {
importResolution: false,
},
},
});
importResolution.entryPoints is an array composed of string representing file relative urls. Each file is considered as an entry point using the import mappings.
importResolution.entryPoints is optional.
importResolution.magicExtensions is an array of strings. Each string represent an extension that will be tried when an import cannot be resolved to a file.
importResolution.magicExtensions is optional.
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"],
},
},
},
});
"inherit"
means the extension tried in taken from the importer.
import "./helper";
importer path | path tried |
---|---|
/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.
importResolution.runtime is a string used to know how to resolve js imports.
For example the following import is correct when runtime is "node"
but would log a warning when runtime is "browser"
.
import { writeFile } from "node:fs";
importResolution.runtime is optional and defaults to "browser"
.
importResolution.keepUnusedMappings is a boolean. When enabled mappings will be kept even if not currently used by import found in js files.
importResolution.keepUnusedMappings is optional.
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
"./demo.html": {
importResolution: {
keepUnusedMappings: true,
},
},
},
});
manualImportmap is an object containing mappings that will be added to the importmap. This can be used to provide additional mappings and/or override node mappings.
manualImportmap is optional.
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./", import.meta.url),
importmaps: {
"./demo.importmap": {
manualImportmap: {
imports: {
"#env": "./env.js",
},
},
},
},
});
packagesManualOverrides is an object that can be used to override some of your dependencies package.json.
packagesManualOverrides is optional.
packagesManualOverrides exists in case some of your dependencies use non standard fields to configure their entry points in their package.json. Ideally they should use "exports"
field documented in https://nodejs.org/dist/latest-v16.x/docs/api/packages.html#packages_package_entry_points. But not every one has updated to this new field yet.
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",
},
},
},
});
At the time of writing this documentation external importmap are not supported by web browsers:
External import maps are not yet supported
If you plan to use importmap in a web browser you need to tell @jsenv/importmap-node-module
to inline importmap into the HTML file as shown in CLI.
This repository can generate importmap to make code produced by the TypeScript compiler executable in a browser.
You need to have your package.json and node_modules into the directory where typescript output js files. You can achieve this with the following "scripts" in your package.json.
{
"scripts": {
"prebuild": "rm -rf dist",
"build": "tsc",
"postbuild": "cp package.json dist && ln -sf ../node_modules ./dist/node_modules"
}
}
Then you can use the script below to produce the importmap.
import { writeImportmaps } from "@jsenv/importmap-node-module";
await writeImportmaps({
directoryUrl: new URL("./dist/", import.meta.url),
importmaps: {
"./index.html": {
importResolution: {
magicExtensions: ["inherit"],
},
},
},
});