Skip to content

CJS require() returns empty namespace because dist/index.js is a CJS bundle in a "type": "module" package #89

@deepentropy

Description

@deepentropy

Summary

require('oakscriptjs') in a CJS consumer returns an empty, frozen, null-prototype object. None of the 81 declared exports are reachable. ESM consumers (import 'oakscriptjs') are unaffected.

Reproduction

In any Node project that has oakscriptjs@0.2.7 installed:

node -e "const oak = require('oakscriptjs'); console.log(Object.keys(oak).length, typeof oak.getSourceSeries);"
# 0 undefined

Compare to ESM, which works:

node -e "import('oakscriptjs').then(m => console.log(Object.keys(m).length, typeof m.getSourceSeries));"
# 81 function

Root cause

package.json declares "type": "module" while pointing the CJS entry at a .js file:

{
  "type": "module",
  "main": "dist/index.js",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js"
    }
  }
}

With "type": "module", Node treats every .js file in the package as ESM, regardless of its contents. But dist/index.js is built by esbuild with --format=cjs and ends with:

module.exports = __toCommonJS(src_exports);

When require() resolves to dist/index.js, Node parses it as ESM. The module.exports = ... line is a no-op in ESM scope, so the returned namespace is empty. The compiled CJS exports never reach the consumer.

Downstream impact

lightweight-charts-indicators (which depends on oakscriptjs) crashes when imported via CJS:

TypeError: (0, import_oakscriptjs10.getSourceSeries) is not a function
  at calculate10 (.../lightweight-charts-indicators/dist/index.cjs:1321)

The crash surfaces in lightweight-charts-indicators, but the namespace it received from oakscriptjs was already empty.

Suggested fix

Rename the CJS bundle to .cjs and update both main and the require export:

{
  "type": "module",
  "main": "dist/index.cjs",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    },
    "./runtime": {
      "types": "./dist/runtime/index.d.ts",
      "import": "./dist/runtime/index.mjs",
      "require": "./dist/runtime/index.cjs"
    }
  }
}

And update the build scripts to emit .cjs:

- "build:cjs": "esbuild src/index.ts --bundle --format=cjs --outfile=dist/index.js ...",
+ "build:cjs": "esbuild src/index.ts --bundle --format=cjs --outfile=dist/index.cjs ...",
- "build:runtime:cjs": "esbuild src/runtime/index.ts --bundle --format=cjs --outfile=dist/runtime/index.js ...",
+ "build:runtime:cjs": "esbuild src/runtime/index.ts --bundle --format=cjs --outfile=dist/runtime/index.cjs ...",

Alternative: drop "type": "module" and keep the .js CJS naming, renaming only the ESM output to .mjs. Either option is correct; the key point is that the file extension and the package's module type must agree.

Environment

  • Node v24.11.0
  • pnpm-managed install
  • oakscriptjs 0.2.7

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions