The first time one function gets expensive, the usual advice is to pull it out
into a service. That is a lot of machinery for one function.
A parser grows. A validator picks up rules. A hash, render, or compression step
starts taking more of the event loop than it should. Nothing else about the app
has changed: one function got heavy, and the fix on offer is new infrastructure.
Knitting is for that gap. Keep the code where it is, move the work off the main
thread.
CPU pressure in a JavaScript app rarely spreads evenly. Real codebases often
have thousands of functions, but only a few of them create most of the cost.
That changes the size of the fix. When the cost is concentrated in a handful of
functions, the response can be concentrated too — moving the expensive part does
not have to mean moving the application around it.
When the main thread starts paying for those functions, JavaScript developers
usually reach for one of three options.
Do nothing. It is simple, and for a while it works. But every
millisecond a hot function spends on the main thread is a millisecond the server
cannot spend on everything else. Cheap routes queue behind expensive ones.
Scaling out dilutes the pain, but it does not remove the hot path from the
event loop.
Workers. Closer to the mark: the work leaves the main thread and stays on
the machine. What makes it painful is everything around it. The runtime hands
you postMessage and leaves the rest to you — request IDs, routing, errors,
promises, lifecycle, payload choices. That plumbing is the original reason
Knitting exists.
Make it a service. Sometimes that is the right call. Separate ownership,
deploy cadence, and failure domains are all real reasons to cross the network.
But for one hot function, owned by the same team, in the same repo, the bill is
strange: a network hop, serialization, deployment, monitoring, and one more
thing to operate. The function did not ask for a hostname. It asked for
somewhere else to run.
Knitting adds a smaller boundary: the code stays where it is, and only the
execution moves.
The function stays in your repo, in your language, in your types — one
import away from its caller. What changes is where it runs and what it can
touch. You export it, hand it to a pool, and call it like the async function it
already was. Underneath, it runs on a real thread or an isolated process.
That is the whole boundary: one export, one pool, one call. hello still reads
like a plain function, but it no longer runs on the main thread.
Call that a function-level execution boundary: a way to move the expensive
part without moving the whole app.
Compare what each option costs for the same few hot functions:
In-process
Hand-rolled workers
Microservice
Knitting
Main thread protected
no
yes
yes
yes
Code stays in the app
yes
mostly
no
yes
Call-site ergonomics
function call
protocol you wrote
HTTP client
function call
Transport cost
none
postMessage per call
network + JSON
shared-memory mailboxes
Isolation available
none
thread only
full, always-on
a dial, per pool
New deploy unit
no
no
yes
no
Two things make that boundary worth having.
Transport cost matters. Workers can feel disappointing when reaching the
worker costs more than the work. Knitting uses shared-memory mailboxes instead
of the runtime’s message queue, so small and medium calls stay practical. The
Architecture page explains the mechanism, and the
benchmarks show where the shape wins and where it
does not.
Isolation should match the task. A microservice gives you process isolation
whether or not you need it. Knitting lets each pool pick its own boundary:
in-process guards, a bootstrap hook, runtime-native permissions, or a real
OS-sandboxed process. Trusted math can stay on cheap threads. An untrusted
plugin can run behind bwrap, and
importTask keeps its code off the
host entirely.
Knitting does not replace distributed systems. If you need cross-machine scale,
separate failure domains, or team-level ownership boundaries, you still need the
network. Knitting is for the moment before that, when the code belongs in the
app but the work no longer belongs on the main thread.
Knitting today is task-call oriented: one request in, one response out. The
transport underneath it is more general than that. Mailboxes, payload buffers,
and named mappings leave room for other shapes.
01Nearer term
Channels
Some same-host work is not a task call: progress, long-lived coordination,
producer/consumer flows. Today the honest answer is MessagePort.
A future channel API would keep that shape on the same shared-memory
transport, without pretending every conversation is request/response.
02Longer term
Cross-language, same-host
The mailbox protocol is not tied to JavaScript. Process workers already
open named mappings, which makes the boundary more about memory than a
specific runtime.
A later version could let JavaScript hand large payloads to another
runtime on the same machine without copying through JSON or
re-marshalling at every hop.
That is why the transport is built with more care than a worker pool strictly
needs. Until those APIs ship, though, judge Knitting on what it does now.