Skip to content

fix(build): add .js extensions to relative imports in the ES module build - #2933

Open
joshuablac wants to merge 1 commit into
validatorjs:masterfrom
joshuablac:fix/es-import-extensions-2924
Open

joshuablac wants to merge 1 commit into
validatorjs:masterfrom
joshuablac:fix/es-import-extensions-2924

Conversation

@joshuablac

Copy link
Copy Markdown

Fixes #2924 (related: #2528)

Problem

Node.js requires relative import paths in ES modules to include the file extension. The files in es/ import each other without one (import assertString from './util/assertString'), so importing anything from es/ in Node fails.

Reproduced on Node.js 26.7.0 with the built package copied into an app's node_modules/validator. Paths below are shortened to /app.

// app.mjs
import isEmail from 'validator/es/lib/isEmail.js';
console.log(isEmail('foo@bar.com'));

Before:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/node_modules/validator/es/lib/util/assertString' imported from /app/node_modules/validator/es/lib/isEmail.js

After:

true

Cause

build:es runs Babel with modules: false, which keeps the import statements as they are written in src/, without extensions. Bundlers resolve those paths, but Node's ESM resolver does not.

Fix

  • babel-plugin-es-import-extensions.js: a small local Babel plugin (no new dependency). For import, export ... from and export * from with a relative path, it:
    • leaves the path alone if it already points to a file;
    • otherwise appends .js when <path>.js exists;
    • otherwise fails the build with "Cannot resolve ... to a file", so a directory import or a typo cannot slip through.
  • .babelrc: the plugin is added to the es env only. The development env (CommonJS build and tests) is unchanged. The browser build uses babelrc: false and is unaffected.

The plugin file sits at the repo root next to build-browser.mjs. npm pack --dry-run confirms that the plugin, .babelrc and test/ are not in the tarball, while es/ still is.

I did not change the README example (import isEmail from 'validator/es/lib/isEmail';). It works with bundlers, and the README's Node ESM import paths are the subject of open PR #2642.

Tests

  • New test/esBuild.test.js: walks the built es/ directory and asserts that every relative import path ends in .js and points to an existing file.
    • It fails on the current build: index.js imports "./lib/toDate", which is not a path to a file.
    • It passes with this change.
    • It uses only fs.readdirSync/fs.statSync/fs.existsSync (no withFileTypes), so it should also run on the Node 8 job in the CI matrix. I have not run it on Node 8 locally.
  • npm test (build + lint + mocha) on Node.js 26.7.0:
    • before: 323 passing, 0 failing;
    • after: 324 passing, 0 failing.

Other checks on Node.js 26.7.0, comparing the build before and after this change:

  • lib/, index.js, validator.js and validator.min.js are byte-identical.
  • In es/, 103 of 114 files change, and only on their relative import lines. Each path gains .js, and Babel prints the rewritten paths with double quotes instead of single quotes. The other 11 files have no relative imports and are byte-identical.
  • From the same node_modules/validator layout, all 114 es/ files can be loaded with import(). Before the change, 103 of them failed with ERR_MODULE_NOT_FOUND.
  • Rolling up es/index.js with the repo's rollup gives byte-identical output before and after, so bundler users see no change.
  • In the same ESM test app, import validator from 'validator' (CommonJS) and validator/lib/isUUID.js still work after the change. index.js and lib/ are byte-identical to before.

Note: validator's package.json has no "type": "module". Node therefore loads es/*.js as ES modules only through module syntax detection, which is on by default in Node.js 26.7.0, the only version I tested. On Node versions that treat these files as CommonJS, importing es/ from Node still won't work. This change doesn't alter that.

Checklist

This fix was written with AI assistance (Claude Code) and verified locally as described above.

🤖 Generated with Claude Code

…uild

Node.js requires relative import paths in ES modules to include the file
extension, so importing any file from `es/` (for example
`validator/es/lib/isEmail.js`) failed with ERR_MODULE_NOT_FOUND on
`./util/assertString`.

Add a small local Babel plugin, used only by the `es` env in `.babelrc`, that
appends `.js` to relative import and re-export paths when the target is a
`.js` file, and fails the build if a relative path does not resolve to a file.
The CommonJS (`lib/`, `index.js`) and browser builds are unchanged.

Fixes validatorjs#2924

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 16, 2026 15:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov

codecov Bot commented Oct 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (9ff3424) to head (cd5794e).

Additional details and impacted files
@@            Coverage Diff            @@
##            master     #2933   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          114       114           
  Lines         2599      2599           
  Branches       658       658           
=========================================
  Hits          2599      2599           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ES build of the library must use explicit extensions in imports

2 participants