Oxlint v1.72 and Oxfmt v0.57 Land the v0.138 Crates Cycle, Unify the AstBuilder, and Retire the Prettier CSS/GraphQL Fallback

Oxlint v1.72 and Oxfmt v0.57 Land the v0.138 Crates Cycle, Unify the AstBuilder, and Retire the Prettier CSS/GraphQL Fallback

lschvn

Oxlint apps_v1.72.0 and oxfmt apps_v0.57.0 shipped together on the evening of June 29, 2026, on top of crates_v0.138.0. The release is the v0.138 cycle that the v1.71 release notes explicitly predicted: "the last apps release before the v0.138 crates release that will land on Monday 2026-06-29." The cycle finishes the AstBuilder migration that has been running across the v0.135 through v0.137 releases, retires the Prettier fallback for CSS / LESS / SCSS and GraphQL in oxfmt, and ships the largest single perf entry of the v0.137 → v0.138 stretch: the value_type memoization that turns a 20,000-term arithmetic-addition chain from 6.1 seconds to 4.7 milliseconds.

This article covers the Oxlint v1.72.0 apps release, the Oxfmt v0.57.0 apps release, and the crates_v0.138.0 release together because the three are designed to ship as one. Oxlint and oxfmt pull the latest crates line at release time, and the breaking changes in this cycle are concentrated in the crates layer.

AstBuilder unification: the v0.135 → v0.138 migration lands

The biggest story of the v0.138 cycle is the unification of the old and new AstBuilder APIs in the oxc_ast crate. The migration has been running since at least v0.135 in early June and is the largest internal API change Oxc has done since the minifier rewrite.

The pattern is five PRs in this release that move the API surface to a single builder. #23876 (overlookmotel) restricts the oxc_ast::builder module to exporting only the AstBuilder type and the NONE sentinel. #23834 (overlookmotel) switches oxc_ecmascript to the new AstBuilder. #23831 (overlookmotel) switches the transformer to the new AstBuilder. #23875 (overlookmotel) removes the in-tree duplication by unifying the builders. #23877 (overlookmotel) marks the legacy AstBuilder methods #[deprecated]. The cycle also adds an oxc_ast Cargo feature flag disable_old_builder (#23886, #23888) so downstream crates can flip the flag and see what breaks before the legacy methods are removed.

The allocator side gets its own batch of changes. #23675 (overlookmotel) renames the AllocatorAccessor trait to GetAllocator. #23676 (overlookmotel) switches GetAllocator::allocator to take &self instead of self. #23781 (overlookmotel) makes Str and Ident methods take &GetAllocator. Together, the three changes finish the migration to a borrowing allocator API: callers no longer need to clone or own an allocator to build strings and identifiers.

For oxlint and oxfmt users, none of this is visible. For downstream crates that build custom AST layers on top of Oxc, this is the largest breaking change in three cycles. The release notes flag the migration window: legacy methods carry #[deprecated] annotations now, will compile with warnings, and will be removed in a future cycle. The team has been signalling this for two cycles, with the v0.137 release notes already warning about the breaking change in oxc_ast.

The headline perf entry: value_type memoization turns O(n²) into O(n)

The single largest user-visible perf entry in v0.138 is PR #23929 (Dunqing): perf(minifier): memoize value_type to remove its O(n^2) re-walk on long binary chains. The minifier's post-order peephole evaluates Expression::value_type at every node, and on a long left-associative binary chain the evaluation re-walks the entire left subtree (to_primitivevalue_typeto_primitive → …). For N nodes the total cost was O(N²), and the dominant case is non-foldable arithmetic-addition chains: a 20,000-term a + a + … + a took 6,118 ms to minify while the parse and semantic phases were linear.

