From f7609023d93e625d42c5a3a314f8db0382261ad3 Mon Sep 17 00:00:00 2001 From: Ben Asher Date: Tue, 6 Oct 2026 21:20:32 -0500 Subject: [PATCH] Fix interop with cjs-module-lexer Split src/index.js into a thin re-export over a new src/validator-main.js so the compiled CommonJS keeps statically analyzable named exports. Node uses cjs-module-lexer to expose named ESM imports from CJS modules; the previous single-file default-export build hid every export behind a dynamically assembled object, so `import { isEmail } from 'validator'` failed while `import validator from 'validator'` worked. - src/validator-main.js holds the full module body as named exports, byte-identical to master's index.js modulo the export wrapper - src/index.js re-exports it and re-exports the namespace as default - validator-main.js added to package files, .gitignore, and the CI artifact upload (test jobs download build artifacts) - build-browser.mjs bundles from a default-only virtual entry, keeping the browser bundle's UMD surface (window.validator, AMD, CJS) exactly as before - test/exports.test.js asserts every runtime export is statically detectable by cjs-module-lexer, following index.js's re-exports; the test registers as skipped where the lexer cannot load (Node 8) --- .github/workflows/ci.yml | 1 + .gitignore | 1 + build-browser.mjs | 15 ++- package.json | 2 + src/index.js | 251 +-------------------------------------- src/validator-main.js | 250 ++++++++++++++++++++++++++++++++++++++ test/exports.test.js | 24 ++++ 7 files changed, 294 insertions(+), 250 deletions(-) create mode 100644 src/validator-main.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b6dd4795..3ecdedb0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,7 @@ jobs: name: build-artifacts path: | index.js + validator-main.js lib/ es/ validator.js diff --git a/.gitignore b/.gitignore index 86aaedee1..492f25ae6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ yarn.lock /index.js validator.js validator.min.js +/validator-main.js diff --git a/build-browser.mjs b/build-browser.mjs index b770acb94..61b93d151 100644 --- a/build-browser.mjs +++ b/build-browser.mjs @@ -5,9 +5,22 @@ import { babel } from '@rollup/plugin-babel'; import babelPresetEnv from '@babel/preset-env'; import pkg from "./package.json" with { type: "json" }; +// Bundle from a default-only entry so Rollup keeps emitting the classic +// single-export UMD wrapper (define(factory), module.exports = factory()), +// leaving the browser bundle's surface unchanged by the named exports that +// src/index.js adds for cjs-module-lexer. +const browserEntry = { + name: 'browser-entry', + resolveId: id => (id === 'browser-entry' ? id : null), + load: id => (id === 'browser-entry' + ? "import * as validator from './src/validator-main'; export default validator;" + : null), +}; + rollup({ - input: 'src/index.js', + input: 'browser-entry', plugins: [ + browserEntry, babel({ presets: [[babelPresetEnv, { modules: false, diff --git a/package.json b/package.json index 4a76409af..8550db4b4 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "homepage": "https://github.com/validatorjs/validator.js", "files": [ "index.js", + "validator-main.js", "es", "lib", "README.md", @@ -43,6 +44,7 @@ "@rollup/plugin-babel": "^6.1.0", "babel-eslint": "^10.0.1", "babel-plugin-add-module-exports": "^1.0.0", + "cjs-module-lexer": "^2.3.0", "eslint": "^4.19.1", "eslint-config-airbnb-base": "^12.1.0", "eslint-plugin-import": "^2.11.0", diff --git a/src/index.js b/src/index.js index 5e2cd78a7..d42705ddd 100644 --- a/src/index.js +++ b/src/index.js @@ -1,251 +1,4 @@ -import toDate from './lib/toDate'; -import toFloat from './lib/toFloat'; -import toInt from './lib/toInt'; -import toBoolean from './lib/toBoolean'; -import equals from './lib/equals'; -import contains from './lib/contains'; -import matches from './lib/matches'; - -import isEmail from './lib/isEmail'; -import isURL from './lib/isURL'; -import isMACAddress from './lib/isMACAddress'; -import isIP from './lib/isIP'; -import isIPRange from './lib/isIPRange'; -import isFQDN from './lib/isFQDN'; -import isDate from './lib/isDate'; -import isTime from './lib/isTime'; - -import isBoolean from './lib/isBoolean'; -import isLocale from './lib/isLocale'; - -import isAbaRouting from './lib/isAbaRouting'; -import isAlpha, { locales as isAlphaLocales } from './lib/isAlpha'; -import isAlphanumeric, { locales as isAlphanumericLocales } from './lib/isAlphanumeric'; -import isNumeric from './lib/isNumeric'; -import isPassportNumber, { locales as passportNumberLocales } from './lib/isPassportNumber'; -import isPort from './lib/isPort'; -import isLowercase from './lib/isLowercase'; -import isUppercase from './lib/isUppercase'; - -import isIMEI from './lib/isIMEI'; - -import isAscii from './lib/isAscii'; -import isFullWidth from './lib/isFullWidth'; -import isHalfWidth from './lib/isHalfWidth'; -import isVariableWidth from './lib/isVariableWidth'; -import isMultibyte from './lib/isMultibyte'; -import isSemVer from './lib/isSemVer'; -import isSurrogatePair from './lib/isSurrogatePair'; - -import isInt from './lib/isInt'; -import isFloat, { locales as isFloatLocales } from './lib/isFloat'; -import isDecimal from './lib/isDecimal'; -import isHexadecimal from './lib/isHexadecimal'; -import isOctal from './lib/isOctal'; -import isDivisibleBy from './lib/isDivisibleBy'; - -import isHexColor from './lib/isHexColor'; -import isRgbColor from './lib/isRgbColor'; -import isHSL from './lib/isHSL'; - -import isISRC from './lib/isISRC'; - -import isIBAN, { locales as ibanLocales } from './lib/isIBAN'; -import isBIC from './lib/isBIC'; - -import isMD5 from './lib/isMD5'; -import isHash from './lib/isHash'; -import isJWT from './lib/isJWT'; - -import isJSON from './lib/isJSON'; -import isEmpty from './lib/isEmpty'; - -import isLength from './lib/isLength'; -import isByteLength from './lib/isByteLength'; - -import isULID from './lib/isULID'; -import isUUID from './lib/isUUID'; -import isMongoId from './lib/isMongoId'; - -import isAfter from './lib/isAfter'; -import isBefore from './lib/isBefore'; - -import isIn from './lib/isIn'; - -import isLuhnNumber from './lib/isLuhnNumber'; -import isCreditCard from './lib/isCreditCard'; -import isIdentityCard from './lib/isIdentityCard'; - -import isEAN from './lib/isEAN'; -import isISIN from './lib/isISIN'; -import isISBN from './lib/isISBN'; -import isISSN from './lib/isISSN'; -import isTaxID from './lib/isTaxID'; - -import isMobilePhone, { locales as isMobilePhoneLocales } from './lib/isMobilePhone'; - -import isEthereumAddress from './lib/isEthereumAddress'; - -import isCurrency from './lib/isCurrency'; - -import isBtcAddress from './lib/isBtcAddress'; - -import { isISO6346, isFreightContainerID } from './lib/isISO6346'; -import isISO6391 from './lib/isISO6391'; -import isISO8601 from './lib/isISO8601'; -import isRFC3339 from './lib/isRFC3339'; -import isISO15924 from './lib/isISO15924'; -import isISO31661Alpha2 from './lib/isISO31661Alpha2'; -import isISO31661Alpha3 from './lib/isISO31661Alpha3'; -import isISO31661Numeric from './lib/isISO31661Numeric'; -import isISO4217 from './lib/isISO4217'; - -import isBase32 from './lib/isBase32'; -import isBase58 from './lib/isBase58'; -import isBase64 from './lib/isBase64'; -import isDataURI from './lib/isDataURI'; -import isMagnetURI from './lib/isMagnetURI'; -import isMailtoURI from './lib/isMailtoURI'; - -import isMimeType from './lib/isMimeType'; - -import isLatLong from './lib/isLatLong'; -import isPostalCode, { locales as isPostalCodeLocales } from './lib/isPostalCode'; - -import ltrim from './lib/ltrim'; -import rtrim from './lib/rtrim'; -import trim from './lib/trim'; -import escape from './lib/escape'; -import unescape from './lib/unescape'; -import stripLow from './lib/stripLow'; -import whitelist from './lib/whitelist'; -import blacklist from './lib/blacklist'; -import isWhitelisted from './lib/isWhitelisted'; - -import normalizeEmail from './lib/normalizeEmail'; -import toString from './lib/util/toString'; - -import isSlug from './lib/isSlug'; -import isLicensePlate from './lib/isLicensePlate'; -import isStrongPassword from './lib/isStrongPassword'; - -import isVAT from './lib/isVAT'; - -const version = '13.15.35'; - -const validator = { - version, - toDate, - toFloat, - toInt, - toBoolean, - equals, - contains, - matches, - isEmail, - isURL, - isMACAddress, - isIP, - isIPRange, - isFQDN, - isBoolean, - isIBAN, - isBIC, - isAbaRouting, - isAlpha, - isAlphaLocales, - isAlphanumeric, - isAlphanumericLocales, - isNumeric, - isPassportNumber, - passportNumberLocales, - isPort, - isLowercase, - isUppercase, - isAscii, - isFullWidth, - isHalfWidth, - isVariableWidth, - isMultibyte, - isSemVer, - isSurrogatePair, - isInt, - isIMEI, - isFloat, - isFloatLocales, - isDecimal, - isHexadecimal, - isOctal, - isDivisibleBy, - isHexColor, - isRgbColor, - isHSL, - isISRC, - isMD5, - isHash, - isJWT, - isJSON, - isEmpty, - isLength, - isLocale, - isByteLength, - isULID, - isUUID, - isMongoId, - isAfter, - isBefore, - isIn, - isLuhnNumber, - isCreditCard, - isIdentityCard, - isEAN, - isISIN, - isISBN, - isISSN, - isMobilePhone, - isMobilePhoneLocales, - isPostalCode, - isPostalCodeLocales, - isEthereumAddress, - isCurrency, - isBtcAddress, - isISO6346, - isFreightContainerID, - isISO6391, - isISO8601, - isISO15924, - isRFC3339, - isISO31661Alpha2, - isISO31661Alpha3, - isISO31661Numeric, - isISO4217, - isBase32, - isBase58, - isBase64, - isDataURI, - isMagnetURI, - isMailtoURI, - isMimeType, - isLatLong, - ltrim, - rtrim, - trim, - escape, - unescape, - stripLow, - whitelist, - blacklist, - isWhitelisted, - normalizeEmail, - toString, - isSlug, - isStrongPassword, - isTaxID, - isDate, - isTime, - isLicensePlate, - isVAT, - ibanLocales, -}; +import * as validator from './validator-main'; +export * from './validator-main'; export default validator; diff --git a/src/validator-main.js b/src/validator-main.js new file mode 100644 index 000000000..4676bf936 --- /dev/null +++ b/src/validator-main.js @@ -0,0 +1,250 @@ +import toDate from './lib/toDate'; +import toFloat from './lib/toFloat'; +import toInt from './lib/toInt'; +import toBoolean from './lib/toBoolean'; +import equals from './lib/equals'; +import contains from './lib/contains'; +import matches from './lib/matches'; + +import isEmail from './lib/isEmail'; +import isURL from './lib/isURL'; +import isMACAddress from './lib/isMACAddress'; +import isIP from './lib/isIP'; +import isIPRange from './lib/isIPRange'; +import isFQDN from './lib/isFQDN'; +import isDate from './lib/isDate'; +import isTime from './lib/isTime'; + +import isBoolean from './lib/isBoolean'; +import isLocale from './lib/isLocale'; + +import isAbaRouting from './lib/isAbaRouting'; +import isAlpha, { locales as isAlphaLocales } from './lib/isAlpha'; +import isAlphanumeric, { locales as isAlphanumericLocales } from './lib/isAlphanumeric'; +import isNumeric from './lib/isNumeric'; +import isPassportNumber, { locales as passportNumberLocales } from './lib/isPassportNumber'; +import isPort from './lib/isPort'; +import isLowercase from './lib/isLowercase'; +import isUppercase from './lib/isUppercase'; + +import isIMEI from './lib/isIMEI'; + +import isAscii from './lib/isAscii'; +import isFullWidth from './lib/isFullWidth'; +import isHalfWidth from './lib/isHalfWidth'; +import isVariableWidth from './lib/isVariableWidth'; +import isMultibyte from './lib/isMultibyte'; +import isSemVer from './lib/isSemVer'; +import isSurrogatePair from './lib/isSurrogatePair'; + +import isInt from './lib/isInt'; +import isFloat, { locales as isFloatLocales } from './lib/isFloat'; +import isDecimal from './lib/isDecimal'; +import isHexadecimal from './lib/isHexadecimal'; +import isOctal from './lib/isOctal'; +import isDivisibleBy from './lib/isDivisibleBy'; + +import isHexColor from './lib/isHexColor'; +import isRgbColor from './lib/isRgbColor'; +import isHSL from './lib/isHSL'; + +import isISRC from './lib/isISRC'; + +import isIBAN, { locales as ibanLocales } from './lib/isIBAN'; +import isBIC from './lib/isBIC'; + +import isMD5 from './lib/isMD5'; +import isHash from './lib/isHash'; +import isJWT from './lib/isJWT'; + +import isJSON from './lib/isJSON'; +import isEmpty from './lib/isEmpty'; + +import isLength from './lib/isLength'; +import isByteLength from './lib/isByteLength'; + +import isULID from './lib/isULID'; +import isUUID from './lib/isUUID'; +import isMongoId from './lib/isMongoId'; + +import isAfter from './lib/isAfter'; +import isBefore from './lib/isBefore'; + +import isIn from './lib/isIn'; + +import isLuhnNumber from './lib/isLuhnNumber'; +import isCreditCard from './lib/isCreditCard'; +import isIdentityCard from './lib/isIdentityCard'; + +import isEAN from './lib/isEAN'; +import isISIN from './lib/isISIN'; +import isISBN from './lib/isISBN'; +import isISSN from './lib/isISSN'; +import isTaxID from './lib/isTaxID'; + +import isMobilePhone, { locales as isMobilePhoneLocales } from './lib/isMobilePhone'; + +import isEthereumAddress from './lib/isEthereumAddress'; + +import isCurrency from './lib/isCurrency'; + +import isBtcAddress from './lib/isBtcAddress'; + +import { isISO6346, isFreightContainerID } from './lib/isISO6346'; +import isISO6391 from './lib/isISO6391'; +import isISO8601 from './lib/isISO8601'; +import isRFC3339 from './lib/isRFC3339'; +import isISO15924 from './lib/isISO15924'; +import isISO31661Alpha2 from './lib/isISO31661Alpha2'; +import isISO31661Alpha3 from './lib/isISO31661Alpha3'; +import isISO31661Numeric from './lib/isISO31661Numeric'; +import isISO4217 from './lib/isISO4217'; + +import isBase32 from './lib/isBase32'; +import isBase58 from './lib/isBase58'; +import isBase64 from './lib/isBase64'; +import isDataURI from './lib/isDataURI'; +import isMagnetURI from './lib/isMagnetURI'; +import isMailtoURI from './lib/isMailtoURI'; + +import isMimeType from './lib/isMimeType'; + +import isLatLong from './lib/isLatLong'; +import isPostalCode, { locales as isPostalCodeLocales } from './lib/isPostalCode'; + +import ltrim from './lib/ltrim'; +import rtrim from './lib/rtrim'; +import trim from './lib/trim'; +import escape from './lib/escape'; +import unescape from './lib/unescape'; +import stripLow from './lib/stripLow'; +import whitelist from './lib/whitelist'; +import blacklist from './lib/blacklist'; +import isWhitelisted from './lib/isWhitelisted'; + +import normalizeEmail from './lib/normalizeEmail'; +import toString from './lib/util/toString'; + +import isSlug from './lib/isSlug'; +import isLicensePlate from './lib/isLicensePlate'; +import isStrongPassword from './lib/isStrongPassword'; + +import isVAT from './lib/isVAT'; + +const version = '13.15.35'; + +export { + version, + toDate, + toFloat, + toInt, + toBoolean, + equals, + contains, + matches, + isEmail, + isURL, + isMACAddress, + isIP, + isIPRange, + isFQDN, + isBoolean, + isIBAN, + isBIC, + isAbaRouting, + isAlpha, + isAlphaLocales, + isAlphanumeric, + isAlphanumericLocales, + isNumeric, + isPassportNumber, + passportNumberLocales, + isPort, + isLowercase, + isUppercase, + isAscii, + isFullWidth, + isHalfWidth, + isVariableWidth, + isMultibyte, + isSemVer, + isSurrogatePair, + isInt, + isIMEI, + isFloat, + isFloatLocales, + isDecimal, + isHexadecimal, + isOctal, + isDivisibleBy, + isHexColor, + isRgbColor, + isHSL, + isISRC, + isMD5, + isHash, + isJWT, + isJSON, + isEmpty, + isLength, + isLocale, + isByteLength, + isULID, + isUUID, + isMongoId, + isAfter, + isBefore, + isIn, + isLuhnNumber, + isCreditCard, + isIdentityCard, + isEAN, + isISIN, + isISBN, + isISSN, + isMobilePhone, + isMobilePhoneLocales, + isPostalCode, + isPostalCodeLocales, + isEthereumAddress, + isCurrency, + isBtcAddress, + isISO6346, + isFreightContainerID, + isISO6391, + isISO8601, + isISO15924, + isRFC3339, + isISO31661Alpha2, + isISO31661Alpha3, + isISO31661Numeric, + isISO4217, + isBase32, + isBase58, + isBase64, + isDataURI, + isMagnetURI, + isMailtoURI, + isMimeType, + isLatLong, + ltrim, + rtrim, + trim, + escape, + unescape, + stripLow, + whitelist, + blacklist, + isWhitelisted, + normalizeEmail, + toString, + isSlug, + isStrongPassword, + isTaxID, + isDate, + isTime, + isLicensePlate, + isVAT, + ibanLocales, +}; + diff --git a/test/exports.test.js b/test/exports.test.js index 283b11540..e051d66e8 100644 --- a/test/exports.test.js +++ b/test/exports.test.js @@ -1,4 +1,6 @@ import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; import validator from '../index'; import { locales as isPostalCodeLocales } from '../src/lib/isPostalCode'; import { locales as isAlphaLocales } from '../src/lib/isAlpha'; @@ -8,6 +10,15 @@ import { locales as isFloatLocales } from '../src/lib/isFloat'; import { locales as ibanCountryCodes } from '../src/lib/isIBAN'; import { locales as passportNumberLocales } from '../src/lib/isPassportNumber'; +let parse = null; +try { + // eslint-disable-next-line global-require + ({ parse } = require('cjs-module-lexer')); +} catch (e) { + // cjs-module-lexer uses the optional catch binding (ES2019), which + // Node 8 cannot parse, so requiring it throws a SyntaxError there. +} + describe('Exports', () => { it('should export isPassportNumbers\'s supported locales', () => { assert.ok(passportNumberLocales instanceof Array); @@ -32,6 +43,19 @@ describe('Exports', () => { assert.strictEqual(validator.toString(undefined), ''); }); + (parse ? it : it.skip)('should be statically analyzable by cjs-module-lexer', () => { + const detectable = new Set(); + const visit = (file) => { + const { exports, reexports } = parse(fs.readFileSync(file, 'utf8'), file); + exports.forEach(name => detectable.add(name)); + reexports.forEach(specifier => visit(path.join(path.dirname(file), `${specifier}.js`))); + }; + visit(path.join(__dirname, '..', 'index.js')); + Object.keys(validator).forEach((name) => { + assert.ok(detectable.has(name), `cjs-module-lexer cannot detect export "${name}"`); + }); + }); + it('should export the version number', () => { /* eslint-disable global-require */ assert.strictEqual(