Build1 distinct publisher3 min readUpdated
A hobby Pokemon generator resolves its whole dataset at build time from a devDependency and ships only filtered slices. The pattern travels further than the hobby.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The author of a random Pokemon generator put the `@pkmn/dex` package in devDependencies, ran a single script against it at build time, and emitted plain static JSON the app imports like any other module [1][2]. The site makes zero calls to PokeAPI at runtime, and the 2.4MB dex package never reaches a browser [3][4].
What ships instead is five files: `pokedex.json` with 1,025 species carrying exactly 13 fields each, an 18x18 type effectiveness matrix, 685 moves, 310 abilities, and a per-species legal move list [5][6][7][8][9]. The reasoning for not fetching is structural rather than aesthetic. PokeAPI answers questions one at a time, and a generator does not ask questions one at a time [10]: the moment a user toggles "Gen 3 only, no Legendaries, starters excluded" and presses the button, the app needs the entire filtered pool in memory immediately [11]. Per-click fetching buys spinners, rate-limit exposure, and freeloading on a free public API [12].
The more durable payoff is that the messy definitional work lands in one file. The raw dex includes battle-only formes, cosmetic variants and fan-designed CAP entries, so the script hardcodes the exclusion policy: skip anything with a non-empty `forme`, skip `num <= 0`, skip anything flagged `isNonstandard` as CAP, LGPE, Custom, Unobtainable or Future [13][14]. Some categories do not exist upstream at all. There is no `isStarter` field, so the script carries a hardcoded set of 27 starter names and stamps a boolean onto each match [15]. Legendary and mythical come from tags [16]. Generation is not stored either; it is derived from National Dex number ranges, 151 and below being Gen 1 [17]. The client reads pre-computed booleans and fields and reasons about none of it, and changing the pool is a one-file diff plus a rebuild [18][19].
That is the part that generalizes. Anything you currently resolve at runtime because a rule is ambiguous - eligibility, tiering, region mapping - is a candidate for compilation, and the win is not only bytes. It is that the ambiguity gets a single owner and a version history instead of being re-litigated in three components.
Compilation does not make everything small. After trimming, the learnsets file still weighs 870KB, down from a 3.1MB raw chunk [20][21], roughly a 72 percent reduction and still an order of magnitude heavier than everything around it [1]. Because exactly one feature needs it, it is the only file with no static import: it loads via `await import()` the first time someone generates a moveset, and the chunk is cached afterward [22][23]. Moves and abilities sit in separate lib files rather than one shared battle-data module, so each route carries only its own weight [24]. Sprite URLs are not stored at all, since the filename is the National Dex number and the URL can be assembled at runtime [25].
Two things to watch if you copy this. The 13-field contract is the pressure point: the moment the UI needs a fourteenth field, the schema, the generator script and the rebuild are all in the loop, which is cheap in a hobby repo and less cheap behind a release process. And the only described refresh path is re-running the build [19], so any dataset that changes on someone else's schedule needs a scheduled rebuild and a way to notice when the upstream package moves. The pattern trades freshness for determinism. That is usually the right trade, but it should be a decision rather than a side effect.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The @pkmn/dex npm package sits in devDependencies of the project and never ships to users.
A build-time script, scripts/generate-pokedex.mjs, runs the dex package once and emits plain static JSON that the app imports like any other module.
At runtime the site makes zero calls to PokeAPI.
The dex package weighs 2.4MB and never appears in the client bundle.
The raw dex data includes battle-only formes, cosmetic variants, fan-designed CAP species and other entries that would look like bugs coming out of a generator.
The build script hardcodes the exclusion policy in one place: skip anything with a non-empty forme and cosmetic formes generally, skip entries with num <= 0, and skip anything whose isNonstandard is CAP, LGPE, Custom, Unobtainable or Future.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Detailed but wholly single-source and self-reported
Every factual element — file sizes, record counts, exclusion rules, import strategy — comes from one first-person dev.to post by the project's author. The account is specific, internally consistent and technically plausible, and the derived 3.1MB→870KB reduction follows arithmetically from the stated figures. But there is no repository link, bundle analyzer output, load-time measurement, or independent corroboration, so nothing is externally verifiable.
One self-reported hobby deployment
The only observable use is the author's own fan project shipping with this pipeline. No downloads, dependents, third-party implementations, traffic numbers, or reusable package are disclosed; the closing generalization that the pattern travels further is an argument, not an adoption datapoint.
Mostly measured, with an unevidenced leap from one hobby project to a general pattern
The technical claims are modest and bounded, and the author deflates his own work ('the line users think of as the app took thirty seconds to write'). The overstatement is scope: a single fan project is presented as validating a general build-step-as-data-compiler pattern, and the efficiency wins are asserted through file sizes rather than any measured user-facing load or bundle numbers.
Self-promotional practitioner post, no disclosed commercial stake
The author is writing up his own side project on a developer blogging platform, which carries a normal reputational and portfolio incentive to present the architecture favorably and to omit measurements that might undercut it. Against that, no vendor, funding, or monetization relationship is disclosed; the post explicitly states it is an independent fan project unaffiliated with the rights holders, and it praises a third-party open-source package without any indicated commercial tie.
High confidence in what was said, low confidence that it is verified or generalizes
There is little ambiguity about the described design — the source is explicit, specific and consistent — so the descriptive claims can be recorded with reasonable confidence. Confidence in the wider assessment is capped by having one publisher, one first-party account, one deployment, and no measurement or independent check of any number.
build
A build step instead of a backend: 1,025 records, 8 locales, no runtime API1 distinct publisher
build
Force the tool call, then hand Lightsail a long-lived key1 distinct publisher
build
AI-written code fails the same four ways, and every gate you own reports green1 distinct publisher
build
CSA's 2026 threat list is a flat line, so ask which threats a config snapshot can prove1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 16, 2026