The fix memoizes value_type by node address on MinifierState, with one cut point at the recursive Expression::value_type method. Each node is computed once, so the total cost becomes O(N) amortized. The cache is exposed as opt-in hooks on GlobalContext (cached_value_type / cache_value_type), and only the minifier's TraverseCtx<MinifierState> supplies a real cache. Every other caller (WithoutGlobalReferenceInformation, the constant-evaluation pass, side-effects analysis, the value_type unit harness) keeps the exact previous, uncached behaviour. The cache is cleared at the single mutation chokepoint (record_mutation, hit by every replace_* / drop_*) and at each per-pass reset.

The numbers, straight from the PR description:

inputbeforeaftereffect
a + a + … × 4,000234 ms0.9 msscaling 4.07x → 2.09x (O(n²) → O(n))
a + a + … × 8,000953 ms1.9 mslinear from here on
a + a + … × 20,0006,118 ms4.7 ms≈1300x faster
antd.js (real code)78.1 ms65.4 ms≈16% faster on real input

The takeaway is that arithmetic-addition chains on minified output (the kind that show up in generated numeric code and in big polyfills) drop by three orders of magnitude. Real-world bundles drop by 16% on the first run, with the gains compounding on larger inputs.

The cycle has five other perf entries worth flagging. semantic: Flatten hoisting_variables to avoid per-scope map allocation (Lawrence Lin) removes the per-scope map allocation that fired on every hoisted-variable update. isolated-declarations: Pool scope maps to avoid per-scope alloc/rehash (Boshen) pools the scope maps used by oxc_isolated_declarations so the per-function alloc / rehash is gone. minifier: Bail member-expr folding before the side-effect walk (Lawrence Lin) folds the cheap cases of a.b.c.d chains before the side-effect walk. parser: Avoid span lookup for arrow expression body (camc314) drops one hashmap lookup per arrow expression. On the formatter side, formatter_core: Add printable-ASCII fast path to TextWidth (Lawrence Lin) gives the formatter's line-width computation a fast path when the text is pure printable ASCII, which covers the vast majority of formatted output.

There is also a cluster of three PRs that move AST node allocation out of Box::new_in into a direct arena allocation: parser: Allocate AST nodes in arena directly (overlookmotel), minifier: Allocate AST nodes in arena directly (overlookmotel), and isolated_declarations: Allocate AST nodes in arena directly (overlookmotel). Together they replace the Box::new_in indirection with a direct bump-allocator write, removing one heap allocation per AST node in the parser, the minifier, and the isolated-declarations pipeline.

Oxfmt v0.57 retires the Prettier fallback for CSS / LESS / SCSS / GraphQL

The biggest structural change in oxfmt is the retirement of the Prettier fallback for the CSS and GraphQL grammars. Before v0.57, oxfmt could format CSS / LESS / SCSS files and GraphQL files, but it did so by shelling out to a bundled Prettier for those grammars. v0.57 retires the fallback and formats both grammars natively in Rust.

The CSS side lands as oxc_formatter_css (leaysgur) and is wired in via the BREAKING entry Format parser:css,less,scss files + css-in-js by oxc_formatter_css (leaysgur). The crate is a standalone formatter implementation that takes the existing CSS / LESS / SCSS parsers, walks the AST, and emits Prettier-compatible output. The release notes explicitly call out css-in-js as supported: styled-components and Emotion-style template literals, which oxfmt already detects and routes to the CSS parser, are now formatted by oxc_formatter_css rather than the Prettier shim.

The GraphQL side lands as oxc_formatter_graphql (leaysgur) and is wired in via the BREAKING entry Support draft syntax with removing prettier fallback (leaysgur). The "draft syntax" part is the part that is genuinely new: oxfmt's previous GraphQL implementation handled the stable 2021 spec; the new one handles the GraphQL October 2021 draft syntax and the working-draft syntax used by Apollo Federation and Yoga. This is a real feature, not just a rename: the Prettier fallback could not handle the draft syntax either, so the upgrade also closes a real format-on-save gap for users on Apollo's stack.

