From b4e8605a4dba0da1aac0e8e8c26cdfa57c88f210 Mon Sep 17 00:00:00 2001 From: leaysgur <6259812+leaysgur@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:44:28 +0000 Subject: [PATCH] refactor(oxfmt): Clean up `src-js` files and directories (#17040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` . ├── bindings.d.ts ├── bindings.js ├── cli │ ├── migration │ │ ├── init.ts │ │ ├── migrate-prettier.ts │ │ └── shared.ts │ └── worker-proxy.ts ├── cli-worker.ts # CLI worker endpoint ├── cli.ts # CLI endpoint ├── index.ts # API endpoint └── libs └── prettier.ts # Core logic shared by index, cli-worker, cli/worker-proxy ``` By doing this, Node.js API `format()` does not use `worker_threads` anymore. --- apps/oxfmt/src-js/cli-worker.ts | 4 + apps/oxfmt/src-js/cli.ts | 40 ++++--- apps/oxfmt/src-js/{ => cli}/migration/init.ts | 7 +- .../{ => cli}/migration/migrate-prettier.ts | 7 +- .../src-js/{ => cli}/migration/shared.ts | 0 apps/oxfmt/src-js/cli/worker-proxy.ts | 33 ++++++ apps/oxfmt/src-js/index.ts | 16 ++- apps/oxfmt/src-js/libs/prettier.ts | 96 +++++++++++++++ apps/oxfmt/src-js/migration/index.ts | 2 - apps/oxfmt/src-js/prettier-proxy.ts | 109 ------------------ apps/oxfmt/src-js/prettier-worker.ts | 44 ------- apps/oxfmt/tsdown.config.ts | 2 +- 12 files changed, 168 insertions(+), 192 deletions(-) create mode 100644 apps/oxfmt/src-js/cli-worker.ts rename apps/oxfmt/src-js/{ => cli}/migration/init.ts (85%) rename apps/oxfmt/src-js/{ => cli}/migration/migrate-prettier.ts (97%) rename apps/oxfmt/src-js/{ => cli}/migration/shared.ts (100%) create mode 100644 apps/oxfmt/src-js/cli/worker-proxy.ts create mode 100644 apps/oxfmt/src-js/libs/prettier.ts delete mode 100644 apps/oxfmt/src-js/migration/index.ts delete mode 100644 apps/oxfmt/src-js/prettier-proxy.ts delete mode 100644 apps/oxfmt/src-js/prettier-worker.ts diff --git a/apps/oxfmt/src-js/cli-worker.ts b/apps/oxfmt/src-js/cli-worker.ts new file mode 100644 index 0000000000000..126bbe638e5a3 --- /dev/null +++ b/apps/oxfmt/src-js/cli-worker.ts @@ -0,0 +1,4 @@ +// `oxfmt` CLI - Worker Thread Entry Point + +// Re-exports core functions for use in `worker_threads` +export { formatEmbeddedCode, formatFile } from "./libs/prettier"; diff --git a/apps/oxfmt/src-js/cli.ts b/apps/oxfmt/src-js/cli.ts index 0026a294c5976..a698041bd2467 100644 --- a/apps/oxfmt/src-js/cli.ts +++ b/apps/oxfmt/src-js/cli.ts @@ -1,11 +1,13 @@ -import { runCli } from "./bindings.js"; -import { initExternalFormatter, formatEmbeddedCode, formatFile } from "./prettier-proxy.js"; -import { runInit, runMigratePrettier } from "./migration/index.js"; +import { runCli } from "./bindings"; +import { initExternalFormatter, formatEmbeddedCode, formatFile } from "./cli/worker-proxy"; + +// napi-JS `oxfmt` CLI entry point +// See also `run_cli()` function in `./src/main_napi.rs` void (async () => { const args = process.argv.slice(2); - // Call the Rust CLI to parse args and determine mode + // Call the Rust CLI first, to parse args and determine mode // NOTE: If the mode is formatter CLI, it will also perform formatting and return an exit code const [mode, exitCode] = await runCli( args, @@ -14,20 +16,20 @@ void (async () => { formatFile, ); - switch (mode) { - // Handle `--init` and `--migrate` command in JS - case "init": - await runInit(); - break; - case "migrate:prettier": - await runMigratePrettier(); - break; - // Other modes are handled by Rust, just need to set `exitCode` - default: - // NOTE: It's recommended to set `process.exitCode` instead of calling `process.exit()`. - // `process.exit()` kills the process immediately and `stdout` may not be flushed before process dies. - // https://nodejs.org/api/process.html#processexitcode - if (exitCode) process.exitCode = exitCode; - break; + // Migration modes are handled by JS + if (mode === "init") { + await import("./cli/migration/init").then((m) => m.runInit()); + return; + } + if (mode === "migrate:prettier") { + await import("./cli/migration/migrate-prettier").then((m) => m.runMigratePrettier()); + return; } + + // Other modes are handled by Rust, just need to set `exitCode` + + // NOTE: It's recommended to set `process.exitCode` instead of calling `process.exit()`. + // `process.exit()` kills the process immediately and `stdout` may not be flushed before process dies. + // https://nodejs.org/api/process.html#processexitcode + process.exitCode = exitCode!; })(); diff --git a/apps/oxfmt/src-js/migration/init.ts b/apps/oxfmt/src-js/cli/migration/init.ts similarity index 85% rename from apps/oxfmt/src-js/migration/init.ts rename to apps/oxfmt/src-js/cli/migration/init.ts index de98eda5175c9..30444d0f86c05 100644 --- a/apps/oxfmt/src-js/migration/init.ts +++ b/apps/oxfmt/src-js/cli/migration/init.ts @@ -1,11 +1,6 @@ /* oxlint-disable no-console */ -import { - hasOxfmtrcFile, - createBlankOxfmtrcFile, - saveOxfmtrcFile, - exitWithError, -} from "./shared.js"; +import { hasOxfmtrcFile, createBlankOxfmtrcFile, saveOxfmtrcFile, exitWithError } from "./shared"; /** * Run the `--init` command to scaffold a default `.oxfmtrc.json` file. diff --git a/apps/oxfmt/src-js/migration/migrate-prettier.ts b/apps/oxfmt/src-js/cli/migration/migrate-prettier.ts similarity index 97% rename from apps/oxfmt/src-js/migration/migrate-prettier.ts rename to apps/oxfmt/src-js/cli/migration/migrate-prettier.ts index 8f411738c79e0..e3a30153974a7 100644 --- a/apps/oxfmt/src-js/migration/migrate-prettier.ts +++ b/apps/oxfmt/src-js/cli/migration/migrate-prettier.ts @@ -2,12 +2,7 @@ import { join } from "node:path"; import { readFile } from "node:fs/promises"; -import { - hasOxfmtrcFile, - createBlankOxfmtrcFile, - saveOxfmtrcFile, - exitWithError, -} from "./shared.js"; +import { hasOxfmtrcFile, createBlankOxfmtrcFile, saveOxfmtrcFile, exitWithError } from "./shared"; /** * Run the `--migrate prettier` command to migrate various Prettier's config to `.oxfmtrc.json` file. diff --git a/apps/oxfmt/src-js/migration/shared.ts b/apps/oxfmt/src-js/cli/migration/shared.ts similarity index 100% rename from apps/oxfmt/src-js/migration/shared.ts rename to apps/oxfmt/src-js/cli/migration/shared.ts diff --git a/apps/oxfmt/src-js/cli/worker-proxy.ts b/apps/oxfmt/src-js/cli/worker-proxy.ts new file mode 100644 index 0000000000000..ce7d0d4935026 --- /dev/null +++ b/apps/oxfmt/src-js/cli/worker-proxy.ts @@ -0,0 +1,33 @@ +import Tinypool from "tinypool"; +import { resolvePlugins } from "../libs/prettier"; +import type { Options } from "prettier"; + +// Worker pool for parallel Prettier formatting +let pool: Tinypool | null = null; + +export async function initExternalFormatter(numThreads: number): Promise { + pool = new Tinypool({ + filename: new URL("./cli-worker.js", import.meta.url).href, + minThreads: numThreads, + maxThreads: numThreads, + }); + + return resolvePlugins(); +} + +export async function formatEmbeddedCode( + options: Options, + tagName: string, + code: string, +): Promise { + return pool!.run({ options, code, tagName }, { name: "formatEmbeddedCode" }); +} + +export async function formatFile( + options: Options, + parserName: string, + fileName: string, + code: string, +): Promise { + return pool!.run({ options, code, fileName, parserName }, { name: "formatFile" }); +} diff --git a/apps/oxfmt/src-js/index.ts b/apps/oxfmt/src-js/index.ts index 10c39478bcd15..5b40316171a97 100644 --- a/apps/oxfmt/src-js/index.ts +++ b/apps/oxfmt/src-js/index.ts @@ -1,6 +1,12 @@ -import { format as napiFormat } from "./bindings.js"; -import { initExternalFormatter, formatEmbeddedCode, formatFile } from "./prettier-proxy.js"; +import { format as napiFormat } from "./bindings"; +import { resolvePlugins, formatEmbeddedCode, formatFile } from "./libs/prettier"; +// napi-JS `oxfmt` API entry point +// See also `format()` function in `./src/main_napi.rs` + +/** + * Format the given source text according to the specified options. + */ export async function format(fileName: string, sourceText: string, options?: FormatOptions) { if (typeof fileName !== "string") throw new TypeError("`fileName` must be a string"); if (typeof sourceText !== "string") throw new TypeError("`sourceText` must be a string"); @@ -9,9 +15,9 @@ export async function format(fileName: string, sourceText: string, options?: For fileName, sourceText, options ?? {}, - initExternalFormatter, - formatEmbeddedCode, - formatFile, + resolvePlugins, + (options, tagName, code) => formatEmbeddedCode({ options, tagName, code }), + (options, parserName, fileName, code) => formatFile({ options, parserName, fileName, code }), ); } diff --git a/apps/oxfmt/src-js/libs/prettier.ts b/apps/oxfmt/src-js/libs/prettier.ts new file mode 100644 index 0000000000000..40f7747565140 --- /dev/null +++ b/apps/oxfmt/src-js/libs/prettier.ts @@ -0,0 +1,96 @@ +import type { Options } from "prettier"; + +// Lazy load Prettier +// +// NOTE: In the past, statically importing caused issues with `oxfmt --lsp` not starting. +// However, this issue has not been observed recently, possibly due to changes in the bundling configuration. +// Anyway, we keep lazy loading for now to minimize initial load time. +let prettierCache: typeof import("prettier"); + +/** + * TODO: Plugins support + * - Read `plugins` field + * - Load plugins dynamically and parse `languages` field + * - Map file extensions and filenames to Prettier parsers + * + * @returns Array of loaded plugin's `languages` info + */ +export async function resolvePlugins(): Promise { + return []; +} + +const TAG_TO_PARSER: Record = { + // CSS + css: "css", + styled: "css", + // GraphQL + gql: "graphql", + graphql: "graphql", + // HTML + html: "html", + // Markdown + md: "markdown", + markdown: "markdown", +}; + +/** + * Format xxx-in-js code snippets + * + * @returns Formatted code snippet + * TODO: In the future, this should return `Doc` instead of string, + * otherwise, we cannot calculate `printWidth` correctly. + */ +export async function formatEmbeddedCode({ + code, + tagName, + options, +}: { + code: string; + tagName: string; + options: Options; +}): Promise { + // TODO: This should be resolved in Rust side + const parserName = TAG_TO_PARSER[tagName]; + + // Unknown tag, return original code + if (!parserName) return code; + + if (!prettierCache) { + prettierCache = await import("prettier"); + } + + // SAFETY: `options` is created in Rust side, so it's safe to mutate here + options.parser = parserName; + return prettierCache + .format(code, options) + .then((formatted) => formatted.trimEnd()) + .catch(() => code); +} + +/** + * Format non-js file + * + * @returns Formatted code + */ +export async function formatFile({ + code, + parserName, + fileName, + options, +}: { + code: string; + parserName: string; + fileName: string; + options: Options; +}): Promise { + if (!prettierCache) { + prettierCache = await import("prettier"); + } + + // SAFETY: `options` is created in Rust side, so it's safe to mutate here + // We specify `parser` to skip parser inference for performance + options.parser = parserName; + // But some plugins rely on `filepath`, so we set it too + options.filepath = fileName; + return prettierCache.format(code, options); +} diff --git a/apps/oxfmt/src-js/migration/index.ts b/apps/oxfmt/src-js/migration/index.ts deleted file mode 100644 index ca8119beacd6f..0000000000000 --- a/apps/oxfmt/src-js/migration/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./init.js"; -export * from "./migrate-prettier.js"; diff --git a/apps/oxfmt/src-js/prettier-proxy.ts b/apps/oxfmt/src-js/prettier-proxy.ts deleted file mode 100644 index 4c9e151f27708..0000000000000 --- a/apps/oxfmt/src-js/prettier-proxy.ts +++ /dev/null @@ -1,109 +0,0 @@ -import Tinypool from "tinypool"; -import type { Options } from "prettier"; -import type { FormatEmbeddedCodeArgs, FormatFileArgs } from "./prettier-worker.ts"; - -// Worker pool for parallel Prettier formatting -// Used by each exported function -let pool: Tinypool | null = null; - -type InitResult = string[]; -let initResultCache: InitResult | null = null; - -// --- - -/** - * Setup worker pool for Prettier formatting. - * NOTE: Called from Rust via NAPI ThreadsafeFunction with FnArgs - * @param numThreads - Number of worker threads to use (same as Rayon thread count) - * @returns Array of loaded plugin's `languages` info - */ -export async function initExternalFormatter(numThreads: number): Promise { - // NOTE: When called from CLI, it's only called once at the beginning. - // However, when called via API, like `format(fileName, code)`, it may be called multiple times. - // Therefore, allow it by returning cached result. - if (initResultCache !== null) return initResultCache; - - // Initialize worker pool for parallel Prettier formatting - pool = new Tinypool({ - filename: new URL("./prettier-worker.js", import.meta.url).href, - minThreads: numThreads, - maxThreads: numThreads, - }); - - // TODO: Plugins support - // - Read `plugins` field - // - Load plugins dynamically and parse `languages` field - // - Map file extensions and filenames to Prettier parsers - initResultCache = []; - - return initResultCache; -} - -// --- - -// Map template tag names to Prettier parsers -const TAG_TO_PARSER: Record = { - // CSS - css: "css", - styled: "css", - // GraphQL - gql: "graphql", - graphql: "graphql", - // HTML - html: "html", - // Markdown - md: "markdown", - markdown: "markdown", -}; - -/** - * Format embedded code using Prettier. - * NOTE: Called from Rust via NAPI ThreadsafeFunction with FnArgs - * @param options - Prettier configuration as object - * @param tagName - The template tag name (e.g., "css", "gql", "html") - * @param code - The code to format - * @returns Formatted code - */ -export async function formatEmbeddedCode( - options: Options, - tagName: string, - code: string, -): Promise { - const parser = TAG_TO_PARSER[tagName]; - - // Unknown tag, return original code - if (!parser) { - return code; - } - - options.parser = parser; - - return pool!.run({ options, code } satisfies FormatEmbeddedCodeArgs, { - name: "formatEmbeddedCode", - }); -} - -// --- - -/** - * Format whole file content using Prettier. - * NOTE: Called from Rust via NAPI ThreadsafeFunction with FnArgs - * @param options - Prettier configuration as object - * @param parserName - The parser name - * @param fileName - The file name (e.g., "package.json") - * @param code - The code to format - * @returns Formatted code - */ -export async function formatFile( - options: Options, - parserName: string, - fileName: string, - code: string, -): Promise { - options.parser = parserName; - options.filepath = fileName; - - return pool!.run({ options, code } satisfies FormatFileArgs, { - name: "formatFile", - }); -} diff --git a/apps/oxfmt/src-js/prettier-worker.ts b/apps/oxfmt/src-js/prettier-worker.ts deleted file mode 100644 index 984185d2f1006..0000000000000 --- a/apps/oxfmt/src-js/prettier-worker.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { Options } from "prettier"; - -// Lazy load Prettier in each worker thread -// -// NOTE: In the past, statically importing caused issues with `oxfmt --lsp` not starting. -// However, this issue has not been observed recently, possibly due to changes in the bundling configuration. -// Nevertheless, we will keep it as lazy loading just in case. -let prettierCache: typeof import("prettier"); - -// --- - -export type FormatEmbeddedCodeArgs = { - code: string; - options: Options; -}; - -export async function formatEmbeddedCode({ - options, - code, -}: FormatEmbeddedCodeArgs): Promise { - if (!prettierCache) { - prettierCache = await import("prettier"); - } - - return prettierCache - .format(code, options) - .then((formatted) => formatted.trimEnd()) - .catch(() => code); -} - -// --- - -export type FormatFileArgs = { - code: string; - options: Options; -}; - -export async function formatFile({ options, code }: FormatFileArgs): Promise { - if (!prettierCache) { - prettierCache = await import("prettier"); - } - - return prettierCache.format(code, options); -} diff --git a/apps/oxfmt/tsdown.config.ts b/apps/oxfmt/tsdown.config.ts index e405246d9d297..939d04aa277e9 100644 --- a/apps/oxfmt/tsdown.config.ts +++ b/apps/oxfmt/tsdown.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "tsdown"; export default defineConfig({ // Build all entry points together to share Prettier chunks - entry: ["src-js/index.ts", "src-js/cli.ts", "src-js/prettier-worker.ts"], + entry: ["src-js/index.ts", "src-js/cli.ts", "src-js/cli-worker.ts"], format: "esm", platform: "node", target: "node20",