faker icon indicating copy to clipboard operation
faker copied to clipboard

Faker v10 ESM-only breaks compatibility with Jest

Open leandromatos opened this issue 4 months ago • 25 comments

MAINTAINER EDIT:

[!NOTE] See the current workaround in this discussion.


Problem Description

The recent release of Faker v10 introduced a breaking change by removing CommonJS support entirely, making it ESM-only. This breaks compatibility with many existing TypeScript/JavaScript projects that use Jest and other testing frameworks in CommonJS environments.

Real-world Impact

I'm experiencing this issue in a TypeScript monorepo with the following setup:

  • NestJS API (CommonJS-based)
  • Database package using Faker in factories for testing
  • Jest for unit testing with complex configuration (env-cmd, tsconfig-paths, etc.)

After upgrading to Faker v10, all tests fail with:

Cannot use import statement outside a module Must use import to load ES Module: @faker-js/faker/dist/index.js

Technical Analysis

Why ESM-only is problematic:

  1. Jest ecosystem compatibility: Jest's ESM support is still experimental and fragile
  2. Complex project configurations: Projects with env-cmd, path mapping, and custom setups break
  3. Framework limitations: NestJS and many other frameworks still primarily use CommonJS
  4. Forced ecosystem migration: Requires migrating entire testing stack, not just faker

Current workarounds are insufficient:

  • Transform ignore patterns: Doesn't work reliably with complex Jest setups
  • Jest ESM experimental: Unstable and breaks other tooling
  • Manual mocking: Defeats the purpose of using faker
  • Downgrading to v9: Only temporary solution

Comparison with Other Libraries

Most mature libraries handle this transition better by maintaining dual package exports:

  • TypeScript itself maintains both ESM and CommonJS
  • Lodash provides separate packages
  • Many testing utilities kept backward compatibility

Suggested Solution: Restore CommonJS Support

Provide dual exports like many other libraries:

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

Business Impact

This breaking change affects:

  • Enterprise projects with complex testing setups
  • CI/CD pipelines that suddenly fail
  • Development teams forced to spend time on tooling instead of features
  • Library adoption - teams may switch to alternatives

Conclusion

While ESM is the future, forcing adoption by breaking existing projects is counterproductive. The faker library is widely used in testing environments that are inherently conservative and slow to change.

Please consider restoring CommonJS support or at least providing clearer migration paths for complex real-world setups.


Environment:

  • @faker-js/faker: v10.0.0
  • TypeScript: 5.9.x
  • Jest: 30.0.x
  • Node.js: 24.3.x

leandromatos avatar Aug 24 '25 13:08 leandromatos

Thanks for reporting this issue. Most common uses of CommonJS should still work with Faker v10.0, see the migration guide at https://fakerjs.dev/guide/upgrading. We're looking into why this may not work with certain environments. If you have a minimal reproducible example that would be very useful.

matthewmayer avatar Aug 24 '25 14:08 matthewmayer

I'm using const { faker } = require('@faker-js/faker'); and it throws SyntaxError: Cannot use import statement outside a module.

mjlehrke avatar Aug 24 '25 18:08 mjlehrke

@mjlehrke could you send us your tsconfig.json and package.json? Which Node version do you use?

Shinigami92 avatar Aug 24 '25 18:08 Shinigami92

Hey @Shinigami92, here is my TSConfig from my NestJS API.

{
  "compilerOptions": {
    // Basic Options
    "target": "ESNext",
    "module": "NodeNext",
    "lib": ["ESNext"],
    "rootDir": "./",
    "outDir": "./dist",
    "baseUrl": "./",
    "paths": { "@/*": ["src/*"] },

    // Module Resolution Options
    "moduleResolution": "NodeNext",
    "moduleDetection": "force",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,

    // Emit Options
    "declaration": true,
    "sourceMap": true,
    "emitDecoratorMetadata": true,

    // Experimental Options
    "experimentalDecorators": true,

    // Additional Checks
    "skipLibCheck": true,
    "isolatedModules": true,

    // Strict Type-Checking Options
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "strictPropertyInitialization": false
  },
  "include": ["drizzle.config.ts", "src"]
}

leandromatos avatar Aug 24 '25 19:08 leandromatos

Ok, as this is a combination we are officially try to support, we need to investigate what's going on. Because in our playground for that everything was working.

https://github.com/faker-js/playground/blob/main/playgrounds/cjs/tsconfig.node20.json

except... 🤔 if "module": "Node20" works differently than "module": "NodeNext" 👀 I assumed that NodeNext is an alias to the latest NodeX version.

If you want to speed up the progress, feel free to checkout the playground and try to add a minimal reproducible to that via a PR. Then we could even avoid regressions in the future.

Shinigami92 avatar Aug 24 '25 19:08 Shinigami92

I am also seeing something similar when trying to use Faker v10 with jest. This is a straight CommonJS project (all *.js files with no TS, babel or other explicit transpilation) but I still get this:

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation, specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:
...

    SyntaxError: Cannot use import statement outside a module

      1 | /* Header */
      2 |
    > 3 | const { faker } = require('@faker-js/faker')

My jsconfig.json file looks like this:

{
  "include": ["index.js", "src/**/*", "config/**/*"],
  "exclude": ["node_modules"],
  "compilerOptions": {
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "maxNodeModuleJsDepth": 5
  },
  "typeAcquisition": {
    "include": ["jest"]
  }
}

robross0606 avatar Aug 25 '25 14:08 robross0606

This seems relevant

https://github.com/jestjs/jest/issues/15275

matthewmayer avatar Aug 26 '25 00:08 matthewmayer

I am also struggling with this! Making this work with NestJS and ts-jest is a nightmare, even with latest Node 20. ESM-only libraries to me mean only a lot of swearing 🤬... wouldn't it be possible to ship this library as dual ESM + CJS library package so nobody gets hurt? I know it's not faker's fault, and it's the twisted way ESM is treated in Node, TS and Jest. However, just trying to be more inclusive. It shouldn't be very hard with modern bundlers such as tsup, provided there aren't ESM-only peer dependencies I think... WDYT?

lamuertepeluda avatar Aug 26 '25 15:08 lamuertepeluda

My opinion: @faker-js/faker v10+ still supports CJS in specific NodeJS environments via the require(esm) feature in NodeJS

Sadly we did not created a jest specific section in our playground and that's also why we didn't even thought about that it could break there. However I'm shocked to hear that jest is using its own/patched module system and do not simply rely on native js. I'm not sure if we can support every single customized framework out there and jest being just one of them, because even I don't know every js framework.

However I found in NestJS docs that they support nowadays vitest: https://docs.nestjs.com/recipes/swc#vitest

And as far as I know, vitest is not even faster then jest, but also very compatible to jest and so could be a in-place replacement.

And yeah, as you @lamuertepeluda also wrote, it looks like this is not "the fault" of @faker-js/faker, but more a fault of jest not updating to modern standards and lagging behind. Bundling everything twice is a huge bummer inside faker, because every local file needs to be bundled twice and so it increases the package size not only by just some kilobytes but some megabytes!

  • v9 is 8.69 MB with 458 total files (https://www.npmjs.com/package/@faker-js/faker/v/9.9.0)
  • v10 is 4.39 MB with 231 total files (https://www.npmjs.com/package/@faker-js/faker/v/10.0.0) this is a reduction of ~49.48%!

And this is running in thousands if not millions of CI/CD pipelines.

Right now it is also no problem to stay on v9 if needed, because v10 does not really have new features that are not available in v9 already. So you are not missing really something. And if you already use NestJS and jest, I highly bet that @faker-js/faker is not the first library that brings an esm-only issue to your stack, but it is just one more on the pile of "you should move to a more modern solution and invest in your infrastructure".

Maybe someone will also contribute to jest and fix the issue there and then the problem is not only solved for faker v10+ but also for MANY other libraries out there.

I also saw some projects out there have a fork and gave the suffix -cjs. https://www.npmjs.com/search?q=-cjs I see no issue in that someone wants to fork the faker repo and maintain a @your-scope/faker-cjs port.

Shinigami92 avatar Aug 26 '25 15:08 Shinigami92

I agree with @Shinigami92 in that is jest fault of lagging behind, and thanks for the hint about vitest, which I did not know. I will look at it although I am not sure it will be a drop-in replacement, because of the plethora of plugins jest has, some of which I use.

That said, I believe jest is one of the more widespread test frameworks, if not the most used one (stats might be inaccurate, mine was a very quick search).

So, just my two cents here, but given I (personally) see faker as a mainly backend-y, dev package used for development and automated tests, the problem of the package size is secondary with respect to the problem of the compatibility. It would be a bigger problem if faker was more often bundled in a frontend page, for instance.

So please think about supporting also CJS for a while. It's probably not this bad.

lamuertepeluda avatar Aug 26 '25 16:08 lamuertepeluda

As noted above but again: my idea not discussed with the team yet, but: I would be more in favor to backport features requested by the community back to v9.x instead of supporting CJS in further v10+ versions.

Shinigami92 avatar Aug 26 '25 17:08 Shinigami92

In the vast majority of cases with a complex Jest setup like this you'd be best to just pin FakerJS version v9.9.0.

Faker is an extremely stable and low-risk package. It has NO dependencies, and the chance of any security issues are extremely low. New features are typically added slowly and in an additive fashion. You should be able to run v9.9.0 indefinitely - or at least until a newer version of Jest supports the require(esm) feature.

matthewmayer avatar Aug 27 '25 00:08 matthewmayer

@matthewmayer I feel this is no specially complex jest setup, but rather a common one if you are using jest with TS on backend. As a the short term, for sure sticking to 9 is the only way. I've already added faker to my .ncurc ignore list. The problems will arise when also the documentation of 9 and 10 will diverge significantly, or new interesting features will be developed. I hope to find a viable alternative to jest some day, be it vitest or node built-in testing module. Until that day, I personally would have appreciated cjs support. Furthermore, if the main issue that made faker move to esm-only support is the package size, wouldn't it be best to split the package in a core package + additional packages as, for instance, lodash did? Another simpler alternative would be publish a faker-cjs package. As someone suggested, this could be made as a fork, but maybe with minimal effort this could be also an official one. This said, I personally don't mind about dev packages size since pipelines have caches and we download far bigger tools when running them. But this is just my use case.

lamuertepeluda avatar Aug 27 '25 07:08 lamuertepeluda

@lamuertepeluda did you know we have domains for old versions down to v6? Try https://v9.fakerjs.dev/.

Shinigami92 avatar Aug 27 '25 07:08 Shinigami92

+1, had to rollback to version 9.9.0 on a dependabot PR + ignore further faker dependabot updates, because this does not work with Jest.

xhuberdeau avatar Aug 27 '25 07:08 xhuberdeau

Fix Steps; ✅

yarn add --dev babel-jest @babel/core @babel/preset-env 

jest.config.json

  "transform": {
    "^.+\\.(js|jsx|ts|tsx)$": "babel-jest"
  },
  "transformIgnorePatterns": [
    "/node_modules/(?!@faker-js/faker)",
    "\\.pnp\\.[^\\/]+$"
  ],

babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: { node: 'current' },
        modules: 'auto'
      }
    ]
  ]
}