Two prettier-diff parity PRs bring the new formatters' output closer to Prettier's: formatter_css: Improve major prettier diffs (leaysgur) and formatter_graphql: Improve major prettier diffs (leaysgur). The release notes flag that real-world diffs on existing codebases are still expected (the typical 'first run changes a few lines, subsequent runs are idempotent' pattern), but the gap is much smaller than it was at v0.56.

The release also adds formatter_css: Handle frontmatter language (leaysgur), which routes Markdown frontmatter in CSS files (e.g. the --- block at the top of a Vue SFC's <style> block when it has frontmatter metadata) to the frontmatter parser rather than treating it as CSS, and the printable-ASCII fast path in TextWidth mentioned above.

The broader picture is that oxfmt is no longer a 'JS / TS formatter with a Prettier sidecar for everything else.' After v0.57 the JS / TS / JSON / JSONC paths stay on the in-tree formatter (which has been on its own since v0.54), and CSS / LESS / SCSS / css-in-js and GraphQL join them. The remaining grammars Prettier handles (Markdown, YAML, HTML) are not in scope for oxfmt; oxfmt explicitly leaves them to Prettier and to the Prettier 3.9 Rust parsers that landed June 27.

The cycle also touches the migrate command: fix(oxfmt): update --migrate prettier (leaysgur) cleans up the Prettier → oxfmt config migration command to handle the new in-tree CSS / GraphQL formatters. The command remains the recommended path for bringing an existing Prettier config into an oxfmt project; the v0.57 update adds the new grammars to the conversion table.

Oxlint v1.72 features and the perf dump

The Oxlint v1.72.0 apps release ships three features, eighteen bug fixes, and twenty-three performance entries on top of crates_v0.138.0. The headline feature is linter/react: Implement suggestion for no-unknown-property (Mikhail Baev), which adds a Quick Fix to the React no-unknown-property rule. Before v1.72 the rule reported the issue without a code action; editors would flag the property as unknown but the developer had to rename it manually. After v1.72 the editor can offer an in-place rename to the closest known property.

The second feature is ast: Unify old and new AstBuilders (overlookmotel), the same unification that powers the crates_v0.138.0 release. The apps side flips the import path to the unified AstBuilder, which is what the rule work in this cycle builds on.

The third feature is linter: Add schema for eslint/no-restricted-import (Sysix). The schema lets users express the paths and patterns arrays of no-restricted-import through the config schema rather than raw JSON, which closes a long-standing gap in the ESLint → oxlint config migration path.

Eighteen bug fixes range from corner-case false positives to panic-prevention fixes. The standout is linter/prefer-called-exactly-once-with: Avoid out-of-bounds slice panic at end of file (Jerry Zhao), which closed a real panic that could crash the linter on test files where the assertion was the last expression. Other notable fixes: linter/unicorn/custom-error-definition: Handle non-ascii class names (camc314), linter/eslint/no-negated-condition: Add autofix for negated conditions (Yagiz Nizipli), linter/eslint/prefer-destructuring: Skip AssignmentExpression autofixes (camc314), and linter/eslint/no-restricted-globals: Handle shadowed locals (camc314).

Twenty-three performance entries continue the v1.71 bucketed-dispatch story. The headline entries: linter/jsx-a11y: Skip lowercasing non-aria attribute names (Lawrence Lin), which removes one to_ascii_lowercase allocation per JSX attribute; linter/typescript/no-unsafe-declaration-merging: Use keyed binding lookup (Marius Schulz), which replaces a linear lookup with the same keyed-binding table the v1.71 bucketed dispatch introduced; linter/eslint/no-unused-vars: Precompute exported bindings (camc314); linter/unicorn/prefer-number-properties: Speed up global checks (camc314); linter/eslint/no-script-url: Match javascript: prefix without allocating (Lawrence Lin); and linter: Skip traversal without this expressions (camc314), which short-circuits a class of rules when there are no this-expressions in the file at all. Together with the v1.71 bucketed-dispatch baseline, the v1.72 perf entries put oxlint at the point where most rules' wall time is dominated by tree traversal, not by rule-internal allocation churn.

The linter bug fixes also include linter/jsx-a11y/role-supports-aria-props: Ignore nullish prop values (Mikhail Baev) and linter/eslint/no-warning-comments: Avoid dropping generated regex patterns (camc314), both of which close false-positive gaps on real-world JSX. The lsp side gets lsp: Normalize user config path to watch pattern (Sysix), which fixes a long-standing LSP path-resolution bug on Windows.

The Vue typeof define keys feature

PR #23605 by Alexander Lichter adds transformer_plugins: Support typeof define keys. The PR adds support for typeof keys inside the define macro pattern that Vue's compiler-macros plugin matches on. Vue's defineProps, defineEmits, defineModel, defineSlots, and friends accept a runtime form (defineProps<typeof import('./foo').bar>()), and the previous transformer_plugins implementation did not recognize that shape. After v0.138, the transformer_plugins crate matches typeof keys as a sub-pattern of the macro body, so the resulting TypeScript code keeps its type imports through macro expansion.

The pattern is shared by the Vue ecosystem macros that sit on top of defineProps. vue-i18n's defineI18nLocale and defineI18nRoute, Nuxt's auto-imports macros (defineNuxtConfig, defineNuxtPlugin), and the macro patterns in pinia's setup stores all accept a typeof import(...) reference inside the macro body. Before this PR, the transformer would either fail to match the macro (the fallback path generated the macro as a no-op call) or, worse, treat the typeof token as a value and break the type import tracking.

The PR is also the first Oxc-side change of the v0.138 cycle with a Vue ecosystem contributor on it. Alexander Lichter is the maintainer of vue-router and a frequent contributor to the Vue compiler macros conversation; the typeof support unblocks a long-standing issue in the vue-i18n ecosystem and is the clearest signal in this cycle that the transformer_plugins surface is moving toward being usable on real Vue codebases without an upstream vue-tsc pass.

What to watch

Three signals for the next two to three weeks. First, watch for the v0.138.1 patch line and for any backward-compat issue reports on the AstBuilder migration: the legacy methods are marked #[deprecated] in this cycle, but downstream crates that build custom ASTs on top of Oxc will hit warnings now and breakage in the next cycle. The PR cluster around #23877 is the spot to monitor; if a downstream crate (rolldown, biome, vite's Rolldown port, parcel) opens an issue about the new AstBuilder shape, expect a v0.139 cycle with an explicit migration guide. Second, watch for oxfmt v0.58 to ship a graphql-or-css compat parity entry. The prettier-diff parity PRs (#23327, #23419) closed the gap substantially, but the release notes flag real-world diffs as still expected; a v0.58 entry that brings the diff count down to zero on a representative CSS / GraphQL codebase would confirm the new formatters are production-ready for migration. Third, watch for value_type memoization to land in oxc_isolated_declarations or oxc_transformer. The PR #23929 design is explicit that the cache is opt-in via hooks on GlobalContext, and only the minifier supplies a real cache in this cycle. The next cycle is the natural place for the transformer and isolated-declarations to opt in; if they do, type-stripping pipelines will get the same arithmetic-chain perf gain the minifier just got.

The broader lesson is the one the Prettier 3.9 article made two days ago: the JavaScript toolchain is being rebuilt on top of the Rust Oxc stack, one grammar at a time, and the v0.138 cycle closes two more grammars (CSS / LESS / SCSS and GraphQL on the oxfmt side) and finishes an internal API migration (AstBuilder + GetAllocator on the crates side). The same pattern shows up at Cline's SDK migration, at Vite's Vite+ unification (the banner ad on the Oxc website), and at the Bun + Anthropic integration. The shape of 2026 across the Oxc ecosystem is a sequence of large architecture rewrites shipped in stages, with each cycle marking one more component as 'done' and freeing the next cycle to focus on the remaining gaps.

Frequently Asked Questions

Related articles

More coverage with overlapping topics and tags.

Vite 8.1.0 Stable Adds Bundled Dev Mode (15x Startup on 10k React Components), Ships WASM ESM Imports and Chunk Import Map, Extends server.fs.deny With .npmrc and Private Keys
tooling

Vite 8.1.0 Stable Adds Bundled Dev Mode (15x Startup on 10k React Components), Ships WASM ESM Imports and Chunk Import Map, Extends server.fs.deny With .npmrc and Private Keys

Vite 8.1.0 stable, published 2026-06-23 by the Vite Team (announcing-vite8-1), graduates Bundled Dev Mode from an experimental flag into a documented --experimental-bundle entry point (15x faster cold start on a 10k React-component app, 10x faster full reloads, 3x faster cold-start rendering and 40% faster full reloads reported by Linear), stabilises the WASM ESM Integration direct .wasm imports and build.chunkImportMap that shipped in 8.1-beta on 2026-06-15, brings Lightning CSS one step closer to default by adding external-CSS-in-CSS and plugin file dependencies, bumps Rolldown to 1.1.2 (1.1.3 follows the same day), pins rolldown with a tilde range so patch releases flow through without a Vite PR, extends server.fs.deny with .npmrc, .yarnrc.yml, and *.{key,p12,pfx,cer,der}, and emits a runtime warning when envFile: false is used (the deprecation is already documented in the types).
Oxlint v1.71 and Oxfmt v0.56 Ship the v0.137 Crates Wins, Land 18 New Linter Rules, and Tame React Lifecycle Traversal with a Streamed-Iterator Refactor
tooling

Oxlint v1.71 and Oxfmt v0.56 Ship the v0.137 Crates Wins, Land 18 New Linter Rules, and Tame React Lifecycle Traversal with a Streamed-Iterator Refactor

Oxlint apps_v1.71.0 and oxfmt apps_v0.56.0, both published on 2026-06-22, close out the v0.137 crates cycle. Oxlint v1.71 picks up the new treeshaking pure typed arrays minifier pass (#23469), the prefer-query-selector compound selector fix, 28 lint bug fixes (most of them Yunfei He's fixer-rewrite work), 13 performance entries anchored on a bucketed rule dispatch refactor (#23450, #23452, #23482-#23486, #23489, #23492), and the oxlint Tokio-on-LSP-only startup (#23447). Oxfmt v0.56.0 ships 9 bug fixes (CRLF normalization for `// @ts-ignore` suppressed text in #23701 and #23702, the member-chain panic fix #23698, the default-export-with-type-cast preservation #23697) and 3 formatter performance entries that remove arena copies on bigint, numeric-literal, and string-literal text. v1.71 is the first apps release since v1.70.0 on 2026-06-15, and the last one before the v0.138 crates release that will land on Monday 2026-06-29.
SWC v1.15.43 Lands the React Compiler as a First-Class Transform, Fixes a Silent Template Literal Minifier Bug, and Aligns `unsafe_math` with Terser
tooling

SWC v1.15.43 Lands the React Compiler as a First-Class Transform, Fixes a Silent Template Literal Minifier Bug, and Aligns `unsafe_math` with Terser

SWC v1.15.43, published on 2026-06-22 with swc_core v71.0.3, ships the experimental Rust React Compiler as a configurable SWC transform via .swcrc jsc.transform.reactCompiler (PR #11917, 54 files, 12,986 additions); fixes a silent template literal minifier bug where `compress` dropped text after the left template's last interpolation when concatenating two template literals (#11939); gates Number(x) -> +x on both `unsafe: true` AND `unsafe_math: true` to match terser v4.3.11+ (#11944, #11949); corrects scope for ES2022 private property brand checks (#11953); and lands seven internal React Compiler fixes (#11940, #11943, #11946, #11950, #11951, #11952, #11957) plus a parser fix that allows no-default builds (#11956) and a docs entry on the untrusted input security scope (#11937).

Comments

Log in Log in to join the conversation.

No comments yet. Be the first to share your thoughts.