-
-
Notifications
You must be signed in to change notification settings - Fork 218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] Add NPM package with API for Node.js #379
Comments
You're right, I should be looking to provide an NPM package. I'll find out how to do that, but if you have any info that would surely help! |
If you're new to the whole NPM and Node.js ecosystem then it can be a bit overwhelming to do this. I guess the steps are something like this:
import {createRequire} from 'module'
const require = createRequire(import.meta.url) |
I started thinking about how I would like the API to be like and came up with this. // Example of a script using the module
import * as minify from '@tdewolff/minify' // Since there is already a "minify" NPM package it must be scoped
let result
result = await minify.file('/whatever.js')
result = await minify.content('js', 'let i=1; console.log(i)') The module's main file could be something like this: // The code here will run only once, even if the module is imported several places, hence you can connect to the binary here and the exported functions can communicate with it using any variables defined here outside of the functions.
export function file(path) {
return new Promise((resolve, reject) => {
// resolve(minifiedContentOfFile)
// or reject('error message')
})
}
export function content(typeOfContent, content) {
} Of course these things are up to you :) |
That looks pretty good to me, note the interface that esbuild has https://esbuild.github.io/api/#js-specific-details. Also see https://github.com/evanw/esbuild/tree/master/lib for a reference of the NPM package. |
FYI esbuild adopted a new installation strategy in v0.13.0 Any updates on when this can be added? Would like to add your library to https://github.com/privatenumber/minification-benchmarks |
I'm actually almost done with an npm package with an API for minify and automatic download of the native binary. Will release soon. |
Hi @JoakimCh that sound awesome! Thank you for the effort, I'd be happy to integrate it with this repository. Let me know if you need any help! |
Sorry about the late release, I'm struggling with some health problems and had to delay it, but here you guys go: I didn't write any tests or documentation (other than JSDoc in the source-code) and I can't really be bothered with that for now... If this is production ready or not I don't have any opinions about, that's up to anyone to decide themselves. Using this JS API might have some overhead compared to using Minify directly, hence performance tests shouldn't claim that they're testing the native performance of Minify.
All of these functions other than |
@JoakimCh Excellent work! Thank you so much for taking the time, it is highly appreciated :-D In terms of maintenance, do you wish to maintain the NPM port for the future, or would you like to incorporate it with this repository where you and I both can maintain it? Let me know what you think. The case for streams, adding an ASCII code for file separation could possibly work, but otherwise we can make a special function for NPM access that for example accepts JS arrays of files or some JSON or binary structure (protocol buffers?). The idea of file separators could work efficiently (but I want to prevent scanning the file twice, first to find the file separators and then for parsing), though there might be an option to do that better (stop parsing at a file separator and start a new parser). Something like that, or by passing the file lengths of the concatenated files in a binary stream... |
I'm thinking that I can maintain it, since I plan to use your minifier for a server I'm developing. I'll look into how it can be merged with this repository though. When it comes to performance. How does it handle the command What I should do is to write a performance test which does the same, but using the JavaScript API. So we can analyze the performance difference and how to increase it. Then if implementing different ways for JS to communicate with minify we will be able to measure them against each other. |
Yes, the files are minified in parallel. There is no functionality yet to separate a single stream into different files, and simply concatenating is not possible. Am I correct that you're calling the In any case, it might be quite useful if the binary (not the library) would accept streams that separates files using the 0x1C file separator character. I'm going to work out that idea and come back to you ;-) |
Yes, it was easiest for me to use the precompiled minify binary. Since I'm not experienced when it comes to binding Node.js to functions in libraries. I'm writing some tests now (when more complete I will merge them with my GitHub repository). But I wanted to share some of my findings already: My API There was some, but only if I forced only one concurrent minify process, with 4 concurrent ones there were no difference. Part of my test code: const dirents = fs.readdirSync('test_data/', {withFileTypes: true})
for (const dirent of dirents) {
if (dirent.isFile()) {
switch (2) {
case 0: { // node reads the file and feeds it to minify, then node feeds stdout to a file
const readStream = fs.createReadStream('test_data/'+dirent.name)
const writeStream = fs.createWriteStream('nodeAPI_out/'+dirent.name)
jobDonePromises.push(minify.pipe(extname(dirent.name).slice(1), readStream, writeStream))
} break
case 1: { // minify reads the file, node reads stdout
jobDonePromises.push(minify.file('test_data/'+dirent.name))
} break
case 2: { // minify does file to file
jobDonePromises.push(minify.fileToFile('test_data/'+dirent.name, 'nodeAPI_out/'+dirent.name))
} break
}
}
} case 0 was the slowest one, seemed to be 7 times slower JavaScript being single threaded is of course the biggest bottleneck I guess, so reading and writing files using a single thread is probably the problem... I will try to learn some more though about what's going on. |
Yes, a big part of the stream bottleneck will be JS performance. Case 2 has very little data that was send between JS and |
I just uploaded the benchmark: |
Awesome work! I like how it automatically downloads the latest version from GitHub. I'm getting the following results:
The I think we could add one additional test where the files are in-memory (read file content before the test) and test |
Forgot to update: I ended up using bina to install the binary.
|
Nice, thanks for the addition! Obviously it seems that our interface for JS seems different than |
The benchmarks should be fair and unbiased as possible. If there's a specific improvement you can recommend I'd be happy to welcome a PR or issue. With the |
I think it does account for it. A quick test using Appreciate the work a lot by the way! |
I confirmed my data locally via binaries (no JS). I I'm still getting a ~2.7s difference (3.227 s - 500.1 ms). tdewolff/minify
esbuild
Curious what specific commands you're running? |
How strange, I run the same commands as you do, but I get |
Local environment info:
But for minification-benchmarks, benchmarking runs on GitHub Actions (which is yields the same results as my local env): https://github.com/privatenumber/minification-benchmarks/runs/4654820187?check_suite_focus=true According to docs, it has:
Ubuntu 20.04.3 LTS: And what do you mean by the power cable that makes it faster? (BTW, happy to take this to convo to a different Issue since this is a different topic) |
Well, the power cable on my laptop enables higher performance ;-) Could you please try using the |
Seems using $ hyperfine 'node_modules/.bin/minify -o=antd.tdewolff.js node_modules/antd/dist/antd.js' 'node_modules/.bin/esbuild node_modules/antd/dist/antd.js --outfile=antd.esbuild.js'
Benchmark 1: node_modules/.bin/minify -o=antd.tdewolff.js node_modules/antd/dist/antd.js
Time (mean ± σ): 188.3 ms ± 13.0 ms [User: 211.6 ms, System: 32.2 ms]
Range (min … max): 168.5 ms … 215.6 ms 15 runs
Benchmark 2: node_modules/.bin/esbuild node_modules/antd/dist/antd.js --outfile=antd.esbuild.js
Time (mean ± σ): 393.0 ms ± 17.8 ms [User: 326.7 ms, System: 65.7 ms]
Range (min … max): 369.1 ms … 422.0 ms 10 runs
Summary
'node_modules/.bin/minify -o=antd.tdewolff.js node_modules/antd/dist/antd.js' ran
2.09 ± 0.17 times faster than 'node_modules/.bin/esbuild node_modules/antd/dist/antd.js --outfile=antd.esbuild.js' Curious if your environment didn't yield this performance difference between using pipe Wonder if there's anything that can be improved in the way tdewolff/minify pipes output because any JS API that wraps tdewolff/minify could be limited by this. I will look into updating the minification benchmarks with this later but I might have to include the file-system read into the tdewolff/minify benchmark because this limitation will exist for any user that wants to use tdewolff/minify at peak performance. I'm not sure if this will be a fair performance comparison because the speed of file-system read can vary and is not reflective purely of the minfier's performance while other minifiers provide a JS API that directly hands over the output via stdout. |
Yes, surely I need to buffer the writing to stdout, perhaps this is automatic on my system but not on yours. Really unsure, I'm using |
I'm using zsh. My env details provided in comment above:
I ran benchmarks using
Also double checked without In any case, I'm getting the same performance via GitHub Actions using Haven't tested but I think this workflow will suffice: name: Benchmark tdewolff/minify
on:
push:
branches: [master]
workflow_dispatch:
jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- name: Benchmark tdewolff/minify
run: |
npm i antd
npx bina tdewolff/minify
time minify node_modules/antd/dist/antd.js > antd.tdewolff.js |
Thanks, I've added the benchmark and added buffering for stdout. Should behave fine now in v2.9.26 |
Thanks! I just updated https://github.com/privatenumber/minification-benchmarks and Shared the news here: https://twitter.com/privatenumbr/status/1476378923231547399 |
Great news! I have some other optimizations in mind that I'd like to look at. Meanwhile, I also want to see how to improve the gzip compression as it seems subpar compared to others. Thanks for the great work! |
Oh, I tried rerunning the tests on latest ubuntu |
Yes, we'd need to add support for various architectures and node versions and glibc, but isn't that getting a bit too many combinations? You think building from source could work on the remaining architectures? GitHub actions sounds nice, can it compile on different architectures? Or can we force node-gyp to compile for another architecture? Two ideas:
|
|
Nice, it seems like we should be using the Node API instead of the V8 engine, I will work on moving the code towards that. Meanwhile, that document mentions using https://github.com/prebuild/prebuildify for building the various binaries for each architecture and upload them together with the NPM package (instead of uploading them in another location such as GitHub). This has my preference to avoid cluttering up the releases here on GitHub. Anyways, what is the |
Well, it contains a bunch of macros and helped in getting the current bindings work inside worker threads, but I guess it won't be needed anymore if we move to |
Just tried the new implementation on the rollup integration and all tests passed like a charm! |
Thanks for the feedback @perrin4869 ! Great to hear it works, does that mean you can build the bindings from source and it works for all Node versions? I suppose we still need to prebuild binaries (with https://github.com/prebuild/prebuildify) so that anyone can install it without needing Go being installed. Am I right? |
I tried building it locally, then copying it into |
The most peculiar thing that I didn't look into yet is that when closing a worker thread that has the |
I've added integration with Now we need to incorporate the various platform we'd like to support. I've been looking at https://github.com/mafintosh/prebuildify-ci but I've also noticed that https://github.com/prebuild/prebuildify#options shows we can change the target architecture and platform. Perhaps we could just iterate over the architectures and for each build using Go and then build the prebuild? |
Just tested! |
Thanks, I've fixed the build-from-source, which should be invoked when the prebuild binary isn't available. In your case, for macos this should trigger a local build (make sure |
It seems that the platform/arch options for I suggest we only support the darwin/win32/linux platforms and the x64 architecture (three binary builds). |
Hey, I've been trying to get Edit: My coworker tried in his mac and got the same error... |
It seems to be working for Linux and MacOS, and additionally I've build a bash script locally that downloads the artifacts generated by the workflow, so that I can package it and ship a new version for NodeJS. As this involves a manual step, happy to hear alternatives. Secondly, I can't for the life get it to work on the Windows platform. I've fixed the initial issue, it compiles fine currently. But somehow it gives me weird error (see https://github.com/tdewolff/minify/runs/7119721849?check_suite_focus=true#step:4:85). The error suggests we've used a different node/npm version for building and for testing, but I don't see how...any help appreciated! |
I've been experiencing a lot of segfaults running @tdewolff/minify in worker threads (for example, https://github.com/dotcore64/rollup-plugin-tdewolff-minify/runs/7076883743?check_suite_focus=true)
I think the root cause of the segfaults may be that Line 21 in 6e2dbbd
Using the node-api, i think the way to fix it might be to call the go GC on https://nodejs.org/dist/latest/docs/api/n-api.html#napi_add_env_cleanup_hook after setting the minifier to nil, but i haven't had the chance to test this yet 😅 |
Thanks, I've instead opted for a way to keep the minifier allocated on the (global) stack, hopefully that resolves the issue. If it still doesn't, I suppose we need to call the GC explicitly. |
Trying to install the latest version I get this error on Mac OS M1 npm i @tdewolff/minify --save-dev npm ERR! A complete log of this run can be found in: |
I've updated the release on NPM manually, but this will happen automatically in the future using GitHub workflows (including prebuild binaries). |
The NodeJS workflow is now running for all three targets, see https://github.com/tdewolff/minify/actions/runs/2626161819 I will try and figure out the sigfault now. |
@tdewolff Thank you so much for this! I think it's closed now :) |
Ah no, the It is exiting after the first test... |
Hm, this must be a problem between Go and Node and I'm not sure how to fix this. It's at least not a memory issue with this library, but something to do with the garbage collector or a race condition. I've tried running Go on a locked OS thread, by calling GC on clean up etc but nothing works. The segfault also happens when no use code is executed in Go (i.e. empty function). I'm pretty sure we'd need to take this up to Go or node-gyp/NAPI. Meanwhile, some other native addons actually have the same problem I've noticed (the node-ffi package notably). Anyways, v.2.12.0 has been released with the JS binaries included, so this issue can be considered resolved. |
@privatenumber This might also speed up the minifier in your benchmarks, you think you could give it a try? Package is available from https://www.npmjs.com/package/@tdewolff/minify |
I'm creating a web-server in Node.js and at first I used a minifier written in JavaScript, which as you can guess had a poor performance. Looking for something better I found this and esbuild, esbuild was easy to integrate into my project since it has an NPM package exposing a module which I can import into Node.js which contains an API to use it with Node.js.
All I have to do is to
npm i esbuild
and then for example:I would rather use your minifier though, so it would be nice it that could be made possible in the future.
The text was updated successfully, but these errors were encountered: