What is the state of the art minification/compression?

What are you all using to compress the generated JavaScript?

I am currently generating my final code using this combination:

psc-package build
purs bundle 'output/**/*.js' -m Main --main Main -o app.js

The output size is very large: 471K.

Gzip can bring this down for bandwidth motivations, but the uncompressed size also places a burden on the client (especially low end mobile phones), and therefore the non-gzipped size of the JS is a concern.

In the past on Fay and GHCJS, I would throw JS output like this at Closure Compiler or UglifyJS, but you have to prepare JS output to cater well to Closure Compiler, and UglifyJS brings it to about 202K.

So, what are y’all doing to bring down those numbers? (Besides gzip and pray.)

I’m looking at zephyr, webpack and rollup. What are your experiences with these? webpack and rollup have wide use, so perhaps they can be considered the modern versions of Closure Compiler and UglifyJS. But I’m concerned about the realiability of something like zephyr which has a small community of users; how do I know it won’t break my code in subtle ways? Experiences?

Coming from a Haskell background I prefer to use Haskell binaries that I can pindown and shy away from installing node; else if a node app is the only option I’d definitely be using docker as a hazmat suit to protect against the moving parts ecosystem of nodejs.

Interested to hear any opinions and experiences.

2 Likes

I can’t give you much personal experience but I can point you to the Spago readme (https://github.com/purescript/spago#make-a-project-with-purescript--javascript). It covers packaging with both Parcel and Webpack (and less relevantly, Nodemon) so maybe check those out.

Great question. I’ve been wondering what we can do to get closer to Elm’s bundle sizes.

Edit: Looks pretty good now. Down to only a 2x gap with Halogen, parcel2, and zephyr.

Language / Framework Optimized (KB) Gzipped (KB)
elm 28 10
purescript react-basic-hooks 134 44
purescript halogen v5 113 24

repo

Edit2: Sizes with just spago bundle-app and parcel2 are not much different. +1 KB for react, + 2 KB for halogen.

Framework spago bundle-app parcel(2) build gzip
purescript react-basic-hooks 26 (PS only) 139 45
purescript halogen v5 316 124 26

Interesting that compressed output with Parcel1 is slightly better for react and slightly worse for halogen. But this difference does not seem significant enough to justify swapping over to Parcel2:

Framework spago bundle-app parcel(1) build gzip
purescript react-basic-hooks 26 (PS only) 140 44
purescript halogen v5 316 126 27

Original post:


Here’s what I’ve observed for equivalent apps:

Language / Framework Optimized (KB) Gzipped (KB)
elm 28 10
purescript react-basic-hooks 348 81
purescript halogen v5 680 127

Here’s the repo with the three apps.

@kevinbarabash and @chrisdone proposed some further minimization techniques in this issue.

Trying those out is on my todo list, but anyone else should feel free to give it a shot on the above example and see if we can get some better numbers.

2 Likes

I think the real optimizations will occur when type class constraints are optimized within the compiler. There’s an issue on the compiler repo that shows the before and after we would want to achieve, but I can’t recall what it is off the top of my head.

5 Likes

@kl0tl was able to achieve small bundles with rollup and ES modules.
Looks like this feature might make it in 0.15.

Is this the size of purs bundle output? If yes you should minify the bundle with UglifyJS, Terser or the Closure Compiler. You should get the best possible results by bundling with rollup and its PureScript and Terser plugin though (after optimizing the compiler output with zephyr).

ES modules are nice to shave even more bytes but zephyr really does most of the work, see https://github.com/purescript/purescript/pull/3791#issuecomment-615883448.

I recently tried bundling with zephyr and parcel a project which used spago and parcel only and the bundle size went from 600kb to 650kb so idk what to say about that

I’d be curious to see an example of that.

I was able to achieve smaller bundles with parcel2 and zephyr and updated my original post with the latest results.

I haven’t tested this bundling strategy with the ES modules change, but I’m mostly looking for workflows that we can recommend to users who aren’t up for rebuilding purs from source. I’ll revisit this at 0.15 to see if the numbers improve.

1 Like

I’ve noticed that the code generate for showing records inlines a big chunk of JavaScript for each call to show, e.g.

log $ show results

is turned into this:

return Control_Bind.discard(Control_Bind.discardUnit)(Effect_Aff.bindAff)(Effect_Class_Console.log(Effect_Aff.monadEffectAff)(Data_Show.show(Data_Show.showArray(Data_Show.showArray(Data_Show.showRecord()(Data_Show.showRecordFieldsCons(new Data_Symbol.IsSymbol(function () {
    return "functionName";
}))(Data_Show.showRecordFieldsCons(new Data_Symbol.IsSymbol(function () {
    return "isBlockCoverage";
}))(Data_Show.showRecordFieldsCons(new Data_Symbol.IsSymbol(function () {
    return "ranges";
}))(Data_Show.showRecordFieldsNil)(Data_Show.showArray(Data_Show.showRecord()(Data_Show.showRecordFieldsCons(new Data_Symbol.IsSymbol(function () {
    return "count";
}))(Data_Show.showRecordFieldsCons(new Data_Symbol.IsSymbol(function () {
    return "endOffset";
}))(Data_Show.showRecordFieldsCons(new Data_Symbol.IsSymbol(function () {
    return "startOffset";
}))(Data_Show.showRecordFieldsNil)(Data_Show.showInt))(Data_Show.showInt))(Data_Show.showInt)))))(Data_Show.showBoolean))(Data_Show.showString)))))(results)))

and then repeated for each copy log $ show results in the source file.

Are any of the minification methods mentioned in this thread able to extract code that’s duplicated like this into a helper function? If not, is this something that would make sense to add to the compiler’s code generation?

The compiler could definitely be smarter about specialisation and type class code generation in general, yes. That’s something we’ve been wanting to do (or at least vaguely thinking about doing) for quite a while.

1 Like