Skip to content

feat: transform core modules to standalone module functions - #3857

Open
ST-DDT wants to merge 14 commits into
nextfrom
feat/standalone/core-modules
Open

feat: transform core modules to standalone module functions#3857
ST-DDT wants to merge 14 commits into
nextfrom
feat/standalone/core-modules

Conversation

@ST-DDT

@ST-DDT ST-DDT commented May 23, 2026

Copy link
Copy Markdown
Member

Split from #3748


Transforms the core modules to standalone module functions.

These core modules are interdependent and thus cannot be transformed on their own.

  • DatatypeModule
  • HelpersModule
  • NumberModule
  • StringModule

Most of the conversion steps are done automatically, but some changes have been done manually.

Please note that I renamed some util files to _, to distinguish files that export an API SMF from those that don't.

This PR can be reviewed more easily using two ways

1. Compare automated steps vs PR

  • checkout PR
  • hard reset to before the automated scripts
  • run scripts
  • compare with commit

2. Verify no diff in modules to #3748 and look at the manual transform commits

#checkout PR
git diff origin/feat/standalone-module-functions -- src/modules/<ModuleName>
#No Diff expected

Check the individual module transform commits:


Please do not merge any PRs changing anything in the modules after this PR until all modules are transformed.

@ST-DDT ST-DDT added this to the v10.x milestone May 23, 2026
@ST-DDT ST-DDT self-assigned this May 23, 2026
@ST-DDT
ST-DDT requested a review from a team as a code owner May 23, 2026 15:17
@ST-DDT ST-DDT added c: feature Request for new feature p: 1-normal Nothing urgent m: datatype Something is referring to the datatype module m: helpers Something is referring to the helpers module m: string Something is referring to the string module m: number Something is referring to the number module labels May 23, 2026
@netlify

netlify Bot commented May 23, 2026

Copy link
Copy Markdown

Deploy Preview for fakerjs ready!

Name Link
🔨 Latest commit b34f47b
🔍 Latest deploy log https://app.netlify.com/projects/fakerjs/deploys/6a70b71fc824a5000821a0ba
😎 Deploy Preview https://deploy-preview-3857.fakerjs.dev
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@ST-DDT
ST-DDT requested a review from a team May 23, 2026 15:17
@codecov

codecov Bot commented May 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.24473% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.94%. Comparing base (3125eaf) to head (b34f47b).

Files with missing lines Patch % Lines
src/modules/helpers/from-reg-exp.ts 93.46% 8 Missing and 2 partials ⚠️
src/modules/helpers/replace-credit-card-symbols.ts 88.00% 6 Missing ⚠️
src/modules/helpers/weighted-array-element.ts 92.30% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             next    #3857      +/-   ##
==========================================
+ Coverage   98.91%   98.94%   +0.03%     
==========================================
  Files         924      961      +37     
  Lines        3224     3325     +101     
  Branches      567      609      +42     
==========================================
+ Hits         3189     3290     +101     
  Misses         31       31              
  Partials        4        4              
Files with missing lines Coverage Δ
src/modules/datatype/boolean.ts 100.00% <100.00%> (ø)
src/modules/datatype/module.ts 100.00% <100.00%> (ø)
src/modules/helpers/_eval.ts 98.55% <ø> (ø)
src/modules/helpers/_luhn-check.ts 100.00% <ø> (ø)
src/modules/helpers/array-element.ts 100.00% <100.00%> (ø)
src/modules/helpers/array-elements.ts 100.00% <100.00%> (ø)
src/modules/helpers/enum-value.ts 100.00% <100.00%> (ø)
src/modules/helpers/maybe.ts 100.00% <100.00%> (ø)
src/modules/helpers/module.ts 100.00% <100.00%> (+5.15%) ⬆️
src/modules/helpers/multiple.ts 100.00% <100.00%> (ø)
... and 35 more
🚀 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.

@Shinigami92

Copy link
Copy Markdown
Member

discussable arguable opinionated feedback

this feels quite messy
the problem I have with it, is that you mentally need to know the entire faker structure in your head instead of exploring it by e.g. namespace or one import

this is right now just internal code, I would never like to ship something like this for customers
customers should be able to use e.g. import { datatypeBoolean, helpersArrayElement } from '@faker-js/faker' and so on

maybe we can have something like this also internally (and still don't 🦆-up 🌲-🫨)

@ST-DDT

ST-DDT commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

Just for clarification:

Would you like for us to use qualified method names from the start?
E.g. export function helpersArrayElement
Instead of the rename during import (if the name collides).
Or something else?

This is kind of relevant for the later public API: Will we only export from root or also export via the module paths e.g. @faker-js/faker/module/helpers? Or will we adapt our own naming scheme such as fakeArrayElement similar to useRouter?

@Shinigami92

Shinigami92 commented Jun 5, 2026

Copy link
Copy Markdown
Member

Just for clarification:

Would you like for us to use qualified method names from the start? E.g. export function helpersArrayElement Instead of the rename during import (if the name collides). Or something else?

Partly that was my thought regarding yes, but I did not fully thought that through yet.
My greater problem is more that we e.g. could use internally imports like:

// instead of
import { arrayElement as helpersArrayElement } from './array-element';
import { arrayElements as helpersArrayElements } from './array-elements';
import { enumValue as helpersEnumValue } from './enum-value';

// something like
import { arrayElement as helpersArrayElement, arrayElements as helpersArrayElements, enumValue as helpersEnumValue } from '../helpers';

but ../helpers is equals ./index and this is the file we are just in... so I understand that problem

thinking further...

maybe we move the module class into a separate file, so we can import from index... 🦆 might not work either, because we would need to import the module class in index to pass further to top again 🤔 ...

best what we could try to achieve would be something like:

import * as helpers from './?whatever?';
import * as helpers from './?whatever?';
import * as string from '../string';

export class SimpleHelpersModule extends SimpleModuleBase {
  slugify(string: string = ''): string {
    return helpers.slugify(this.faker.fakerCore, string);
  }

  // string.alphanumeric

I know that what I start to propose here might need fundamental movements of files, and this is why I raising this right now (I'm sorry 🥲)

I can also later (not in company-working-hours try to thinker with claude about this, maybe it has some enlightening ideas)

Edit:
I'm raising this right now, because I think this is how we maintainers, contributors and even AIs will need to live and work in the future codebase of Faker. This is such a fundamental shaping. And I did not delivered the solution yet, just uncovered the "problem" (mental load).

@ST-DDT

ST-DDT commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

IMO moving the module to a separate file (and not including it in the index) would work for me too.

Maybe prepare a proposal so that we can talk about it later. Though I dont have time this weekend.

Note: I ammended my previous comment. Not sure whether you have seen it.

@Shinigami92

Copy link
Copy Markdown
Member

IMO moving the module to a separate file (and not including it in the index) would work for me too.

👍

Maybe prepare a proposal so that we can talk about it later. Though I dont have time this weekend.

🤝 I will also hide myself this weekend deeply into Gothic 1 Remake 😇

Note: I ammended my previous comment. Not sure whether you have seen it.

THX! I have NOT seen it 👍

It is kinda a sibling topic, because this is then how we structure the external API
I would be fine maybe even ship and allow both: { arrayElements } from @faker-js/faker/module/helpers AND { helpersArrayElements } from @faker-js/faker

But maybe we should outsource this discussion to a separate place

@ST-DDT

ST-DDT commented Jun 9, 2026

Copy link
Copy Markdown
Member Author

Alternatively, split the files as proposed here, then later refactor the imports if needed.
Since that is nonbreaking, we can still do so later without issues.

@Shinigami92

Copy link
Copy Markdown
Member

I now had a talk with Claude (Opus4.8[1m]), and the simple solution is just to move the Module class to a src/modules/<module>/module.ts.
In src/modules/<module>/index.ts we then just to import * from './module'.

In this PR we can then extend it by adding the standalone functions by just doing this:

// ./array-element.ts
import { ... } from '../<other-module>'
import { ... } from './<other-same-module-standalone-fn>'

export function arrayElement

// ./module.ts
import { arrayElement } from './array-element'

export class SimpleHelpersModule

// ./index.ts
import * from './array-element'
import * from './module'

I will spin up a PR for the moving, and we can move on there and decide if this is the wanted solution or if it contains any other problems I have not seen yet.

@ST-DDT

ST-DDT commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

Ready for review again.


The PR may be easier to review if you look at the individual commits.

The auto generated commits can be recreated with:

pnpm tsx scripts/temp-tranform-once.ts 
pnpm tsx scripts/generate-module-tree.ts 

@Shinigami92 Shinigami92 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I stopped reviewing here and first would like to get the commented points discussed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nitpick (non-blocking): not really relevant for this PR and we can outsource it into a follow-up PR, but I'm not sure if I like the _ prefix for internal in our source folder 🤔
maybe we could just add an internal folder in every module folder? this resolves also potential naming collisions.

@ST-DDT ST-DDT Jun 26, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This affects final files:

  • 3x color
  • 1x date
  • 1x finance
  • 2x helpers
  • 1x internet
  • 1x person
  • 1x string
  • 1x word

You can see them all here using the _ file filter on the left side.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't like folders that only contain a single file.
Also note: The internals are sometimes "faker global"-internals and not "module-exclusive".

The main reason they are prefixed with _ to visually separate them from files that contain SMFs.
We can remove the _ prefix, no collisions will occur, as I renamed the files that are likely to collide beforehand.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@xDivisionByZerox what's your opinion?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

just another soft-contra for _ prefixed: we have e.g. a _local folder in root which is git-ignored, but excluded from pnpm run clean
so we are introducing different meanings for a _ file/folder

Comment on lines +2 to +4
import { arrayElement } from '../helpers/array-element';
import { rangeToNumber } from '../helpers/range-to-number';
import { weightedArrayElement } from '../helpers/weighted-array-element';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: here is the point where I would like to see that imports are used from their module/index

Suggested change
import { arrayElement } from '../helpers/array-element';
import { rangeToNumber } from '../helpers/range-to-number';
import { weightedArrayElement } from '../helpers/weighted-array-element';
import { arrayElement, rangeToNumber, weightedArrayElement } from '../helpers';

or does this not work? 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That does work, at the risk of including any side effects the index contains.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure or aware that we plan to include side-effects in index 😅 I would even call that an anti-pattern if there would be side-effects in index files

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The faker main index has some kind of side effects, mainly regarding the pre-created faker instances.
I think I mentioned that somewhen/somewhere related to PURE annoations.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A root index (or often also a main.ts in other context) is a bit differently handled as index files in sub directories which just reexport their folder content upwards.

@ST-DDT ST-DDT Jun 29, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@xDivisionByZerox Which variant do you prefer for foreign module method imports?

  • A) Import from the exact file that exports the method
  • B) Import from the foreign module index instead

?

@ST-DDT ST-DDT Jun 29, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Currently, most of our imports import directly from the actual source file, not from a parent index:

  • import { ModuleBase } from '../../internal/module-base';
    import type { NumberRange } from '../../utils/types';
  • import type { Faker, SimpleFaker } from '../..';
    import { FakerError } from '../../errors/faker-error';
    import { SimpleModuleBase } from '../../internal/module-base';
    import type { NumberRange } from '../../utils/types';
  • import { FakerError } from '../../errors/faker-error';
    import type { Faker } from '../../faker';
    import { toBase64Url } from '../../internal/base64';
    import { ModuleBase } from '../../internal/module-base';

(excluding the new module imports)

Comment thread src/modules/string/nanoid.ts Outdated
@ST-DDT

ST-DDT commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

Random fun fact: I just searched and found the issues which laid the foundation for this/these changes.

@ST-DDT
ST-DDT force-pushed the feat/standalone/core-modules branch from 71b6418 to 6b0e50b Compare July 3, 2026 18:25
@ST-DDT ST-DDT linked an issue Jul 5, 2026 that may be closed by this pull request
@ST-DDT

ST-DDT commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Should the SMFs be annotated with @experminetal for now?

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

Labels

c: feature Request for new feature m: datatype Something is referring to the datatype module m: helpers Something is referring to the helpers module m: number Something is referring to the number module m: string Something is referring to the string module p: 1-normal Nothing urgent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Export individual API modules on their own

2 participants