Harness-engineering node-esm-patterns

Node.js ESM Patterns

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/node-esm-patterns" ~/.claude/skills/intense-visions-harness-engineering-node-esm-patterns && rm -rf "$T"
manifest: agents/skills/claude-code/node-esm-patterns/SKILL.md
source content

Node.js ESM Patterns

Write Node.js ES modules correctly using import.meta.url, package.json type, and CJS interop

When to Use

  • Setting up a Node.js project with ES modules
  • Converting CommonJS code to ES modules
  • Handling
    __dirname
    and
    __filename
    equivalents in ESM
  • Interoperating between ESM and CommonJS modules

Instructions

  1. Enable ESM in
    package.json
    :
{ "type": "module" }

All

.js
files are now ESM. Use
.cjs
extension for CommonJS files.

  1. Replace
    __dirname
    and
    __filename
    :
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const configPath = join(__dirname, 'config.json');
  1. Dynamic imports:
const { default: chalk } = await import('chalk');

// Conditional imports
const db =
  process.env.DB === 'postgres' ? await import('./db/postgres.js') : await import('./db/sqlite.js');
  1. Import JSON with import assertions:
import config from './config.json' with { type: 'json' };
// Or dynamically
const pkg = await import('./package.json', { with: { type: 'json' } });
  1. Import CommonJS modules from ESM:
// Default import for CJS modules
import express from 'express';

// Named imports may not work for all CJS modules
// Use default import + destructure instead
import pkg from 'lodash';
const { pick, omit } = pkg;
  1. Top-level await:
// Works at module top level in ESM (not in CJS)
const config = await loadConfig();
const db = await connectDatabase(config.dbUrl);

export { db, config };
  1. File extensions are required in imports:
// CJS: works without extension
const { foo } = require('./utils');

// ESM: extension required
import { foo } from './utils.js'; // .js even for .ts files with Node16 module resolution
  1. Configure TypeScript for ESM:
{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "ES2022"
  }
}

Details

ES modules are the standard module system for JavaScript. Node.js supports them natively since v12 (stable since v14). ESM is statically analyzable, supports tree-shaking, and uses

import
/
export
syntax.

ESM vs CJS differences:

  • ESM:
    import
    /
    export
    , static analysis, async loading, strict mode by default
  • CJS:
    require
    /
    module.exports
    , dynamic, synchronous, non-strict by default
  • ESM can import CJS; CJS cannot
    require()
    ESM (use dynamic
    import()
    instead)

import.meta
: ESM-only global with module metadata:

  • import.meta.url
    — file URL of the current module (
    file:///path/to/module.js
    )
  • import.meta.resolve()
    — resolve a module specifier relative to the current module

Dual package publishing: Libraries can support both ESM and CJS consumers:

{
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  }
}

Trade-offs:

  • ESM enables tree-shaking — but requires file extensions in imports
  • Top-level await is convenient — but blocks module loading for all importers
  • Static imports enable bundler optimizations — but dynamic imports are needed for conditional loading
  • CJS interop mostly works — but named imports from CJS can fail depending on how the CJS module exports

Source

https://nodejs.org/api/esm.html

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.