@bobfrankston/importgen
v0.1.28
Published
Generate ES Module import maps from package.json dependencies for native browser module loading
Downloads
1,271
Maintainers
Readme
@bobfrankston/importgen
Use the same npm packages and import statements in the browser as in Node.js/Bun -- no bundler required.
Why
In Node.js you write:
import { formatDate } from "date-utils";Node resolves date-utils by looking in node_modules. Browsers can't do that. Normally you'd need a bundler (webpack, rollup, etc.) to make bare module specifiers work.
Browsers now support import maps -- a <script type="importmap"> block that maps package names to file paths. importgen reads your package.json and generates this import map automatically, injecting it into your HTML file. Your code stays identical between Node and browser -- transparent, no changes needed.
Install
npm install -g @bobfrankston/importgenUsage
Run from your project directory (containing package.json and your HTML file):
importgen [htmlfile] [--watch|-w] [--freeze] [--unfreeze] [--version|-v]# Generate import map in index.html (default)
importgen
# Specify a different HTML file
importgen default.htm
importgen app.html
# Watch mode: regenerate whenever package.json changes
importgen --watch
importgen default.htm --watch
# Show version
importgen -v
# Freeze: replace symlinks/junctions with real copies for deployment
importgen --freeze
# Unfreeze: restore symlinks and run npm install
importgen --unfreezeimportgen reads dependencies from package.json in the current directory and injects the import map into the HTML file.
If no HTML file is specified, it searches for index.html, default.html, default.htm (in that order) and uses the first one found.
If the HTML already has a <script type="importmap"> block, it is replaced in place. Otherwise the block is inserted before </head>.
What It Generates
Given a package.json like:
{
"dependencies": {
"date-utils": "^2.0.0",
"my-ui-lib": "file:../shared/my-ui-lib"
}
}importgen produces:
<!-- Generated by importgen 0.1.14 on Feb 9, 2026, 3:45:12 PM EST -->
<script type="importmap">
{
"imports": {
"date-utils": "./node_modules/date-utils/index.js",
"my-ui-lib": "./node_modules/my-ui-lib/index.js",
"color-convert": "./node_modules/color-convert/index.js"
}
}
</script>A timestamp comment is added above the import map showing the version and local date/time. It is updated on each regeneration.
Transitive dependencies are included automatically -- if my-ui-lib depends on color-convert, it appears in the map too. Circular dependencies are detected and handled.
Using Imports in the Browser
Once the import map is in your HTML, <script type="module"> code uses the same imports as Node:
// This works identically in Node AND the browser
import { formatDate } from "date-utils";
import { render } from "my-ui-lib";
import { initApp } from "./app.js";The browser sees date-utils, looks it up in the import map, and loads ./node_modules/date-utils/index.js. Local relative imports (./app.js) work as always without the import map.
The key point: your TypeScript source files are the same whether they run in Node or the browser. importgen just provides the mapping the browser needs to find the packages.
How Entry Points Are Resolved
For each dependency, importgen reads that package's package.json and picks the entry point in this order:
exports["."].importorexports["."].default(modern ESM)modulefield (ESM convention)mainfield./index.js(fallback)
Dependency Types
- npm versions (
^1.2.3) -- resolved fromnode_modules/ file:paths (file:../path/to/package) -- resolved relative topackage.jsonworkspace:references -- resolved fromnode_modules/
.dependencies Override
Add a .dependencies field to package.json to override resolution paths for specific packages without changing the real dependencies:
{
"dependencies": {
"my-utils": "^1.0.0"
},
".dependencies": {
"my-utils": "file:../local-my-utils"
}
}The import map will use the .dependencies path instead of the node_modules path for that package.
Freeze / Unfreeze
--freeze prepares a project for deployment by replacing all symlinks and junctions in node_modules with real directory copies. This is useful when file: or workspace: dependencies point to local packages via symlinks that won't exist on a deployment target.
Freeze also adds a preinstall guard script to package.json that prevents accidental npm install from overwriting the frozen copies. If a preinstall script already exists, it is saved as frozen-preinstall so it can be restored later.
--unfreeze reverses the process: it removes the preinstall guard (restoring any original preinstall script), then runs npm install to re-establish normal symlinked dependencies.
VS Code tasks.json Integration
Add importgen --watch as a background task that starts automatically when you open the project. Combined with tsc --watch, your import map stays current as you develop.
Example .vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "tsc: watch",
"type": "shell",
"command": "tsc",
"args": ["--watch"],
"runOptions": {
"runOn": "folderOpen"
},
"problemMatcher": "$tsc-watch",
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "importgen: watch",
"type": "shell",
"command": "importgen",
"args": ["--watch"],
"runOptions": {
"runOn": "folderOpen"
},
"isBackground": true,
"problemMatcher": {
"pattern": {
"regexp": "^$",
"file": 1,
"location": 2,
"message": 3
},
"background": {
"activeOnStart": true,
"beginsPattern": "^\\[generate-importmap\\].*watching.*$",
"endsPattern": "^\\[generate-importmap\\].*Updated.*$"
}
}
}
]
}Key points:
"runOn": "folderOpen"starts both tasks automatically when you open the workspace"isBackground": truekeeps them running as persistent watchers- The custom
problemMatcherfor importgen tells VS Code this is a long-running watcher, not a one-shot build - If your HTML file isn't
index.html, add it to the args:"args": ["default.htm", "--watch"]
Workflow: edit package.json (add/remove a dependency) -> importgen regenerates the import map in the HTML -> tsc recompiles TypeScript -> browser picks up changes on reload.
PWA Compatible
Generated import maps are static and can be cached by service workers.
License
MIT
