Skip to content

flashkot/jsPackCompress

Repository files navigation

jsPackCompress

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.

⚠️ NOT FOR REGULAR WEB!

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.

Install

From NPM for use as a command line app:

npm install jspackcompress -g

From NPM for programmatic use:

npm install jspackcompress

Command line usage

Usage: 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:

--arg-name

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.

--getter

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");

--small-decoder

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.

--compact-html

HTML of packed file looks like this (newlines and indentation added for readability):

About

packing JavaScript files into self-extracting compressed HTML

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published