UNPKG

@cedx/php-minifier

Version:

Minify PHP source code by removing comments and whitespace.

99 lines (86 loc) 2.88 kB
import {spawn, type ChildProcess} from "node:child_process"; import {createServer, type AddressInfo} from "node:net"; import {join, normalize, resolve} from "node:path"; import {setTimeout} from "node:timers"; import type {ITransformer} from "./ITransformer.js"; /** * Removes comments and whitespace from a PHP script, by calling a Web service. */ export class FastTransformer implements ITransformer { /** * The path to the PHP executable. */ readonly #executable: string; /** * The port that the PHP process is listening on. */ #port = -1; /** * The underlying PHP process. */ #process: ChildProcess|null = null; /** * Creates a new fast transformer. * @param executable The path to the PHP executable. */ constructor(executable = "php") { this.#executable = normalize(executable); } /** * Releases any resources associated with this object. * @returns Resolves when this object is finally disposed. */ [Symbol.asyncDispose](): Promise<void> { return this.close(); } /** * Closes this transformer. * @returns Resolves when the transformer has been closed. */ close(): Promise<void> { this.#process?.kill(); this.#process = null; return Promise.resolve(); } /** * Starts the underlying PHP process and begins accepting connections. * @returns The TCP port used by the PHP process. */ async listen(): Promise<number> { if (this.#process) return Promise.resolve(this.#port); this.#port = await this.#getPort(); return new Promise((fulfill, reject) => { const args = ["-S", `127.0.0.1:${this.#port}`, "-t", join(import.meta.dirname, "../www")]; this.#process = spawn(this.#executable, args, {stdio: ["ignore", "pipe", "ignore"]}) .on("error", reject) .on("spawn", () => setTimeout(() => fulfill(this.#port), 1_000)); }); } /** * Processes a PHP script. * @param file The path to the PHP script. * @returns The transformed script. * @throws `Error` when an error occurred while processing the script. */ async transform(file: string): Promise<string> { const port = await this.listen(); const url = new URL(`http://127.0.0.1:${port}/index.php`); url.searchParams.set("file", resolve(file)); const response = await fetch(url); if (response.ok) return await response.text(); throw Error(`An error occurred while processing the script: ${file}`); } /** * Gets an ephemeral TCP port chosen by the system. * @returns The TCP port chosen by the system. */ #getPort(): Promise<number> { return new Promise((fulfill, reject) => { const server = createServer().unref().on("error", reject); server.listen({host: "127.0.0.1", port: 0}, () => { const {port} = server.address() as AddressInfo; server.close(() => fulfill(port)); }); }); } }