Follow-up to #428, non-breaking, for after v6.0.0.
The problem
src/utils/currency-vocab.js exports one object literal per language, and a language file
reads it by string key:
const { major, minor } = CURRENCY_VOCAB[currency]
export const currencyValues = { currency: Object.keys(CURRENCY_VOCAB) }
Both pin the whole object. Terser drops unused bindings but never unused properties, so
a page that only ever spells dollars still ships all 24 English currencies:
|
raw |
gzip |
all 24 en currencies |
1,418 B |
347 B |
USD alone |
63 B |
70 B |
≈ 277 B gzipped of vocabulary nobody asked for. Credit to @TylerVigario, who measured
this first on proto/form-split and pointed out that it's the same rule
docs/currency-vocab.md already applies one level up (keying by language rather than by
ISO code), just not carried one level further down.
Why #428 doesn't fix it
Per-currency named exports and string-keyed selection are mutually exclusive. A switch,
or a code → binding map, still references every binding — nothing is dropped. Only making
the option value be the vocab object tree-shakes, and that would break
{ currency: row.currency } (an invoicing app picks from data, not from identifiers) along
with the currencyValues enum and currency-vocab-contract.test.js.
That's a real cost, so #428 keeps the string API and pays the 277 B.
The proposal
Accept both shapes for the same option.
// today, and still the default — validated, works from data
toCurrency(42.50, { currency: 'GBP' })
// new — tree-shakes to one currency
import { GBP } from 'n2words/en/currencies'
toCurrency(42.50, { currency: GBP })
A string keeps its RangeError-on-typo contract against currencyValues. An object is
opaque to the enum but statically resolved, so a typo is a link-time error instead — the
stronger guarantee of the two, just enforced somewhere else.
Open questions
- Where do per-currency bindings live? A new
src/currencies/{lang}.js per language, or
named exports beside the existing map in currency-vocab.js?
- How does
resolveOptions validate a non-enum value for an option that also has an enum?
The <form>Values contract currently assumes one allowed set.
- Does each vocab object carry its own
code, so assertCurrencyExponent and
minorUnitDigits still work without a separate lookup?
- What does
currency-vocab-contract.test.js assert about the object path?
Acceptance
@TylerVigario — offered to you if you'd like it, since you measured it and this is the part
of proto/form-split that survives keeping the string API. Happy to take it otherwise.
Follow-up to #428, non-breaking, for after v6.0.0.
The problem
src/utils/currency-vocab.jsexports one object literal per language, and a language filereads it by string key:
Both pin the whole object. Terser drops unused bindings but never unused properties, so
a page that only ever spells dollars still ships all 24 English currencies:
encurrenciesUSDalone≈ 277 B gzipped of vocabulary nobody asked for. Credit to @TylerVigario, who measured
this first on
proto/form-splitand pointed out that it's the same ruledocs/currency-vocab.mdalready applies one level up (keying by language rather than byISO code), just not carried one level further down.
Why #428 doesn't fix it
Per-currency named exports and string-keyed selection are mutually exclusive. A
switch,or a code → binding map, still references every binding — nothing is dropped. Only making
the option value be the vocab object tree-shakes, and that would break
{ currency: row.currency }(an invoicing app picks from data, not from identifiers) alongwith the
currencyValuesenum andcurrency-vocab-contract.test.js.That's a real cost, so #428 keeps the string API and pays the 277 B.
The proposal
Accept both shapes for the same option.
A string keeps its
RangeError-on-typo contract againstcurrencyValues. An object isopaque to the enum but statically resolved, so a typo is a link-time error instead — the
stronger guarantee of the two, just enforced somewhere else.
Open questions
src/currencies/{lang}.jsper language, ornamed exports beside the existing map in
currency-vocab.js?resolveOptionsvalidate a non-enum value for an option that also has an enum?The
<form>Valuescontract currently assumes one allowed set.code, soassertCurrencyExponentandminorUnitDigitsstill work without a separate lookup?currency-vocab-contract.test.jsassert about the object path?Acceptance
dist/en-US/currency.jsbuilt against a single imported currency lands near thesingle-currency floor (~4,069 B was the measured target in feat(core)!: separate currency from language, with a validated cross-language matrix #428's discussion)
currencyValuesand theRangeErroron a typo'd string survive unchanged@TylerVigario — offered to you if you'd like it, since you measured it and this is the part
of
proto/form-splitthat survives keeping the string API. Happy to take it otherwise.