mt-ks avatar Aug 28 '25 13:08 mt-ks

+1, had to rollback to version 9.9.0 on a dependabot PR + ignore further faker dependabot updates, because this does not work with Jest.

@xhuberdeau same - just downgraded to v9 in a Jest CJS project.

matthewbordas avatar Sep 04 '25 19:09 matthewbordas

FYI https://github.com/wooorm/npm-esm-vs-cjs

lamuertepeluda avatar Sep 05 '25 07:09 lamuertepeluda

I took some time to set up a minimal reproduction in our playground and managed to configure Jest so that Faker works properly within it.

Since there were no specific requirements mentioned regarding the Jest setup in this issue discussion, I had full freedom in how I approached the configuration. It might not be perfect, but at least it works!

Feel free to check out the PR and the related GitHub Actions run:

  • faker-js/playground#60
  • https://github.com/faker-js/playground/actions/runs/17506831031

This confirms the workaround that was previously provided by @mt-ks.


TL;DR; (I want my issue fixed, tell me how)

Set a custom "transformIgnorePatterns" configuration that excludes all node_module libraries but Faker.

In the jest config:

module.exports = {
  // ...
  transformIgnorePatterns: [
    'node_modules/(?!@faker-js).+', // choose a pattern that works for YOU!
  ],
  // ...
}

xDivisionByZerox avatar Sep 05 '25 23:09 xDivisionByZerox

Note: Similar to what @mt-ks posted above, you can also just use SWC's capabilities instead of Babel for this (especially if you're already using SWC):

// jest.config.js
const esmOnlyPackages = [
  "@faker-js/faker",
  // …
]

const swcConfig = JSON.parse(readFileSync(path.resolve(__dirname, ".swcrc"), "utf8"))

const config = {
  transform: {
    "\\.m?[jt]sx?$": ["@swc/jest", { jsc: swcConfig.jsc }],
  },
  transformIgnorePatterns: [`node_modules/(?!(${esmOnlyPackages.join("|")})/.*)`],
  // …
}

The line with swcConfig is so that the JIT compilation takes over any compilation-related stuff from the main SWC configuration – in our case, that's e.g. that legacy decorators are enabled.s

clemens avatar Oct 23 '25 06:10 clemens

I just tried the 10.1.0 version and the error is gone! Everything worked fine! 🥳 No workaround needed!

leandromatos avatar Nov 02 '25 01:11 leandromatos

I just tried the 10.1.0 version and the error is gone! Everything worked fine! 🥳 No workaround needed!

Would you be so kind and share your combination of NodeJS/Jest/typescript/faker?

npx envinfo --npmPackages @faker-js/faker,jest,typescript --binaries

xDivisionByZerox avatar Nov 02 '25 20:11 xDivisionByZerox

@xDivisionByZerox

  Binaries:
    Node: 24.4.1 - /Users/leandromatos/.nvm/versions/node/v24.4.1/bin/node
    Yarn: 1.22.22 - /opt/homebrew/bin/yarn
    npm: 11.4.2 - /Users/leandromatos/.nvm/versions/node/v24.4.1/bin/npm
    pnpm: 10.13.1 - /opt/homebrew/bin/pnpm
    Watchman: 2025.06.30.00 - /opt/homebrew/bin/watchman
  npmPackages:
    @faker-js/faker: ^10.1.0 => 9.9.0 
    jest: ^30.2.0 => 30.2.0 
    typescript: ^5.9.3 => 5.9.3 

leandromatos avatar Nov 03 '25 14:11 leandromatos

@faker-js/faker: ^10.1.0 => 9.9.0

That's what I though. The real version that is installed on your local machine is v9.9.0 :)

xDivisionByZerox avatar Nov 03 '25 21:11 xDivisionByZerox

Sorry, guys! My mistake. I rolled back to 9.9.0.

leandromatos avatar Nov 22 '25 19:11 leandromatos