Utility for packing JavaScript files into self-extracting compressed HTML. Also can add extra files (images, css, anything else) into package.
While there are already tools like this (for example: fetchcrunch or jsExe) they do not suit my needs because they share same flaw - compressed HTML file must be served from HTTP server or they will not work.
You can read more about my motivation and how this tool works in Why and how section.
While it may be tempting to use this packing for regular web sites (or PWAs), it is a bad idea to do so.
You can achieve much better result with properly configured compression on your HTTP server and by using service workers.
And even without that, delay introduced by unpacking is quite noticeable. Especially on low-end devices (like smartphones). Better let users download more and then see content instantly without delay and page freeze.
So this tool is a curiosity better suited for niche projects or stuff like demoscene or game jams like js13kgames.
Note
If you still want to publish packed file on the web, read about
--universal-decoder option.
From NPM for use as a command line app:
npm install jspackcompress -gFrom NPM for programmatic use:
npm install jspackcompressUsage: jspackcompress [options] [extra files...]
Package JavaScript file into self-extracting compressed HTML file
Options:
-i, --input <file> JavaScript file to use as the entry point
-o, --output <file> output filename for the compressed HTML. If not
specified, the input file name with the extension
changed from `.js` to `.html` will be used
-l, --level <number> compression level (2–9). A lower value is faster
but less compressed; a higher value is slower but
more compressed (default: 9)
-a, --arg-name <name> name of the wrapper function argument to access
extra files (default: "$fs")
-g, --getter <type> how content of extra files should be returned.
Options include: `ArrayBuffer`, `Uint8Array`,
`String`, `Blob`, or `BlobURL`. Use `all` to have
all types as properties of the wrapper argument
(default: "all")
-s, --small-decoder always use small decoder
-u, --universal-decoder use universal decoder
-c, --compact-html generate compact HTML
-e, --extra-head <string> additional string to include in the <HEAD> section
-b, --extra-body <string> additional string to include in the <BODY> section
-V, --version output the version number
-h, --help display help for command
You can use glob patterns to specify extra files:
$ jspackcompress -i index.js -o game.html assets/*
Options worth explaining:
If package consists of single .js file, after decompression its contents
executed via eval command.
But if package contains extra files, their content must be passed to main script somehow. This is achieved by executing main script via Function. It looks something like this:
new Function("$fs", scriptContent)(filesData);"$fs" is the default name for function argument. If it clashes with some
variable in your code, or if you want to save some extra bytes, you can change
name of this variable with --arg-name. Next section describes how to use this
argument to get files content.
Caution
No checks are made that value provided with --arg-name is valid
variable name in JavaScript.
Before packing all files are concatenated and compressed as single binary blob.
Uncompressed ArrayBuffer then passed to main script as $fs argument.
To make access easier a small bit of code prepended to main script that
transforms this $fs to an object with following methods and props:
// get file content as ArrayBuffer (slice)
$fs.ab("image.webp");
// get file content as Uint8Array
$fs.u8("image.webp");
// get file content as a String
$fs.str("data.json");
// get file content as a Blob
$fs.blob("data.csv");
// get file content as a Blob URL
$fs.url("data.csv");
// original ArrayBuffer with all files
$fs.b;
// object with information about each file
// NOTE: main script is not included into this object
$fs.list;
// and it looks liek this:
{
"1.webp": {
o: 0, // offest
s: 12345 // size of the file
},
"2.webp": {
o: 12345,
s: 54321
},
...
}This is default. But if you don't need all of this and want to save some bytes,
then you can use --getter to limit returned value to one type. Possible values
are ArrayBuffer, Uint8Array, String, Blob, and BlobURL. In this case
you use $fs directly as a function
// if packed with `--getter BlobURL` then Blolb URL for the file is returned
let url = $fs("1.webp");There are two decoders for the packed file. jsPackCompress chooses one depending on the size of the payload. If it is less than 100KiB then smaller decoder is used. If it is more than 100KiB then it uses bigger decoder.
The bigger one uses Look Up Table (needs to be filled first) for faster decoding. Which is especially noticeable when packing several megabytes. But it becomes slower on small payloads (especially when look up table is bigger than payload itself)
If you still need to save extra bytes (like 31 in this case) you can force smaller decoder with this option.
HTML of packed file looks like this (newlines and indentation added for readability):