0.4.2
Highlights
- Faster builds of large programs:
- scriptc checks with tsc-ts 0.0.7, which answers contextual types and resolved signatures in batches, so lowering asks for them in one request per chunk instead of one per node. A native build of tsc-ts is 7% faster (110.5 → 103.5 s on 8 vCPUs, lowering 12% faster), with an identical executable.
- The native compiler is built with profile-guided optimization, so tsc-ts builds 6.9% faster cold and 10.7% faster after an edit.
- The native compiler's optimization profile is refreshed, so a native build of tsc-ts is a further 10% faster (124.7 → 111.7 s on 16 vCPUs), with lowering 17% faster.
- The class dispatch passes index their receivers by name instead of testing each one, and they reuse their per-function scans while a function's body is unchanged.
- Checker replies resolve objects the compiler already holds from their ids without decoding them.
- Leaner builds:
- The frontend of an optimized executable's first build runs in a child process, so the native code generator starts after its memory is released. The cold build of tsc-ts peaks at 2.21 GB instead of 3.30 GB.
- The compiler collects its heap as lowering and LLVM emission grow it.
- WebCrypto:
- The
cryptoglobal,node:crypto'swebcryptoandcrypto.subtleare one object. generateKey,importKey,exportKey,encrypt,decrypt,deriveBits,deriveKey,wrapKeyandunwrapKeycover HMAC, AES-GCM, AES-CBC, AES-CTR, AES-KW, PBKDF2, HKDF and JSON Web Keys.crypto.subtlesigns and verifies with ECDSA, RSASSA-PKCS1-v1_5, RSA-PSS and Ed25519.- All of it follows Node 24's argument conversion, algorithm normalization and
DOMExceptions. jose's symmetric JWE paths run as in Node.
- The
- JSX:
.tsx,.jsxand JavaScript files with JSX compile under the project'sjsxoptions.react,react-jsxandreact-jsxdevlower to the factory calls TypeScript emits, with the same keys, fallbacks and source positions.preserveandreact-nativeare refused with a named diagnostic.
- Faster worker messages:
- A message is encoded into one block sized to its contents.
- A typed record is posted from its fields.
- A
'message'listener with a typed parameter builds it from the message, without a checked-dynamic copy. - Messages between
MessageChannelports take 90 ms where they took 250 ms in 0.4.1 (Node: 337 ms), with a peak RSS of 45 MB instead of 115 MB.
- Loops over typed arrays vectorize: a loop to a typed array's length keeps one bounds test for every typed array it indexes.
- Index temporaries such as
const j = i * 4are computed as integers. - Uint8ClampedArray stores convert inline.
- Row-major index terms such as
r * cols + jare tested once before the loop. - Typed-array callback methods call a literal callback directly on an integer counter.
- An 896 × 896 matrix-vector product with four accumulators takes 55 ms instead of 396 ms (Node: 275 ms).
Uint8Array.mapis 4.5 times as fast andInt32Array.some7.8 times as fast as in 0.4.1.
- Index temporaries such as
Features
- Asymmetric keys and signatures in
node:crypto:generateKeyPair,generateKeyPairSync,createPrivateKeyandcreatePublicKeyhandle RSA, EC and Ed25519 keys in PEM, DER and JWK.sign,verify,createSignandcreateVerifysign and verify with those keys, with Node's output and errors.- Ed25519 verification matches Node on 2,083 test vectors, including small-order and mixed-order keys.
crypto.createDiffieHellman,crypto.getDiffieHellmanandcrypto.createDiffieHellmanGroup:- The modp1, modp2 and modp5 to modp18 groups, explicit primes and generators, and generated safe primes are supported, with Node 24's key widths,
verifyErrorbits, key range checks and errors. - Secrets of 0, 1 and p − 1 are rejected, and so are peer keys outside the RFC 5114 subgroups.
- Primes longer than 8192 bits are refused.
- The modp1, modp2 and modp5 to modp18 groups, explicit primes and generators, and generated safe primes are supported, with Node 24's key widths,
- TLS:
tls.connectandtls.createServernegotiateALPNProtocols, and both sockets reportalpnProtocol.minVersionandmaxVersionbound the handshake, andgetProtocol()reports the version it picked.tls.createServertakes anSNICallbackthat answers aSecureContextsynchronously or later.new tls.TLSSocket(socket, { isServer: true, ... })andtls.connect({ socket })run the handshake on an open socket, such as an HTTPCONNECTtunnel.- A client whose peer closes before the handshake completes fails with Node's
ECONNRESET.
- Streams:
- Readable, Writable, Duplex, Transform and PassThrough streams take
objectMode,readableObjectModeandwritableObjectMode, and an object-mode side carries any value throughpush,write,pipe,pipeline,for awaitand'data'listeners. _writeand_transformreceive the encoding argument as Node passes it.CipherivandDecipherivare Transforms: they can be piped, used aspipeline()stages, read and iterated.pipeline()accepts function stages anywhere among its streams, and an array, a string or a synchronous generator as its source.
- Readable, Writable, Duplex, Transform and PassThrough streams take
- Packages compiled from their JavaScript:
- js-yaml and smol-toml compile statically and match Node: ES5 error constructors (
Object.create(Error.prototype),Error.call(this),Error.captureStackTrace) and classes extendingDatebehave as in Node. - TypeScript's UMD module wrapper compiles as the module body, and tsc's
__importDefaultand__importStarhelpers around builtin modules compile as plainrequire()calls. - Interfaces, type aliases and enums that only a package's
.d.tsdeclares keep their types.
- js-yaml and smol-toml compile statically and match Node: ES5 error constructors (
- JavaScript sources:
- Error constructors can be read as values, so
err.constructor === TypeErrorworks. workerDatacan be destructured.- Unary
+converts unions of strings, numbers, booleans,nullandundefinedasNumber()does, so+process.argv[2]compiles. - These compile and behave as in Node:
- chained readline listener registration;
Number.parseIntwithout a radix;matchandsearchwith a possibly undefined regular expression;Buffer.fromover an untypedArrayBuffer, with its byte offset;- Atomics on untyped arrays.
- Buffer methods run on Buffers held as untyped values.
- Error constructors can be read as values, so
fs:- The synchronous functions (
readFileSync,writeFileSync,statSync,readdirSync,mkdirSync,rmSync,openSyncand the others) acceptfile:URLs wherever they accept a path, with Node's conversion errors. fs.promises.rmtakesrecursive,force,maxRetriesandretryDelay, and removing a directory withoutrecursivethrows Node'sERR_FS_EISDIR.
- The synchronous functions (
TypedArray.ofcreates a typed array of its arguments, and a dynamic iterable spread intoArray.of,pushorTypedArray.ofis drained through the iterator protocol.- Global
isFiniteconverts its argument with ToNumber, asisNaNdoes. The Math functions andString.fromCharCodeevaluate every argument before converting any. Object.freeze,Object.sealandObject.preventExtensionsof a function act on its own properties, andObject.isFrozen,Object.isSealedandObject.isExtensibleanswer for functions. @noble/hashes' frozen hashers work.- EventEmitters used through both typed and untyped references share one listener list. An untyped
emitreaches typed listeners, an untypedemit('error')follows Node's contract, andlistenerCount,eventNames,listeners,rawListeners,removeAllListeners,setMaxListenersandgetMaxListenerswork on untyped values. - Template literals,
join,Array.prototype.toStringand the defaultsort()call an object's own or inheritedtoString, asString()does, instead of printing[object Object]. - Classes and generics:
- A generic factory declared to return a constructor type can return one of its local classes, including an anonymous class expression, and
class X extends factory(...)extends it. - Static methods of a function-local class compile.
- Constructor types declared in the program (
new () => unknown, types that also name static members, construct overloads) hold class values. - Members typed by a conditional type over a literal class type argument (
Field<string, "required">) resolve. - Object literals under an erased generic member may hold callbacks with optional or defaulted parameters.
- A generic factory declared to return a constructor type can return one of its local classes, including an anonymous class expression, and
- Assignment and update targets wrapped in parentheses or non-null assertions (
(x) = 1,h.sums[k]! += 10) compile. - Untyped values keep what JavaScript code adds:
- Properties that untyped code adds to a typed object literal stay on it.
- Named properties written to an array through an
unknownview stay on the array, andfor...invisits them after the indices. - An
unknownvalue asserted to a structural type with methods keeps its class's methods. - The type inferred from an empty object literal holds any non-nullish value, as TypeScript's
{}does.
- A WebAssembly module the build finds but does not translate rejects with a
CompileErrorthat names the reason, for example a proposal outside the translated feature set. - SC6101 names the functions that likely run often and use untyped values:
- It shows up to five, most likely hot first, with why their values are untyped: an
any, an untypedJSON.parseresult or an untyped JavaScript file. scriptc coverage --fail-on=dynamicfails on it, and--no-dynamic-tier-warningleaves it out.
- It shows up to five, most likely hot first, with why their values are untyped: an
Performance
- Regular expressions skip start positions that their leading character sets rule out, testing 16 positions at once. Searching 50 MB of text for nine case-insensitive alternations takes 0.83 s instead of 3.88 s (Node: 4.38 s).
- Loop counters stay integers in more loops:
- Counters that step beside a counted loop's counter (
for (let i = 0, p = 0; i < n; i++, p += 4)) stay integers: a pixel-stride loop takes 114 ms instead of 340 ms (Node: 190 ms). - So do two counters that close in on each other (
lo++, hi--), 17% faster on a permutation-reversal loop. - So do counters of loops bounded by a string's length or by an invariant limit other than an array's length, and while-loop counters that nested scans advance. A non-ASCII lexer is 9.5% faster.
- Counters that step beside a counted loop's counter (
- Loops that call a known callback test its closure once before the loop and call the function directly, so captured
letbindings stay in registers.forEachsums with a captured accumulator are 25 to 55% faster. - A speculative integer loop whose check fails finishes the iteration outside the ordinary loop, which stays optimizable, and a loop site that keeps failing stops trying. With the hole-free-prefix test that element reads now take first, a pairwise loop over an array of class instances with number fields is about 1.25 times as fast as in 0.4.1.
- Typed arrays over a SharedArrayBuffer read and write elements inline as relaxed atomic accesses. A numeric kernel over shared
Float64Arrays takes 0.78 s instead of 1.56 s (Node: 0.84 s). Math.pow(2, n)for integer exponents builds the result from its bits. A counter that masks withMath.pow(2, length)takes 11.0 s instead of 20.4 s.- Async calls that never suspend run in place, and so does
await Promise.allof such calls, with Node's microtask turns and rejection handling. An async pipeline takes 5.5 ms instead of 9.7 ms. - Strings:
Number()of a short integer string returns the integer directly.splitbuilds its result in one inline block, andindexOfandsliceon ASCII strings skip the UTF-16 index map. A log summary is 20% faster.charAtands[i]on ASCII strings read the character from a table.- Chains of one-character
replacecalls (the HTML-escape idiom) replace a block of 16 bytes at a time, 12.8% faster on a template renderer.
JSON.parseandJSON.stringifyscan whole words and append parsed typed arrays in bulk, 22% faster to parse and 10% faster to stringify records.- Smaller arrays, maps and program-lifetime instances:
- The array header is 32 bytes instead of 48.
- Arrays of up to 16 known elements keep their slots in their own block.
- Arrays of a known final length get exactly that capacity.
- Small map entry arrays grow in finer steps.
- On Linux,
ProgramLifetimeinstances take a 32-bit header and are referenced in 4 bytes. - They lower the peak memory of a large TypeScript type checker: the 4-byte references alone take a large project's check from 4.13 GB to 3.75 GB, and the 32-bit header saves a further 5%.
- Worker messages queued together are delivered in one batch before immediates run. 20,000 messages interleaved with a worker's output drain in 0.5 s instead of 5.6 s (Node: 0.57 s).
- The cycle collector never buffers instances of classes whose fields hold nothing cycle-capable, and its per-object paths are shorter. A workload of cyclic graphs is about 5% faster.
- Array helpers specialized for a callback that cannot throw skip the pending-exception test, 4.5% faster on a functional pipeline.
- Compile time:
- A file's function declarations are walked once for their references, so lowering a 10 MB single-module bundle takes 69 s instead of 219 s.
- LLVM emission computes its module facts once, 6.9% faster on tsc-ts.
- AST walks visit subtrees by their node ranges and read node words through a
Uint32Array. - Program builds no longer import runtime bitcode, so builds through Node and through the native compiler produce identical executables.
Fixes
- Member reads and method calls on an array hole held in a binding narrowed to one arm of a wide union throw Node's
Cannot read properties of undefinedTypeError. AsyncLocalStorage:- A store entered before an async function's first await, or in a body that never suspends, is the caller's store after the call.
- Each synchronous generator step runs in its resumer's context, as in Node.
- Generic arrow and function-expression bindings used before their declarations behave as in Node, including the
ReferenceErrorof aletorconst. - Assignment expressions into a module
letdeclared without an initializer (new Promise((r) => (resolve = r))) store the value instead of throwing aReferenceError. - The
extendsexpression of a class defined inside a function is evaluated each time the definition runs. - A
super()call that leads anifcondition's comma chain compiles. - A catch binding tests
instanceofcorrectly for values thrown asunknown, rejected promises and runtime errors. - zlib callbacks receive
undefinedas their result when the operation fails, as in Node, and a callback may assign a fallback to it. Readable.pushandWritable.writewithstring | nullorBuffer | nullchunks no longer crash.- Object literals keep their key order through compiler rewrites and on shapes a typed
JSON.parsealso builds, soJSON.stringify,Object.keysandconsole.logmatch Node. console.logprints class instances with their class name in programs that testinstanceofon unknown values.String,NumberandBooleanpassed to array methods convert as their direct calls do, and nested array literals in a recursive JSON-style union build.- A dynamic method call that the program also reads through a prototype finds the method when the instance's property bag does not hold it.
- Storing an element on
Array.prototypefails with SC2020, because holes in native arrays do not read inherited elements. - A recursive type alias in a generic signature no longer crashes the compiler.
- A
Bufferview of an untyped typed array compiles. - JSX programs checked with tsgo compile as they do with tsc-ts.
- Two programs whose entries share a name and build into one directory no longer share a cached translation unit, so a later cache hit always relinks the right program.
- A warm build whose cached LLVM exceeds the engine's string limit reads it in parts instead of failing with
Invalid string length. - The same program compiles to the same executable wherever it sits on disk and whatever order its functions lower in, and edits outside a declaration do not rename the helpers and closures inside it.
- Libraries that use untyped byte arrays link.