ES Module Serve
esmoduleserve is a lightweight, shim HTTP development server designed for serving ES modules directly in the browser without requiring a bundling step. Currently at version 0.3.1, it focuses on providing a direct ES module development experience by rewriting import specifiers on-the-fly to precise, resolved paths. It implements a Node-like module resolution algorithm, prioritizing `"module"` or `"jsnext"` fields in `package.json` over `"main"`. This differentiates it from more comprehensive dev servers (e.g., Vite, Webpack dev server) that typically integrate bundling, transpilation, and hot module reloading, by offering a simpler, unbundled approach for native ES module workflows. Its release cadence is likely infrequent, as it serves a specific, focused niche for rapid development and testing of native ES modules.
Common errors
-
Uncaught TypeError: Failed to resolve module specifier "some-package". Relative references must start with "./", "../", or "/".
cause A dependency referenced by your project (or one of its transitive dependencies) does not provide a native ES module build, or its `package.json` does not correctly specify its ES module entry point (e.g., the `module` or `exports` field is missing or points incorrectly).fixVerify that all your project's dependencies are ES module compatible. Check their `package.json` for a `module` or `exports` field pointing to an ES module entry. If not, you may need to find an ES module alternative or use a bundler. -
TypeError: Cannot destructure property 'ModuleServer' of require(...) as it is undefined.
cause This error occurs when attempting to use ES Module `import` syntax (`import { ModuleServer } from ...`) to import a CommonJS module. The `esmoduleserve/moduleserver` entry point is a CommonJS module.fixChange your import statement to use CommonJS `require` syntax: `const { ModuleServer } = require('esmoduleserve/moduleserver')`. -
Error: ENOTDIR: not a directory, open '/path/to/project/../some-module.js'
cause This typically indicates that the module server tried to resolve a module path that traverses beyond its configured `maxDepth` (defaulting to 1), preventing access to files in higher-level directories for security reasons.fixIf intentional, increase the `maxDepth` option in your `ModuleServer` configuration (e.g., `{ maxDepth: 2 }`) or the `--depth` flag if using the CLI, to allow access to more parent directories. Consider the security implications before increasing this value.
Warnings
- gotcha esmoduleserve is a shim for ES module resolution, not a bundler or transpiler. It will not process or convert CommonJS (CJS) modules, nor will it transpile modern JavaScript features for older browsers. If your dependencies are not distributed as native ES modules, they will likely fail to load, resulting in 'missing import' or 'module not found' errors in the browser console.
- gotcha The server's `--depth` option (or `maxDepth` in the `ModuleServer` constructor) defaults to `1`. This means it can only resolve modules one directory level above its configured `root` directory. This is a security measure to prevent accidental exposure of arbitrary files higher up the file system. Attempting to import modules from further parent directories will fail.
- gotcha The `esmoduleserve` library itself (specifically the `esmoduleserve/moduleserver` entry point) is packaged as a CommonJS module. Using direct ES Module `import` syntax (`import { ModuleServer } from 'esmoduleserve/moduleserver'`) in an ES module context will not work as expected and will typically lead to `TypeError: Cannot destructure property 'ModuleServer' of require(...) as it is undefined` or similar runtime errors.
Install
-
npm install esmoduleserve -
yarn add esmoduleserve -
pnpm add esmoduleserve
Imports
- ModuleServer
import { ModuleServer } from 'esmoduleserve/moduleserver'const { ModuleServer } = require('esmoduleserve/moduleserver')
Quickstart
const http = require('http');
const { ModuleServer } = require('esmoduleserve/moduleserver');
const path = require('path');
const fs = require('fs');
// Create a 'demo' directory and place an 'index.html' and 'my-module.js' inside for testing.
// Example index.html: <script type="module" src="/_m/my-module.js"></script>
// Example my-module.js: export const message = 'Hello from ES module!'; console.log(message);
const rootDir = path.join(__dirname, 'demo');
const moduleServer = new ModuleServer({
root: rootDir,
maxDepth: 2, // Allow access to modules one parent directory deep (e.g., for monorepos)
prefix: '_m' // The URL prefix for rewritten module scripts
});
const server = http.createServer((req, res) => {
console.log(`Request: ${req.method} ${req.url}`);
// First, try to handle the request as a module through esmoduleserve
if (moduleServer.handleRequest(req, res)) {
return; // Request was handled
}
// If not a module request, serve static files (e.g., index.html)
const requestedPath = req.url === '/' ? '/index.html' : req.url;
const filePath = path.join(rootDir, requestedPath);
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found: ' + requestedPath);
console.error(`File not found: ${filePath}`);
return;
}
let contentType = 'text/plain';
if (filePath.endsWith('.html')) contentType = 'text/html';
else if (filePath.endsWith('.js')) contentType = 'application/javascript';
else if (filePath.endsWith('.css')) contentType = 'text/css';
res.writeHead(200, { 'Content-Type': contentType });
res.end(data);
});
});
const port = process.env.PORT ?? 8080;
const host = process.env.HOST ?? 'localhost';
server.listen(port, host, () => {
console.log(`esmoduleserve server running at http://${host}:${port}/`);
console.log(`Serving static files from: ${rootDir}`);
console.log(`Module scripts are available under the /_m/ prefix (e.g., http://${host}:${port}/_m/my-module.js)`);
});