diff --git a/example/gulpfile.coffee b/example/gulpfile.coffee index b240fa09..ed49d688 100644 --- a/example/gulpfile.coffee +++ b/example/gulpfile.coffee @@ -10,6 +10,6 @@ export compressPhp = -> mode: if isWindows then "safe" else "fast" silent: process.stdout.isTTY - return gulp.src "path/to/**/*.php", read: false + gulp.src "path/to/**/*.php", read: false .pipe phpMinify options .pipe gulp.dest "path/to/out" diff --git a/lib/fast_transformer.d.ts b/lib/fast_transformer.d.ts new file mode 100644 index 00000000..e63b30e0 --- /dev/null +++ b/lib/fast_transformer.d.ts @@ -0,0 +1,26 @@ +import {Transformer} from "./transformer.js"; + +/** + * Removes comments and whitespace from a PHP script, by calling a Web service. + */ +export class FastTransformer implements Transformer { + + /** + * Creates a new fast transformer. + * @param executable The path to the PHP executable. + */ + constructor(executable?: string); + + /** + * Closes this transformer and releases any resources associated with it. + * @returns Resolves when the transformer is finally disposed. + */ + close(): Promise; + + /** + * Processes a PHP script. + * @param file The path to the PHP script. + * @returns The transformed script. + */ + transform(file: string): Promise; +} diff --git a/lib/index.d.ts b/lib/index.d.ts index 0a9dbdf2..fb2dd80d 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,4 +1,5 @@ import {Transform} from "node:stream"; +export * from "./fast_transformer.js"; export * from "./safe_transformer.js"; export * from "./transformer.js"; diff --git a/lib/safe_transformer.d.ts b/lib/safe_transformer.d.ts index cd13e1de..cd83b09e 100644 --- a/lib/safe_transformer.d.ts +++ b/lib/safe_transformer.d.ts @@ -5,11 +5,6 @@ import {Transformer} from "./transformer.js"; */ export class SafeTransformer implements Transformer { - /** - * The path to the PHP executable. - */ - executable: string; - /** * Creates a new safe transformer. * @param executable The path to the PHP executable. diff --git a/src/cli.coffee b/src/cli.coffee new file mode 100644 index 00000000..e69de29b diff --git a/src/fast_transformer.coffee b/src/fast_transformer.coffee new file mode 100644 index 00000000..8a30ff7b --- /dev/null +++ b/src/fast_transformer.coffee @@ -0,0 +1,52 @@ +import {spawn} from "node:child_process" +import {createServer} from "node:net" +import {join, normalize, resolve} from "node:path" +import {setTimeout} from "node:timers" + +# Removes comments and whitespace from a PHP script, by calling a Web service. +export class FastTransformer + + # Creates a new fast transformer. + constructor: (executable = "php") -> + + # The path to the PHP executable. + @_executable = normalize executable + + # The port that the PHP process is listening on. + @_port = -1 + + # The underlying PHP process. + @_process = null + + # Closes this transformer and releases any resources associated with it. + close: -> + @_process?.kill() + @_process = null + Promise.resolve() + + # Starts the underlying PHP process and begins accepting connections. + listen: -> if @_process? then Promise.resolve @_port else + @_port = await @_getPort() + args = ["-S", "127.0.0.1:#{@_port}", "-t", join(import.meta.dirname, "../www")] + new Promise (fulfill, reject) => + spawn @_executable, args, stdio: ["ignore", "pipe", "ignore"] + .on "error", reject + .on "spawn", => setTimeout (=> fulfill @_port), 1000 + + # Processes a PHP script. + transform: (file) -> + await @listen() + url = new URL "http://127.0.0.1:#{@_port}/index.php" + url.searchParams.set "file", resolve(file) + + response = await fetch url + throw Error("An error occurred while processing the script: #{file}") unless response.ok + response.text() + + # Gets an ephemeral TCP port chosen by the system. + _getPort: -> new Promise (fulfill, reject) -> + socket = createServer().on "error", reject + socket.unref() + socket.listen host: "127.0.0.1", port: 0, -> + {port} = socket.address() + socket.close -> fulfill port diff --git a/src/gulp_plugin.coffee b/src/gulp_plugin.coffee new file mode 100644 index 00000000..e69de29b diff --git a/src/index.coffee b/src/index.coffee index 3d4252b3..1dab24c4 100644 --- a/src/index.coffee +++ b/src/index.coffee @@ -1 +1,2 @@ +export * from "./fast_transformer.js" export * from "./safe_transformer.js" diff --git a/src/php_minifier/FastTransformer.hx b/src/php_minifier/FastTransformer.hx deleted file mode 100644 index f844ec57..00000000 --- a/src/php_minifier/FastTransformer.hx +++ /dev/null @@ -1,69 +0,0 @@ -package php_minifier; - -import asys.FileSystem; -import js.Node; -import js.node.ChildProcess; -import js.node.child_process.ChildProcess as Process; -import js.node.Net; -import tink.QueryString; -import tink.http.Client; -using haxe.io.Path; - -/** Removes comments and whitespace from a PHP script, by calling a Web service. **/ -class FastTransformer implements Transformer { - - /** The address that the server is listening on. **/ - static inline final address = "127.0.0.1"; - - /** The path to the PHP executable. **/ - final executable: String; - - /** The port that the PHP process is listening on. **/ - var port = -1; - - /** The underlying PHP process. **/ - var process: Null = null; - - /** Creates a new fast transformer. **/ - public function new(executable = "php") - this.executable = executable.normalize(); - - /** Closes this transformer and releases any resources associated with it. **/ - public function close(): Promise { - process?.kill(); - process = null; - return Noise; - } - - /** Processes a PHP script. **/ - public function transform(file: String): Promise - return listen() - .next(_ -> Client.fetch('http://$address:$port/index.php?${QueryString.build({file: FileSystem.absolutePath(file)})}').all()) - .next(response -> response.body.toString()); - - /** Gets an ephemeral TCP port chosen by the system. **/ - function getPort(): Promise { - final trigger = Promise.trigger(); - final socket = Net.createServer().on("error", error -> trigger.reject(Error.ofJsError(error))); - socket.listen(0, address, () -> { - final port = socket.address().port; - socket.close(() -> trigger.resolve(port)); - }); - - socket.unref(); - return trigger.asPromise(); - } - - /** Starts the underlying PHP process and begins accepting connections. **/ - function listen(): Promise - return process != null ? Noise : getPort().next(tcpPort -> { - final trigger: PromiseTrigger = Promise.trigger(); - port = tcpPort; - process = ChildProcess - .spawn(executable, ["-S", '$address:$port', "-t", Path.join([Node.__dirname, "../www"])], {stdio: [Ignore, Pipe, Ignore]}) - .on("error", error -> trigger.reject(Error.ofJsError(error))) - .on("spawn", () -> Future.delay(1_000, Noise).handle(trigger.resolve)); - - trigger.asPromise(); - }); -} diff --git a/src/php_minifier/SafeTransformer.hx b/src/php_minifier/SafeTransformer.hx deleted file mode 100644 index efdbf667..00000000 --- a/src/php_minifier/SafeTransformer.hx +++ /dev/null @@ -1,27 +0,0 @@ -package php_minifier; - -import asys.FileSystem; -import js.node.ChildProcess; -using haxe.io.Path; - -/** Removes comments and whitespace from a PHP script, by calling a PHP process. **/ -class SafeTransformer implements Transformer { - - /** The path to the PHP executable. **/ - final executable: String; - - /** Creates a new safe transformer. **/ - public function new(executable = "php") - this.executable = executable.normalize(); - - /** Closes this transformer and releases any resources associated with it. **/ - public function close(): Promise - return Noise; - - /** Processes a PHP script. **/ - public function transform(file: String): Promise - return Promise.irreversible((resolve, reject) -> { - final callback = (error, stdout, stderr) -> error != null ? reject(Error.ofJsError(error)) : resolve(stdout); - ChildProcess.execFile(executable, ["-w", FileSystem.absolutePath(file)], {maxBuffer: 20 * 1_024 * 1_024}, callback); - }); -} diff --git a/src/php_minifier/Transformer.hx b/src/php_minifier/Transformer.hx deleted file mode 100644 index fcf89c17..00000000 --- a/src/php_minifier/Transformer.hx +++ /dev/null @@ -1,11 +0,0 @@ -package php_minifier; - -/** Removes comments and whitespace from a PHP script. **/ -interface Transformer { - - /** Closes this transformer and releases any resources associated with it. **/ - function close(): Promise; - - /** Processes a PHP script. **/ - function transform(file: String): Promise; -} diff --git a/src/safe_transformer.coffee b/src/safe_transformer.coffee index a62a8319..367e8a17 100644 --- a/src/safe_transformer.coffee +++ b/src/safe_transformer.coffee @@ -12,10 +12,10 @@ export class SafeTransformer constructor: (executable = "php") -> # The path to the PHP executable. - @executable = normalize executable + @_executable = normalize executable # Closes this transformer and releases any resources associated with it. close: -> Promise.resolve() # Processes a PHP script. - transform: (file) -> (await run @executable, ["-w", resolve file], maxBuffer: 20 * 1024 * 1024).stdout + transform: (file) -> (await run @_executable, ["-w", resolve file], maxBuffer: 20 * 1024 * 1024).stdout diff --git a/test/fast_transformer_test.coffee b/test/fast_transformer_test.coffee new file mode 100644 index 00000000..a89e1d04 --- /dev/null +++ b/test/fast_transformer_test.coffee @@ -0,0 +1,34 @@ +import {FastTransformer} from "@cedx/php-minifier" +import {doesNotReject, ok} from "node:assert/strict" +import {after, describe, it} from "node:test" + +# Tests the features of the `FastTransformer` class. +describe "FastTransformer", -> + describe "close()", -> + it "should not reject, even if called several times", -> + transformer = new FastTransformer + await doesNotReject transformer.listen() + await doesNotReject transformer.close() + await doesNotReject transformer.close() + + describe "listen()", -> + it "should not reject, even if called several times", -> + transformer = new FastTransformer + await doesNotReject transformer.listen() + await doesNotReject transformer.listen() + await doesNotReject transformer.close() + + describe "transform()", -> + map = new Map [ + ["should remove the inline comments", ""] + ["should remove the multi-line comments", "namespace dummy; class Dummy"] + ["should remove the single-line comments", "$className = get_class($this); return $className;"] + ["should remove the whitespace", "__construct() { $this->property"] + ] + + transformer = new FastTransformer + after -> transformer.close() + + for [key, value] from map then it key, -> + output = await transformer.transform "res/sample.php" + ok output.includes value diff --git a/test/gulp_plugin_test.coffee b/test/gulp_plugin_test.coffee new file mode 100644 index 00000000..e69de29b diff --git a/test/php_minifier/FastTransformerTest.hx b/test/php_minifier/FastTransformerTest.hx deleted file mode 100644 index b1884f42..00000000 --- a/test/php_minifier/FastTransformerTest.hx +++ /dev/null @@ -1,40 +0,0 @@ -package php_minifier; - -using StringTools; - -/** Tests the features of the `FastTransformer` class. **/ -@:asserts final class FastTransformerTest { - - /** Creates a new test. **/ - public function new() {} - - /** Tests the `close()` method. **/ - @:access(php_minifier.FastTransformer.listen) - public function close() { - final transformer = new FastTransformer(); - Promise.inSequence([transformer.listen().noise(), transformer.close(), transformer.close()]).handle(asserts.handle); - return asserts; - } - - /** Tests the `listen()` method. **/ - @:access(php_minifier.FastTransformer.listen) - public function listen() { - final transformer = new FastTransformer(); - Promise.inSequence([transformer.listen().noise(), transformer.listen().noise(), transformer.close()]).handle(asserts.handle); - return asserts; - } - - /** Tests the `transform()` method. **/ - @:variant("") - @:variant("namespace dummy; class Dummy") - @:variant("$className = get_class($this); return $className;") - @:variant("__construct() { $this->property") - public function transform(output: String) { - final transformer = new FastTransformer(); - transformer.transform("res/sample.php") - .next(script -> transformer.close().next(_ -> asserts.assert(script.contains(output)))) - .handle(asserts.handle); - - return asserts; - } -} diff --git a/test/php_minifier/SafeTransformerTest.hx b/test/php_minifier/SafeTransformerTest.hx deleted file mode 100644 index d1a4efef..00000000 --- a/test/php_minifier/SafeTransformerTest.hx +++ /dev/null @@ -1,31 +0,0 @@ -package php_minifier; - -using StringTools; - -/** Tests the features of the `SafeTransformer` class. **/ -@:asserts final class SafeTransformerTest { - - /** Creates a new test. **/ - public function new() {} - - /** Tests the `close()` method. **/ - public function close() { - final transformer = new SafeTransformer(); - Promise.inSequence([transformer.close(), transformer.close()]).handle(asserts.handle); - return asserts; - } - - /** Tests the `transform()` method. **/ - @:variant("") - @:variant("namespace dummy; class Dummy") - @:variant("$className = get_class($this); return $className;") - @:variant("__construct() { $this->property") - public function transform(output: String) { - final transformer = new SafeTransformer(); - transformer.transform("res/sample.php") - .next(script -> transformer.close().next(_ -> asserts.assert(script.contains(output)))) - .handle(asserts.handle); - - return asserts; - } -} diff --git a/test/safe_transformer_test.coffee b/test/safe_transformer_test.coffee index 44f85b8d..92e818f8 100644 --- a/test/safe_transformer_test.coffee +++ b/test/safe_transformer_test.coffee @@ -11,17 +11,16 @@ describe "SafeTransformer", -> await doesNotReject transformer.close() describe "transform()", -> - map = new Map([ + map = new Map [ ["should remove the inline comments", ""] ["should remove the multi-line comments", "namespace dummy; class Dummy"] ["should remove the single-line comments", "$className = get_class($this); return $className;"] ["should remove the whitespace", "__construct() { $this->property"] - ]) + ] - script = "res/sample.php" transformer = new SafeTransformer after -> transformer.close() for [key, value] from map then it key, -> - output = await transformer.transform script + output = await transformer.transform "res/sample.php" ok output.includes value