-
-
Notifications
You must be signed in to change notification settings - Fork 720
feat(oxfmt): add embedded language formatting with Prettier integration #14820
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. |
be4e7a4 to
6813cf3
Compare
CodSpeed Performance ReportMerging #14820 will not alter performanceComparing Summary
|
|
I have a small suggestion, please forgive my abruptness — perhaps I shouldn't bring this up in the discussion section of this Pull Request, but: Could we consider not strictly binding the relationship between this string tag function and its corresponding language? |
|
Would the following mode be supported? const code = /* ts */`...`; |
75ee9b0 to
ff82793
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although I couldn't identify the cause, it has been confirmed that performance degrades simply because it is the napi version (even without actually formatting).
Benchmark 1: node ./node_modules/oxfmt-current/bin/oxfmt -V
Time (mean ± σ): 27.4 ms ± 0.8 ms [User: 18.1 ms, System: 4.3 ms]
Range (min … max): 26.0 ms … 28.7 ms 10 runs
Benchmark 2: node ./node_modules/oxfmt-napi/dist/cli.js -V
Time (mean ± σ): 49.5 ms ± 0.5 ms [User: 41.6 ms, System: 6.5 ms]
Range (min … max): 48.7 ms … 50.3 ms 10 runs
Summary
node ./node_modules/oxfmt-current/bin/oxfmt -V ran
1.81 ± 0.06 times faster than node ./node_modules/oxfmt-napi/dist/cli.js -V
And for full benchmark:
=========================================
Benchmarking parser.ts (single large file)
=========================================
Benchmark 1: prettier
Time (mean ± σ): 422.1 ms ± 4.2 ms [User: 1070.0 ms, System: 62.0 ms]
Range (min … max): 416.2 ms … 430.4 ms 10 runs
Benchmark 2: prettier+oxc-parser
Time (mean ± σ): 344.9 ms ± 3.7 ms [User: 759.8 ms, System: 52.0 ms]
Range (min … max): 340.2 ms … 350.2 ms 10 runs
Benchmark 3: biome
Time (mean ± σ): 79.8 ms ± 1.4 ms [User: 60.8 ms, System: 9.5 ms]
Range (min … max): 78.6 ms … 83.5 ms 10 runs
Benchmark 4: oxfmt
Time (mean ± σ): 43.5 ms ± 0.5 ms [User: 30.7 ms, System: 9.0 ms]
Range (min … max): 42.9 ms … 44.4 ms 10 runs
Benchmark 5: oxfmt_napi
Time (mean ± σ): 175.8 ms ± 0.6 ms [User: 164.8 ms, System: 11.9 ms]
Range (min … max): 174.7 ms … 176.7 ms 10 runs
Summary
oxfmt ran
1.84 ± 0.04 times faster than biome
4.04 ± 0.05 times faster than oxfmt_napi
7.93 ± 0.12 times faster than prettier+oxc-parser
9.70 ± 0.15 times faster than prettier
=========================================
Benchmarking Outline repository
=========================================
Benchmark 1: prettier
Time (mean ± σ): 4.229 s ± 0.169 s [User: 21.219 s, System: 2.269 s]
Range (min … max): 3.874 s … 4.447 s 10 runs
Benchmark 2: prettier+oxc-parser
Time (mean ± σ): 3.340 s ± 0.044 s [User: 12.569 s, System: 1.464 s]
Range (min … max): 3.285 s … 3.406 s 10 runs
Benchmark 3: biome
Time (mean ± σ): 275.8 ms ± 7.4 ms [User: 1098.2 ms, System: 317.4 ms]
Range (min … max): 268.2 ms … 290.4 ms 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 4: oxfmt
Time (mean ± σ): 102.7 ms ± 2.0 ms [User: 264.8 ms, System: 154.0 ms]
Range (min … max): 100.7 ms … 106.9 ms 10 runs
Benchmark 5: oxfmt_napi
Time (mean ± σ): 578.1 ms ± 17.1 ms [User: 3246.6 ms, System: 205.7 ms]
Range (min … max): 560.6 ms … 620.0 ms 10 runs
Summary
oxfmt ran
2.69 ± 0.09 times faster than biome
5.63 ± 0.20 times faster than oxfmt_napi
32.52 ± 0.76 times faster than prettier+oxc-parser
41.17 ± 1.83 times faster than prettier
outline repo contain 26 files which have css`...` calls.
The results are not improved so much after I manually deleted that 26 files.
(I hope that it might be my end's problem?)
- Run
pnpm buildinoxcrepoapps/oxfmtdir - Then update
package.jsoninbench-formatterrepo
"oxfmt-current": "npm:oxfmt@0.8.0",
"oxfmt-napi": "file:../oxc/apps/oxfmt"07e3108 to
9023bea
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for formatting embedded languages (CSS, GraphQL, HTML, Markdown) within JavaScript/TypeScript template literals by integrating Prettier through NAPI bindings. The implementation allows oxfmt to detect and format code in tagged template literals (e.g., css\...`, gql`...``) using Prettier's parsers, providing consistent formatting for embedded language blocks.
Key Changes
- Added
EmbeddedFormattertrait with callback-based API for formatting embedded code via Prettier - Implemented NAPI bridge to enable Rust-to-JavaScript interop for external formatting
- Enhanced template literal formatting to detect supported tags and delegate to Prettier when appropriate
Reviewed Changes
Copilot reviewed 28 out of 30 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
crates/oxc_formatter/src/embedded_formatter.rs |
Defines the EmbeddedFormatter trait and callback interface |
crates/oxc_formatter/src/write/template.rs |
Added logic to detect and format embedded templates with proper indentation |
crates/oxc_formatter/src/formatter/context.rs |
Added embedded formatter support to format context |
apps/oxfmt/src/prettier_plugins/external_formatter.rs |
NAPI integration layer wrapping JS callbacks for embedded formatting |
apps/oxfmt/src-js/embedded.ts |
TypeScript implementation using Prettier for formatting embedded code |
apps/oxfmt/test/e2e.test.ts |
E2E tests for embedded language formatting |
apps/oxfmt/package.json |
Added Prettier dependency and build scripts |
justfile |
Added oxfmt build commands |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
09e24fd to
d6f5cd0
Compare
08f4b7a to
cb127b0
Compare
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
Dunqing
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! I made some improvements on the Rust side. It is a good first step for embedded language formatting support; there is a lot of work that needs to be done.
|
|
|
Thanks! Yes, we should. Also I think we need to follow outcome of #15187. |
Merge activity
|
…on (#14820) ## Summary Adds support for formatting embedded languages (CSS, GraphQL, HTML, Markdown) in JavaScript/TypeScript template literals using Prettier via NAPI bindings. ## Motivation JavaScript/TypeScript developers often embed CSS, GraphQL queries, HTML, and Markdown in their code using tagged template literals. Without proper formatting support, these embedded languages remain unformatted, leading to inconsistent code style and reduced readability. This PR enables oxfmt to format these embedded languages using Prettier, providing a seamless formatting experience similar to Prettier's own embedded language support. ## Changes ### Core Functionality - **Embedded language detection**: Recognizes `css`, `styled`, `gql`, `graphql`, `html`, `md`, and `markdown` template tags - **Prettier integration**: Uses `@prettier/sync` for synchronous formatting of embedded code - **NAPI bridge**: Rust-to-JavaScript interop using NAPI ThreadsafeFunction - **Graceful fallback**: Returns original code if formatting fails or tag is unsupported ### Implementation Details **Rust side** (`crates/oxc_formatter/`, `apps/oxfmt/src/prettier_plugins/`): - New `EmbeddedFormatter` trait with callback-based API - NAPI bindings using `FnArgs` pattern for clean multi-argument calls - Template formatter enhancement to detect and format tagged templates - Proper indentation handling: splits Prettier output into lines, uses `hard_line_break()` separators, wraps in `indent()` for context-aware indentation - Arena allocator pattern to avoid borrow checker conflicts **JavaScript side** (`apps/oxfmt/src-js/`): - TypeScript bindings with clean signatures: `formatEmbeddedCode(tagName: string, code: string)` - Prettier configuration: 80 char width, 2 space indent, semicolons, double quotes - Tag-to-parser mapping for supported languages - Error handling with console logging and fallback **Testing**: - E2E test suite using Vitest (8 test cases) - Test fixtures for each supported language - Snapshot testing with timing normalization for stability - All tests passing consistently ## Examples **Before** (unformatted): ```js const styles = css`.button { color: red; background: blue; }`; ``` **After** (formatted with proper indentation): ```js const styles = css` .button { color: red; background: blue; } `; ``` **Supported tags:** ```js // CSS const styles = css`...`; const component = styled`...`; // GraphQL const query = gql`...`; const schema = graphql`...`; // HTML const template = html`...`; // Markdown const docs = md`...`; const readme = markdown`...`; ``` ## Testing ```bash # Run oxfmt tests just oxfmt # Watch mode for development just watch-oxfmt ``` All 8 E2E tests pass: - ✓ embedded_css - ✓ embedded_graphql - ✓ embedded_html - ✓ embedded_markdown - ✓ mixed_embedded - ✓ no_embedded - ✓ unsupported_tag - ✓ check_mode ## Related Related to #13427 ## Future Work Potential enhancements: - Support for additional embedded languages (SQL, YAML, etc.) - Custom Prettier configuration via oxfmt config - Performance optimization for large embedded blocks - Integration with language servers for inline diagnostics
39c90c0 to
898d6fe
Compare
## [0.10.0] - 2025-11-04 ### 🚀 Features - 505252c formatter: Wrap parenthesis for AssignmentExpression that is a key of `PropertyDefinition` (#15243) (Dunqing) - 880b259 formatter: Align import-like formatting the same as Prettier (#15238) (Dunqing) - b77f254 oxfmt,formatter: Support `embeddedLanguageFormatting` option (#15216) (leaysgur) - 898d6fe oxfmt: Add embedded language formatting with Prettier integration (#14820) (Boshen) - e77a48e formatter: Detect code removal feature (#15059) (leaysgur) ### 🐛 Bug Fixes - daacf85 oxfmt: Release build fails (#15262) (Dunqing) - f5d0348 oxfmt: Sync `dependencies` with `npm/oxfmt` and `apps/oxfmt` (#15261) (leaysgur) - 46793d7 formatter: Correct printing comments for `LabeledStatement` (#15260) (Dunqing) - 831ae99 formatter: Multiple comments in `LogicalExpression` and `TSIntersectionType` (#15253) (Dunqing) - 5fa9b1e formatter: Should not indent `BinaryLikeExpression` when it is an argument of `Boolean` (#15250) (Dunqing) - 99e520f formatter: Handle chain expression for `JSXExpressionContainer` (#15242) (Dunqing) - a600bf5 formatter: Correct printing comments for `TaggedTemplateExpression` (#15241) (Dunqing) - a7289e7 formatter: Handle member chain for the call's parent is a chain expression (#15237) (Dunqing) ### 🚜 Refactor - 36ae721 formatter: Simplify the use of `indent` with `soft_line_break_or_space` (#15254) (Dunqing) - cdd8e2f formatter/sort-imports: Split sort_imports modules (#15189) (leaysgur) - 27b4f36 diagnostic: Remove `path` from sender (#15130) (camc314) - 85fb8e8 formatter/sort-imports: Pass options to is_ignored() (#15181) (leaysgur) ### 🧪 Testing - 9d5b34b formatter/sort-imports: Refactor sort_imports tests (#15188) (leaysgur) Co-authored-by: leaysgur <6259812+leaysgur@users.noreply.github.com>
|
Do you think something similar could be done for whole files? e.g. Svelte files. If I ran: could the html, svelte, and json files be offloaded to prettier? That way I wouldn't need to run two format commands: Delegating unsupported files to other formatters would be most helpful. |

Summary
Adds support for formatting embedded languages (CSS, GraphQL, HTML, Markdown) in JavaScript/TypeScript template literals using Prettier via NAPI bindings.
Motivation
JavaScript/TypeScript developers often embed CSS, GraphQL queries, HTML, and Markdown in their code using tagged template literals. Without proper formatting support, these embedded languages remain unformatted, leading to inconsistent code style and reduced readability.
This PR enables oxfmt to format these embedded languages using Prettier, providing a seamless formatting experience similar to Prettier's own embedded language support.
Changes
Core Functionality
css,styled,gql,graphql,html,md, andmarkdowntemplate tags@prettier/syncfor synchronous formatting of embedded codeImplementation Details
Rust side (
crates/oxc_formatter/,apps/oxfmt/src/prettier_plugins/):EmbeddedFormattertrait with callback-based APIFnArgspattern for clean multi-argument callshard_line_break()separators, wraps inindent()for context-aware indentationJavaScript side (
apps/oxfmt/src-js/):formatEmbeddedCode(tagName: string, code: string)Testing:
Examples
Before (unformatted):
After (formatted with proper indentation):
Supported tags:
Testing
All 8 E2E tests pass:
Related
Related to #13427
Future Work
Potential enhancements: