diff --git a/examples/format-file.js b/examples/format-file.js new file mode 100644 index 0000000..a63d15c --- /dev/null +++ b/examples/format-file.js @@ -0,0 +1,13 @@ +import * as fs from "node:fs/promises"; +import * as prettier from "prettier"; +import makeSynchronized from "make-synchronized"; + +export default makeSynchronized(import.meta, async function formatFile(file) { + const config = await prettier.resolveConfig(file); + const content = await fs.readFile(file, "utf8"); + const formatted = await prettier.format(content, { + ...config, + filepath: file, + }); + await fs.writeFile(file, formatted); +}); diff --git a/examples/run-format-file.js b/examples/run-format-file.js new file mode 100644 index 0000000..f20d40e --- /dev/null +++ b/examples/run-format-file.js @@ -0,0 +1,6 @@ +import * as url from "node:url"; +import formatFile from "./format-file.js"; + +console.log( + formatFile(url.fileURLToPath(new URL("../readme.md", import.meta.url))), +); diff --git a/index.cjs b/index.cjs index 7b7be84..ec2fe2f 100644 --- a/index.cjs +++ b/index.cjs @@ -1,168 +1,10 @@ "use strict"; -const { - Worker, - receiveMessageOnPort, - MessageChannel, -} = require("worker_threads"); -const url = require("url"); -const path = require("path"); +const { makeModuleSynchronized } = require("make-synchronized"); -/** -@template {keyof PrettierSynchronizedFunctions} FunctionName -@typedef {(...args: Parameters) => Awaited> } PrettierSyncFunction -*/ - -/** -@typedef {import("prettier")} Prettier -@typedef {{ [FunctionName in typeof PRETTIER_ASYNC_FUNCTIONS[number]]: PrettierSyncFunction }} PrettierSynchronizedFunctions -@typedef {{ [PropertyName in typeof PRETTIER_STATIC_PROPERTIES[number]]: Prettier[PropertyName] }} PrettierStaticProperties -@typedef {PrettierSynchronizedFunctions & PrettierStaticProperties} SynchronizedPrettier -*/ - -const PRETTIER_ASYNC_FUNCTIONS = /** @type {const} */ ([ - "formatWithCursor", - "format", - "check", - "resolveConfig", - "resolveConfigFile", - "clearConfigCache", - "getFileInfo", - "getSupportInfo", -]); - -const PRETTIER_STATIC_PROPERTIES = /** @type {const} */ ([ - "version", - "util", - "doc", -]); - -/** @type {Worker | undefined} */ -let worker; -function createWorker() { - if (!worker) { - worker = new Worker(require.resolve("./worker.js")); - worker.unref(); - } - - return worker; -} - -/** - * @template {keyof PrettierSynchronizedFunctions} FunctionName - * @param {FunctionName} functionName - * @param {string} prettierEntry - * @returns {PrettierSyncFunction} - */ -function createSynchronizedFunction(functionName, prettierEntry) { - return (...args) => { - const signal = new Int32Array(new SharedArrayBuffer(4)); - const { port1: localPort, port2: workerPort } = new MessageChannel(); - const worker = createWorker(); - - worker.postMessage( - { signal, port: workerPort, functionName, args, prettierEntry }, - [workerPort], - ); - - Atomics.wait(signal, 0, 0); - - const { - message: { result, error, errorData }, - } = receiveMessageOnPort(localPort); - - if (error) { - throw Object.assign(error, errorData); - } - - return result; - }; -} - -/** - * @template {keyof PrettierStaticProperties} Property - * @param {Property} property - * @param {string} prettierEntry - */ -function getProperty(property, prettierEntry) { - return /** @type {Prettier} */ (require(prettierEntry))[property]; -} - -/** - * @template {keyof SynchronizedPrettier} ExportName - * @param {() => SynchronizedPrettier[ExportName]} getter - */ -function createDescriptor(getter) { - let value; - return { - get: () => { - value ??= getter(); - return value; - }, - enumerable: true, - }; -} - -/** - * @param {string | URL} entry - */ -function toImportId(entry) { - if (entry instanceof URL) { - return entry.href; - } - - if (typeof entry === "string" && path.isAbsolute(entry)) { - return url.pathToFileURL(entry).href; - } - - return entry; -} - -/** - * @param {string | URL} entry - */ -function toRequireId(entry) { - if (entry instanceof URL || entry.startsWith("file:")) { - return url.fileURLToPath(entry); - } - - return entry; -} - -/** - * @param {object} options - * @param {string | URL} options.prettierEntry - Path or URL to prettier entry. - * @returns {SynchronizedPrettier} - */ function createSynchronizedPrettier({ prettierEntry }) { - const importId = toImportId(prettierEntry); - const requireId = toRequireId(prettierEntry); - - const prettier = Object.defineProperties( - Object.create(null), - Object.fromEntries( - [ - ...PRETTIER_ASYNC_FUNCTIONS.map((functionName) => { - return /** @type {const} */ ([ - functionName, - () => createSynchronizedFunction(functionName, importId), - ]); - }), - ...PRETTIER_STATIC_PROPERTIES.map((property) => { - return /** @type {const} */ ([ - property, - () => getProperty(property, requireId), - ]); - }), - ].map(([property, getter]) => { - return /** @type {const} */ ([property, createDescriptor(getter)]); - }), - ), - ); - - return prettier; + return makeModuleSynchronized(prettierEntry); } module.exports = createSynchronizedPrettier({ prettierEntry: "prettier" }); -// @ts-expect-error Property 'createSynchronizedPrettier' for named export compatibility module.exports.createSynchronizedPrettier = createSynchronizedPrettier; diff --git a/package.json b/package.json index 8306d0c..f2c1820 100644 --- a/package.json +++ b/package.json @@ -44,17 +44,20 @@ ] }, "peerDependencies": { - "prettier": "^3.0.0" + "prettier": "*" }, "devDependencies": { - "@types/node": "20.4.1", - "c8": "8.0.0", + "@types/node": "20.11.5", + "c8": "9.1.0", "npm-run-all": "4.1.5", - "prettier": "3.0.0" + "prettier": "3.2.4" }, "packageManager": "yarn@3.2.2", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" + }, + "dependencies": { + "make-synchronized": "^0.2.1" } } diff --git a/readme.md b/readme.md index da554ed..e93a596 100644 --- a/readme.md +++ b/readme.md @@ -31,6 +31,26 @@ synchronizedPrettier.format("foo( )", { parser: "babel" }); // => 'foo();\n' ``` +This package is a simple wrapper of [`make-synchronized`](https://github.com/fisker/make-synchronized), currently only the functions and primitive values exported from `prettier` is functional, functions not exported directly (eg: `prettier.__debug.parse`) doesn't work, but it can be supported, if you want more functionality, please [open an issue](https://github.com/prettier/prettier-synchronized/issues/new). + +For more complex use cases, it more reasonable to extract into a separate file, and run with [`make-synchronized`](https://github.com/fisker/make-synchronized), example + +```js +import * as fs from "node:fs/promises"; +import * as prettier from "prettier"; +import makeSynchronized from "make-synchronized"; + +export default makeSynchronized(import.meta, async function formatFile(file) { + const config = await prettier.resolveConfig(file); + const content = await fs.readFile(file, "utf8"); + const formatted = await prettier.format(content, { + ...config, + filepath: file, + }); + await fs.writeFile(file, formatted); +}); +``` + ### `createSynchronizedPrettier(options)` #### `options` diff --git a/test.js b/test.js index f929077..ed72a85 100644 --- a/test.js +++ b/test.js @@ -31,7 +31,6 @@ test("version", () => { const fakePrettierRelatedPath = "./a-fake-prettier-to-test.cjs"; const fakePrettierUrl = new URL(fakePrettierRelatedPath, import.meta.url); for (const prettierEntry of [ - fakePrettierRelatedPath, fakePrettierUrl, fakePrettierUrl.href, fileURLToPath(fakePrettierUrl), diff --git a/tsconfig.json b/tsconfig.json index b7fee61..5d8a362 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "checkJs": true, "noEmit": true, "target": "esnext", - "module": "NodeNext" + "module": "NodeNext", }, - "files": ["index.cjs"] + "files": ["index.cjs"], }