0.4.1
Highlights
- Profile-guided optimization:
scriptc build --profile-generatebuilds an instrumented executable,scriptc merge-profiles(with--sparse) merges the profiles its runs write, and--profile-useoptimizes a release or speed build with the merged profile. tsc-ts built with a profile from its own type check checks other projects 1.15 to 1.2 times as fast, with the same diagnostics and memory. Builds without a profile are unchanged. - Faster cold builds of large programs: large class-based programs build in partitions instead of as one module, so their cold builds are 25 to 32% faster, and an edit to one function recompiles only its partition.
- npm packages: more packages that pass builtins, classes and promises around as values now compile from their JavaScript. Among them are execa 5, hot-shots, @opentelemetry/api, async-sema, buffer-crc32, yazl, tar-stream, smol-toml and truncate-utf8-bytes.
undici5.x and 7.x compile from an implementation that ships with scriptc, andisarray,safe-bufferandprocess-nextick-argscompile as the Node builtins they stand in for. - Dynamic-tier property access goes through per-site inline caches keyed on object shapes. On constructor-and-prototype code, the code that reads several object shapes at one site and an AST interpreter, untyped code runs 1.4 to 3 times as fast as in 0.4.0. Typed programs compile to the same code as before.
- Smaller arrays, class instances and number-keyed maps:
- The array header is 48 bytes instead of 64.
- Small arrays start with three inline slots.
- Instances of program class hierarchies keep their class id in the reference-count word instead of a vtable pointer.
Maps andSets with number keys store 16-byte entries.- Together they lower the peak memory of a large TypeScript type checker by 8 to 10%, with the same output and wall time.
Features
for awaititerates untyped values, including async generators,Symbol.asyncIteratorresults and synchronous iterators, whose values are awaited.yield*delegates inside async generators. A JavaScript generator whose return type is inferred asanycompiles, including one that delegates to itself.crypto.createECDHandECDH.convertKeyare supported for the 11 short Weierstrass curves mbedTLS implements, with Node 24's key formats, encodings and errors. Other curves are refused.undici5.x and 7.x:Client,Pool,Agent,request, the global dispatcher,fetchthrough a dispatcher, the error classes and theundici:diagnostics channels follow each version's behavior. Each importing package compiles against its own installed version. The limitations page lists the parts that differ.- Packages marked as never loaded:
"scriptc": { "packages": { "<name>": "unloaded" } }in the entry'spackage.jsonmakesrequire("<name>")throw Node'sMODULE_NOT_FOUND, so optional dependencies take their absent branch. util.promisifyof an untyped function returns a promise-returning wrapper at run time.- Destructuring with runtime computed keys (
const { [name]: v } = source) reads records and untyped values. A missing key givesundefinedand the element default applies. - Import cycles compile when a class is used only after both modules finish initializing. Reaching the class earlier throws Node's
ReferenceError: Cannot access 'X' before initialization. awaitof an untyped value adopts thenables, including objects that definethenat run time.Promise.prototypeandpromise.constructorcan be read as values. A child process can be given new properties and awaited as a thenable.- Function values convert their results and arguments when they are stored in a slot whose signature has the same structure but a different shape, such as a function returning
Promise<Message>stored in a() => Promise<Message | undefined>slot. - Declarations from SDKs and shared type packages: index signatures of
any,any[]members, classes that a package only declares, andPickviews of classes compile. - Builtins as values:
bind,callandapplywork on builtin functions, andBuffer.byteLength,Buffer.compare,Buffer.alloc,JSON.parse,fs.realpathSyncandString.prototypemethods can be read as values.eventsandEventEmittercan be used as untyped constructor values. - JavaScript sources:
- Bitwise operators convert boolean operands as
ToNumberdoes. arguments.lengthcan be read in sloppy-mode functions with named parameters.- A function value in an untyped slot may have any signature.
- Prototype methods take their JSDoc parameter types.
- A top-level function declaration that its module reassigns works.
- A
varcan be overwritten with a different kind of value. - A default-export object literal can hold class values.
- Bitwise operators convert boolean operands as
- Index-signature records answer
"k" in recordfor literal keys. A fresh array literal spreads into an array of a wider element type. Aconstannotated with a type that resolves toanyholds an untyped object. Set.prototype.addreturns the Set when its result is used, sos.add(a).add(b)compiles.execworks on regular expressions held asany, and a numeric enum from a package can be read by a runtime key (DiagLogLevel[level]).dns.lookupanswers that are decided at run time can be read as untyped values,net.connectaccepts Node'slookupoption, anddns.lookuppasses theADDRCONFIG,V4MAPPEDandALLhints.util.inspect,console.logandutil.formatprint program-declaredErrorsubclasses with Node's constructor-name header ([Plain [Error]: m]).- Typed arrays:
forEach,map,filter,some,every,find,findIndex,findLast,findLastIndex,reduceandreduceRightcompile on every typed-array kind.mapandfilterreturn the receiver's kind (a Buffer for a Buffer), and callbacks receive the element, index and array.BYTES_PER_ELEMENTreads answer the element size, including on untyped values. Reads andintests of members Node 24's typed-array and Buffer prototypes lack answerundefinedandfalse, as in Node. Destructuring assignment into typed-array elements compiles. Math.maxandMath.minwith spreads among other arguments (Math.max(...xs, 5)) compile, with Node's NaN and ±0 rules.- Generic arrow and function-expression bindings inside functions compile when they use nothing from the enclosing function's frame.
const xs = []inside a function takes the element type its later writes give it, as TypeScript evolves it, instead ofnumber[]. Bindings whose reads disagree or that are reassigned become dynamic arrays, as at module scope.
Performance
- Loop counters stay integers in more loops: counters compared as
i + cwith a bound, and for-loop counters that are reset or copied inside the loop. A record-boundary scan takes 104 ms (Node: 260 ms), and a typed-array scan takes 318 ms (Node: 801 ms). - Indexed reads of non-ASCII strings are inline in worker executables, and strings of up to 2^24 UTF-16 units keep their unit table. A lexer over a 2.7 MB non-ASCII source takes 190 ms (Node: 327 ms).
- Worker messages: a thread that finds its mailbox empty spins for up to 20 µs before it blocks. 100,000 round trips between the main thread and a worker take 360 ms, a quarter of Node's time.
- Local-time
Dategetters cache the zone offset, UTC getters reuse the last decomposition, and integers 0 to 99 convert to strings without allocating. Local-time date formatting takes 0.81 times Node's time. - Untyped code:
%and the bitwise operators run on integers for integral operands,o.x += dupdates a number property in place, and arraylengthreads answer from the element count. - Sorts by several keys (comparators that chain field differences with
||, including optional number fields with a default) sort by key in the runtime instead of calling the comparator, about 24% faster on a benchmark of such sorts. - Worker executables start their threads faster: the allocator marks its memory for core dumps only when the system would write a core, which removes about 680 calls that hold the address-space lock as threads start. tsc-ts checks tRPC 6% faster at 16 checkers.
- The native compiler walks IR without a closure per node, about 3 to 10% faster in the frontend and LLVM emission phases of large builds.
- Full cycle collections that the death rule requests run at the event loop's idle moments. Watch-mode rebuilds of large projects are 6 to 11% faster.
Fixes
a?.b.con ananyvalue short-circuits only the guarded step, as in Node: it isundefinedwhenais nullish and throws whena.bis undefined.- A failed checked cast to an optional record names the deepest failing path, for example
expected string at $.order.owner.id, got number. - Spreading a non-iterable value held as
anythrows Node'sx is not iterablemessage. process.versionsentries that the runtime doesn't have read asundefinedin projects that use@types/node.- Functions stored in JavaScript object literals get Node's names, taken from the property key.
monitorEventLoopDelay().percentile()returns 511 on an empty histogram, as Node does.- A CommonJS
varstatement that mixesrequire()declarators with other bindings no longer crashes. - Untyped async methods called through overrides or untyped receivers return their promises, which fixes internal compiler errors in programs that use zod's
parseAsyncthis way. - TypeScript enum exports in compiled packages stay
undefineduntil they are assigned, as in Node. - Element writes accept a possibly undefined index (typed-array writes, compound and update writes, element assignments used as values and destructuring targets) and convert it with ToNumber, as element reads already did.