authored by claude, rubber stamped by Bryan MacLee
TL;DR: The npm package list you'd actually need in scrml is short. The critique is mostly cargo-culted muscle memory.
I am a truck driver by trade. I program because I love solving puzzles. So I have always been much more the "roll your own" type. I mean, why should I let someone else have all the fun.
Every time I ever had to actually involve Node into anything I was working on, I cringed, and would try desperately not to have to fall down that rabbit hole. Because, axiomatically, the only way out is through.
But no matter how much I dreaded typing "node", what truly got my blood boiling was what I always have, and always will, consider the world's most cancerous leaky abstraction. NPM.
What npm package do you actually need in scrml?
The obvious "weakness" someone could point at is that scrml doesn't have an easy npm-install path yet. That isn't a weakness. That's the whole point. I built scrml in part because I was sick of pulling in 200 packages to do what one well-designed language should do natively.
Now to be fair, there is a real version of this critique, and I'm going to address it head-on. A scrml vendor add <url> CLI is on the roadmap. Until it ships, ingestion of arbitrary client-side bundles is rougher than it should be. Fine. I'll concede that.
But the invalid version of the critique would be the implication that there's some long, essential list of npm packages a scrml app would want and can't have. So let's actually enumerate them. Because when you do, the list is comically short.
Here's the punchline up front: the npm-interop critique is mostly cargo-culted muscle memory from the React/Node era. Modern Bun + scrml's stdlib + scrml's language features collapse the whole package list down to about five categories, and only one of those (heavyweight client-side widgets like CodeMirror, three.js, and Leaflet) is a real story problem worth solving with vendor ingestion. The rest is a rounding error.
What npm typically supplies, and where each goes in a scrml app
Replaced by the language itself
These categories account for most of a typical Node project's package.json. None of them have a place in a scrml app, because scrml is this layer.
-
Framework + state + routing + forms. React, Vue, Svelte, Redux, Zustand, Pinia, react-router, vue-router, Formik, react-hook-form. scrml's reactive primitives (
@var, derived, effects), components, file-based routing, and bindings replace the entire stack. <!-- cite: bio §3d state-as-first-class voice-scrmlTS:290-291 + design-insights-2026-04-08 transformation-registry --> -
ORM / query builder. Prisma, Drizzle, Kysely, TypeORM, Sequelize. scrml's
?{}SQL block writes parameterized queries directly against Bun.SQL, with compile-time schema introspection (§39) and protected-field enforcement (§11), and?{}itself is specified at §44. <!-- cite: SPEC.md §44 ?{} multi-database adaptation; §39 schema and migrations; §11 protect= --> -
CSS-in-JS / scoping. styled-components, emotion, vanilla-extract. scrml's
#{}scoped CSS uses native@scope(§9.1, §25.6). <!-- cite: SPEC.md §9.1 inline CSS line 4918, §25.6 native @scope line 11579 --> -
HTTP client. axios, got, ky. Server fns can call
fetchdirectly. Markup-side fetches use<request>andlift. -
Build / bundle / dev server. Vite, Webpack, esbuild, Parcel.
scrml dev,scrml build,scrml serve. Done. -
Test runner. vitest, jest, mocha.
bun test, plus thescrml:teststdlib for assertions. -
WebSocket plumbing. socket.io, ws. scrml's
<channel>(§38). <!-- cite: SPEC.md §38 WebSocket Channels --> - Auth / CSRF middleware. passport, csurf, express-session. Boundary security is enforced by the compiler. CSRF mint-on-403 is built in.
-
Validation (the zod case). zod, yup, joi, ajv, superstruct. I have two answers here, and both of them are stronger than zod:
-
§53 inline type predicates are compile-time-enforced refinement types:
let x: number(>0 && <10000),fn process(amount: number(>0 && <10000)),type Invoice:struct = { amount: number(>0 && <10000) }. Named shapes likeemail,url,uuid,phoneare first-class (and so aredate,time,color). Violations are compile errors (E-CONTRACT-001), not runtime exceptions you find out about when production blows up. Zod can't fail your build. This can. <!-- cite: SPEC.md §53.6.1 named shape registry; type-system.ts:538 NAMED_SHAPES live registry --> -
scrml:data/validatestdlib for runtime form validation when the data shape is genuinely unknown until runtime:validate(data, schema)returns{ field: errors[] }, with rule builders forrequired,email,minLength,maxLength,pattern,min/max,numeric,integer,matches,oneOf,url, plus domain composites (emailField,passwordField,passwordConfirmField). <!-- cite: stdlib/data/validate.scrml lines 70-245 -->
-
§53 inline type predicates are compile-time-enforced refinement types:
If you've ever installed all of the above into a single React app, you've installed roughly 60-80% of the average package.json line count. None of it belongs in a scrml app. Ever.
Replaced by Bun
Bun is the runtime, and Bun's stdlib has steadily absorbed the rest of the lower-level Node ecosystem. Every one of these used to be an npm package. Now they're built in:
-
bcryptbecomesBun.password -
jsonwebtoken/josebecome web crypto +Bun.password -
pg,mysql2,better-sqlite3becomeBun.SQL -
ioredis,redisbecomeBun.redis -
sharp(some cases) becomesBun.spawnto imagemagick, or vendor when you really need to -
nodemailerbecomesBun.spawnto system MTA, or REST-call a transactional email API -
dotenvis built into Bun -
fs-extrais just Bun'sfsergonomics
This is what I mean when I say "roll your own": Bun's authors did. Now nobody has to npm-install bcrypt and pray the maintainer doesn't get bored.
Already in scrml's stdlib
The 13-module stdlib already covers most of the "I'd npm install a small utility" reflex. I built it intentionally small but not so small that you have to leave the language for the basics:
| stdlib module | replaces |
|---|---|
data/validate (+ §53) |
zod, yup, joi |
data/transform |
lodash (pick, omit, groupBy, sortBy, unique, flatten, ...) |
auth |
bcrypt, jsonwebtoken, speakeasy (TOTP), express-rate-limit |
crypto |
crypto-js, bcryptjs, hashing helpers |
http |
axios, got, node-fetch (typed wrapper with timeout + retry) |
time |
date-fns / dayjs (basic format), lodash.debounce/throttle |
format |
slugify, change-case, pluralize, currency/number formatting |
store |
KV store, session store, counter (replaces connect-sqlite3 and basic redis use) |
router |
path-to-regexp, qs |
test |
chai, parts of jest/expect |
fs, path, process
|
Node compat layer |
The stdlib isn't trying to be everything. It covers the high-frequency reaches. Specialty libraries get vendored. That's the deal.
Trivially vendored or rewritten
Things that are honestly small enough to copy straight into your project:
-
uuid,nanoid. One-liners againstcrypto.randomUUID()or web crypto. Why are these npm packages? - More date math beyond the stdlib. Vendor a single function from
date-fns. Don't drag in the whole library. - Markdown rendering for short content.
markedis small and CDN-vendorable. - Most of the
@types/*ecosystem. Irrelevant. scrml has its own type system.
If your "I need this from npm" instinct fires for one of these, the cost-benefit of vendoring a 40-line helper vs. wiring up a package manager flow isn't even close. Just write the function. Have the fun.
Service SDKs are mostly thin REST wrappers
This is the category most often invoked as a counterexample, and it's the one that drives me up a wall, because it's mostly a misconception:
-
Stripe, OpenAI, Anthropic, Resend, SendGrid, Twilio, Slack, GitHub. These are REST APIs. Their official SDKs are typed wrappers around
fetch. Calling the REST endpoint directly from a server fn is a 10-linefetchcall. Yes, the SDK convenience is real (typed responses, retry logic, pagination helpers). No, it isn't load-bearing. - AWS SDK. Somewhat heavier (SigV4 request signing), but you can either vendor the v3 modular packages or write a small SigV4 helper. People sign requests in 30 lines of bash. You can do it in scrml.
A scrml convention of "here's the canonical pattern for hitting Stripe / OpenAI / AWS from a server fn" docs page closes most of this gap. The capability already exists. The recipe doesn't, yet. That's a docs problem, not a language problem.
Where npm interop actually bites
Here's the honest list. The places where I'll grant you the criticism has teeth. These are heavyweight client-side libraries that you cannot reasonably re-implement, no matter how much I love rolling my own:
-
Code editors. CodeMirror 6, Monaco, ProseMirror, TipTap, Lexical. 100k+ LOC each. (6nz already vendors CM6 via dynamic import + a
__cmModglobal bridge. It's a working pattern.) - 3D. three.js, babylon.js
- Maps. Leaflet, mapbox-gl, MapLibre
-
Charts beyond stdlib. Chart.js, ECharts, D3, Plotly, Highcharts (scrml has
chart-utils.jsfor the lighter end) - PDF generation. pdf-lib, jspdf
- Animation beyond CSS. Framer Motion, GSAP, anime.js
- Real-time collab CRDTs. Yjs, Automerge
- Rich graph viz. dagre, vis-network, cytoscape
Every one of these is the same pattern: a heavyweight bundle that needs to load on the client, get a JS handle, and be called from app code. One mechanism solves the whole class: scrml vendor add <url> ingests a UMD or ES bundle from a CDN, generates a type shim, and wires it into the boundary security model. The 6nz CM6 integration is a working proof-of-concept already.
That's the entire honest list. About ten categories of widget. Not "an open-ended ecosystem of two million packages."
So what's the strategic gap?
The critique stops landing once I ship three things:
-
scrml vendor add <url>CLI. Flat-file ingestion of a CDN bundle, type-shim generation, manifest tracking. On the roadmap and on me to land. -
A type-shim story for vendored bundles. The moment an adopter does
vendor add chart.js, they hit "untyped global." A canonical pattern (declare-only.d.scrmlor equivalent) closes this. I'm building it. -
A "calling external REST SDKs" recipes doc. Five examples (Stripe, OpenAI, AWS S3, Resend, Slack webhook) showing the
fetch-from-server-fn pattern with auth headers and typed responses. Docs work. I'll write it.
Once those three are in, the npm critique loses about 90% of its bite. The remaining 10% is the heavy widget category, and the answer there is "vendor the bundle. We will never npm-install three.js, and that's fine."
The deeper point
The npm ecosystem is enormous because the JavaScript language and the browser platform are minimal. Most of those packages exist to paper over missing primitives. A state library to give you reactivity, a router to give you routing, a CSS-in-JS library to give you scoped styles, an ORM to give you queries, a validation library to give you types at the boundary. When the language and runtime supply those primitives natively, the package list collapses.
That's the bet I'm making with scrml. A first-principles, full-stack language with a real type system, a real reactive model, real boundary security, real query syntax, and a small focused stdlib is a smaller surface to learn and a smaller surface to maintain than a Node project that wires together 200 packages to recreate the same capabilities. The npm-interop critique reads as a weakness only if you assume the package list is a fixed cost. It isn't. It's the symptom.
The one package you actually need is vendor add chart.js. I'm shipping it. Once it lands, the conversation is over.
And me? I'll be back in the cab, thinking about the next puzzle.
Further reading
- Why programming for the browser needs a different kind of language. The companion piece. What a browser-shaped language actually owns at the type level.
- What scrml's LSP can do that no other LSP can, and why giti follows from the same principle. What vertical integration unlocks, in two pieces of the ecosystem.
- Introducing scrml: a single-file, full-stack reactive web language. The starting-point overview if you haven't seen scrml before.
-
Null was a billion-dollar mistake. Falsy was the second.. On
not, presence as a type-system question, and why scrml refuses to inherit JavaScript's truthiness rules. - scrml's Living Compiler. The transformation-registry framing. The constructive flip-side of the npm critique above.
- scrml on GitHub: github.com/bryanmaclee/scrmlTS. The working compiler, examples, spec, benchmarks.
Top comments (0)