Skip to content

Commit 3251000

Browse files
committed
feat(oxfmt): use prettier directly and bundle prettier (#15544)
`@prettier/sync` is the synchronous version of `prettier`, which uses the `Worker` to hack it and make it synchronous. Everything worked fine before we wanted to bundle them together. However, it hangs when they are bundled. I haven't found the reason why it hangs. Anyway, I don't think `@prettier/sync` is a good choice because it is not `real` synchronous, which sacrifices some performance to make it available. Note: I am not familiar with NAPI, so I'm not sure whether the API is used correctly.
1 parent f4b75b6 commit 3251000

File tree

7 files changed

+19
-50
lines changed

7 files changed

+19
-50
lines changed

apps/oxfmt/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
"node": "^20.19.0 || >=22.12.0"
2121
},
2222
"dependencies": {
23-
"prettier": "3.6.2",
24-
"@prettier/sync": "0.6.1"
23+
"prettier": "3.6.2"
2524
},
2625
"devDependencies": {
2726
"@types/node": "catalog:",

apps/oxfmt/src-js/embedded.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import prettier from '@prettier/sync';
1+
import prettier from 'prettier';
22

33
// Map template tag names to Prettier parsers
44
const TAG_TO_PARSER: Record<string, string> = {
@@ -25,27 +25,22 @@ const TAG_TO_PARSER: Record<string, string> = {
2525
* @param code - The code to format
2626
* @returns Formatted code
2727
*/
28-
export function formatEmbeddedCode(tagName: string, code: string): string {
28+
export async function formatEmbeddedCode(tagName: string, code: string): Promise<string> {
2929
const parser = TAG_TO_PARSER[tagName];
3030

3131
if (!parser) {
3232
// Unknown tag, return original code
3333
return code;
3434
}
3535

36-
try {
37-
const formatted = prettier.format(code, {
36+
return prettier
37+
.format(code, {
3838
parser,
3939
printWidth: 80,
4040
tabWidth: 2,
4141
semi: true,
4242
singleQuote: false,
43-
});
44-
45-
// Remove trailing newline that Prettier adds
46-
return formatted.trimEnd();
47-
} catch {
48-
// If Prettier fails to format, return original code
49-
return code;
50-
}
43+
})
44+
.then((formatted) => formatted.trimEnd())
45+
.catch(() => code);
5146
}

apps/oxfmt/src/prettier_plugins/external_formatter.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::sync::Arc;
22

33
use napi::{
44
Status,
5-
bindgen_prelude::FnArgs,
5+
bindgen_prelude::{FnArgs, Promise, block_on},
66
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
77
};
88
use oxc_formatter::EmbeddedFormatterCallback;
@@ -13,7 +13,7 @@ pub type JsFormatEmbeddedCb = ThreadsafeFunction<
1313
// Input arguments
1414
FnArgs<(String, String)>, // (tag_name, code) as separate arguments
1515
// Return type (what JS function returns)
16-
String,
16+
Promise<String>,
1717
// Arguments (repeated)
1818
FnArgs<(String, String)>,
1919
// Error status
@@ -65,7 +65,7 @@ pub fn wrap_format_embedded(cb: JsFormatEmbeddedCb) -> EmbeddedFormatterCallback
6565
let status = cb.call_with_return_value(
6666
FnArgs::from((tag_name_str.clone(), code_str)),
6767
ThreadsafeFunctionCallMode::Blocking,
68-
move |result: Result<String, napi::Error>, _env| {
68+
move |result: Result<Promise<String>, napi::Error>, _env| {
6969
// Send the result through the channel
7070
let _ = tx.send(result);
7171
Ok(())
@@ -80,7 +80,9 @@ pub fn wrap_format_embedded(cb: JsFormatEmbeddedCb) -> EmbeddedFormatterCallback
8080

8181
// Wait for the result from the channel
8282
match rx.recv() {
83-
Ok(Ok(formatted)) => Ok(formatted),
83+
Ok(Ok(promise)) => block_on(promise).map_err(|e| {
84+
format!("JS formatter promise rejected for tag '{tag_name_str}': {e}")
85+
}),
8486
Ok(Err(e)) => Err(format!("JS formatter failed for tag '{tag_name_str}': {e}")),
8587
Err(_) => {
8688
Err(format!("Failed to receive result from JS formatter for tag '{tag_name_str}'"))

apps/oxfmt/src/run.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ use crate::{
2424
#[allow(clippy::trailing_empty_array, clippy::unused_async)] // https://github.com/napi-rs/napi-rs/issues/2758
2525
#[napi]
2626
pub async fn format(args: Vec<String>, format_embedded_cb: JsFormatEmbeddedCb) -> bool {
27-
format_impl(&args, format_embedded_cb).report() == ExitCode::SUCCESS
27+
format_impl(args, format_embedded_cb).report() == ExitCode::SUCCESS
2828
}
2929

3030
/// Run the formatter.
31-
fn format_impl(args: &[String], format_embedded_cb: JsFormatEmbeddedCb) -> CliRunResult {
31+
#[expect(clippy::needless_pass_by_value)]
32+
fn format_impl(args: Vec<String>, format_embedded_cb: JsFormatEmbeddedCb) -> CliRunResult {
3233
init_tracing();
3334
init_miette();
3435

apps/oxfmt/tsdown.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ export default defineConfig({
99
clean: true,
1010
outDir: 'dist',
1111
shims: false,
12+
noExternal: ['prettier'],
1213
});

npm/oxfmt/package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@
2424
"engines": {
2525
"node": "^20.19.0 || >=22.12.0"
2626
},
27-
"dependencies": {
28-
"prettier": "3.6.2",
29-
"@prettier/sync": "0.6.1"
30-
},
3127
"files": [
3228
"bin/oxfmt",
3329
"configuration_schema.json",

pnpm-lock.yaml

Lines changed: 1 addition & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)