├── src ├── __tests__ │ ├── __fixtures__ │ │ └── code.jpg │ └── wasm-zopfli.spec.js ├── zopfli │ ├── Cargo.toml │ ├── .gitignore │ └── src │ │ └── main.rs └── index.js ├── benchmark ├── package.json ├── rollup.config.js ├── src │ └── index.js └── README.md ├── .gitignore ├── package.json ├── rollup.config.js └── README.md /src/__tests__/__fixtures__/code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfrankland/wasm-zopfli/HEAD/src/__tests__/__fixtures__/code.jpg -------------------------------------------------------------------------------- /src/zopfli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm-zopfli" 3 | version = "0.0.0" 4 | 5 | [dependencies] 6 | zopfli = "0.3.6" 7 | -------------------------------------------------------------------------------- /src/zopfli/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "wasm-zopfli-benchmark", 4 | "version": "0.0.0", 5 | "scripts": { 6 | "build": "rollup -c", 7 | "prestart": "npm run build", 8 | "start": "node ./dist/index.js", 9 | "test": "npm start", 10 | "benchmark": "npm start" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.0.0-beta.35", 14 | "@babel/preset-env": "^7.0.0-beta.35", 15 | "@babel/preset-stage-0": "^7.0.0-beta.35", 16 | "rollup": "^0.52.2", 17 | "rollup-plugin-babel": "^4.0.0-beta.0" 18 | }, 19 | "dependencies": { 20 | "@gfx/zopfli": "^1.0.5", 21 | "benchmark": "^2.1.4", 22 | "node-zopfli": "^2.0.2", 23 | "wasm-zopfli": ".." 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /benchmark/rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import { dependencies } from './package.json'; 3 | 4 | export default { 5 | input: './src/index.js', 6 | output: { 7 | file: './dist/index.js', 8 | format: 'cjs', 9 | sourcemap: true, 10 | }, 11 | plugins: [ 12 | babel({ 13 | include: ['**/*.js'], 14 | babelrc: false, 15 | presets: [ 16 | [ 17 | '@babel/preset-env', 18 | { 19 | modules: false, 20 | targets: { 21 | node: 'current', 22 | }, 23 | }, 24 | ], 25 | '@babel/preset-stage-0', 26 | ], 27 | }), 28 | ], 29 | external: [ 30 | ...Object.keys(dependencies), 31 | 'util', 32 | 'crypto', 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # Rollup 61 | dist 62 | -------------------------------------------------------------------------------- /src/zopfli/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate zopfli; 2 | 3 | use std::mem; 4 | use std::os::raw::c_void; 5 | 6 | use zopfli::{Format, Options, compress}; 7 | use zopfli::Format::{Deflate, Gzip, Zlib}; 8 | 9 | // This is just so the file compiles 10 | 11 | fn main() {} 12 | 13 | // Memory management utility for `kaffeerost` to hook into 14 | 15 | #[no_mangle] 16 | pub extern "C" fn alloc(size: usize) -> *mut c_void { 17 | let mut buf = Vec::with_capacity(size); 18 | let ptr = buf.as_mut_ptr(); 19 | mem::forget(buf); // This is JS' responsibility now 20 | return ptr as *mut c_void; 21 | } 22 | 23 | // Actual module code 24 | 25 | fn zopfli(format: &Format, data: &[u8]) -> Vec { 26 | let mut out = vec![]; 27 | 28 | compress( 29 | &Options::default(), 30 | &format, 31 | &data, 32 | &mut out, 33 | ).unwrap(); 34 | 35 | out 36 | } 37 | 38 | #[no_mangle] 39 | pub extern "C" fn deflate(data: &[u8]) -> Vec { 40 | zopfli(&Deflate, &data) 41 | } 42 | 43 | #[no_mangle] 44 | pub extern "C" fn gzip(data: &[u8]) -> Vec { 45 | zopfli(&Gzip, &data) 46 | } 47 | 48 | #[no_mangle] 49 | pub extern "C" fn zlib(data: &[u8]) -> Vec { 50 | zopfli(&Zlib, &data) 51 | } 52 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* global WebAssembly */ 2 | 3 | import { wrap } from 'kaffeerost'; 4 | import zopfliWasmModule from './zopfli/Cargo.toml'; 5 | 6 | export const FORMAT_DEFLATE = {}; 7 | export const FORMAT_GZIP = {}; 8 | export const FORMAT_ZLIB = {}; 9 | 10 | const formats = new Map([ 11 | [FORMAT_DEFLATE, 'deflate'], 12 | [FORMAT_GZIP, 'gzip'], 13 | [FORMAT_ZLIB, 'zlib'], 14 | ]); 15 | 16 | export const zopfli = async (format, data) => { 17 | if (!formats.has(format)) throw new Error('format is invalid'); 18 | if (!(data instanceof Uint8Array)) throw new Error('data must be a Uint8Array'); 19 | 20 | const { 21 | instance: { 22 | exports, 23 | } = {}, 24 | } = await zopfliWasmModule({ 25 | env: { 26 | memory: new WebAssembly.Memory({ initial: 4096, limit: 4096 }), 27 | log: x => Math.log(x), 28 | }, 29 | }); 30 | 31 | const zopfliFormat = wrap( 32 | exports, 33 | formats.get(format), 34 | ['&[u8]'], 35 | 'Vec', 36 | ); 37 | 38 | return zopfliFormat(data); 39 | }; 40 | 41 | export const deflate = (...args) => zopfli(FORMAT_DEFLATE, ...args); 42 | export const gzip = (...args) => zopfli(FORMAT_GZIP, ...args); 43 | export const zlib = (...args) => zopfli(FORMAT_ZLIB, ...args); 44 | -------------------------------------------------------------------------------- /benchmark/src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { randomBytes } from 'crypto'; 4 | import { Suite } from 'benchmark'; 5 | import { promisify } from 'util'; 6 | 7 | import { gzip as nativeGzip } from 'node-zopfli'; 8 | import { gzip as cppGzip } from '@gfx/zopfli'; 9 | import { gzip as rustGzip } from 'wasm-zopfli'; 10 | 11 | const implementations = [ 12 | { name: 'node-zopfli', type: 'native', method: promisify(nativeGzip) }, 13 | { name: '@gfx/zopfli', type: 'cpp wasm', method: promisify(cppGzip) }, 14 | { name: 'wasm-zopfli', type: 'rust wasm', method: rustGzip }, 15 | ]; 16 | 17 | (async () => { 18 | // warmup 19 | await Promise.all(( 20 | implementations.map(( 21 | ({ method }) => method(randomBytes(1024 * 1024), {}) 22 | )) 23 | )); 24 | 25 | const benchmarks = [1, 1024, 1024 * 1014].map(( 26 | size => () => new Promise(async (resolve) => { 27 | console.log(`\n## payload size: ${size}`); 28 | 29 | const bytes = randomBytes(size); 30 | 31 | const fullSuite = implementations.reduce( 32 | (suite, { name, type, method }) => ( 33 | suite.add(`${name} (${type})`, { 34 | defer: true, 35 | fn: async (deffered) => { 36 | await method(bytes, {}); 37 | deffered.resolve(); 38 | }, 39 | }) 40 | ), 41 | new Suite(), 42 | ); 43 | 44 | fullSuite 45 | .on('cycle', (event) => { 46 | console.log(`${event.target}`); 47 | }) 48 | .on('complete', () => { 49 | console.log(`Fastest is ${fullSuite.filter('fastest').map('name')}`); 50 | resolve(); 51 | }) 52 | .run({ async: true }); 53 | }) 54 | )); 55 | 56 | await benchmarks.reduce( 57 | async (promiseChain, benchmark) => { 58 | await promiseChain; 59 | await benchmark(); 60 | }, 61 | Promise.resolve(), 62 | ); 63 | })(); 64 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # wasm-zopfli-benchmark 2 | 3 | A simple benchmark that runs a warmup using random bytes, then generates random 4 | bytes for a series of modules to compress using gzip zopfli. 5 | 6 | The current modules that this tests are: 7 | 8 | * [`node-zopfli`][node-zopfli]: A Node.js native addon zopfli library. 9 | 10 | * [`universal-zopfli-js`][universal-zopfli-js] (`@gfx/zopfli` on npm): A C++ 11 | WebAssembly Emscripten-compiled zopfli library. 12 | 13 | * [`wasm-zopfli`][wasm-zopfli]: A Rust WebAssembly `rust-unknown-unknown`- 14 | compiled zopfli library. 15 | 16 | [node-zopfli]: https://github.com/pierreinglebert/node-zopfli 17 | [universal-zopfli-js]: https://github.com/gfx/universal-zopfli-js 18 | [wasm-zopfli]: https://github.com/dfrankland/wasm-zopfli 19 | 20 | ## To Run 21 | 22 | 1. Build `wasm-zopfli` if you haven't already done so— 23 | [see instructions here][build]. 24 | 25 | [build]: https://github.com/dfrankland/wasm-zopfli#development 26 | 27 | 2. Install all dependencies. 28 | 29 | ```bash 30 | npm install 31 | ``` 32 | 33 | 3. Build and run the benchmark. 34 | 35 | ```bash 36 | npm run benchmark 37 | ``` 38 | 39 | 4. Wait a while... The tests might run quite slow. 40 | 41 | ## Results 42 | 43 | These results are run on a MacBook Pro (Retina, 15-inch, Mid 2015) with a 44 | 2.2 GHz Intel Core i7 processor and 16 GB 1600 MHz DDR3 memory, running macOS 45 | High Sierra version 10.13.2: 46 | 47 | ``` 48 | ## payload size: 1 49 | node-zopfli (native) x 177 ops/sec ±2.62% (81 runs sampled) 50 | @gfx/zopfli (cpp wasm) x 102 ops/sec ±0.92% (78 runs sampled) 51 | wasm-zopfli (rust wasm) x 8.82 ops/sec ±1.83% (44 runs sampled) 52 | Fastest is node-zopfli (native) 53 | 54 | ## payload size: 1024 55 | node-zopfli (native) x 4.59 ops/sec ±0.61% (27 runs sampled) 56 | @gfx/zopfli (cpp wasm) x 1.67 ops/sec ±1.45% (13 runs sampled) 57 | wasm-zopfli (rust wasm) x 0.74 ops/sec ±0.95% (8 runs sampled) 58 | Fastest is node-zopfli (native) 59 | 60 | ## payload size: 1038336 61 | node-zopfli (native) x 0.34 ops/sec ±3.73% (6 runs sampled) 62 | @gfx/zopfli (cpp wasm) x 0.27 ops/sec ±3.18% (6 runs sampled) 63 | wasm-zopfli (rust wasm) x 0.03 ops/sec ±2.60% (5 runs sampled) 64 | Fastest is node-zopfli (native) 65 | ``` 66 | -------------------------------------------------------------------------------- /src/__tests__/wasm-zopfli.spec.js: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | import { 3 | deflateRaw as normalDeflateRaw, 4 | gzip as normalGzip, 5 | deflate as normalDeflate, 6 | inflateRaw, 7 | gunzip, 8 | inflate, 9 | } from 'zlib'; 10 | import { readFileSync } from 'fs'; 11 | import { resolve as resolvePath } from 'path'; 12 | import { 13 | deflate, 14 | gzip, 15 | zlib, 16 | zopfli, 17 | } from '../../'; 18 | 19 | jest.setTimeout(60000); 20 | 21 | const input = readFileSync(resolvePath(__dirname, './__fixtures__/code.jpg')); 22 | 23 | describe('zopfli', () => { 24 | it('can deflate (deflate raw)', async () => { 25 | const [ 26 | zopfliCompressed, 27 | normalCompressed, 28 | ] = await Promise.all([ 29 | promisify(normalDeflateRaw)(input), 30 | deflate(input), 31 | ]); 32 | 33 | expect(zopfliCompressed.length).toBeLessThan(normalCompressed.length); 34 | 35 | const decompressed = await promisify(inflateRaw)(zopfliCompressed); 36 | 37 | expect(decompressed).toEqual(input); 38 | }); 39 | 40 | it('can gzip', async () => { 41 | const [ 42 | zopfliCompressed, 43 | normalCompressed, 44 | ] = await Promise.all([ 45 | promisify(normalGzip)(input), 46 | gzip(input), 47 | ]); 48 | 49 | expect(zopfliCompressed.length).toBeLessThan(normalCompressed.length); 50 | 51 | const decompressed = await promisify(gunzip)(zopfliCompressed); 52 | 53 | expect(decompressed).toEqual(input); 54 | }); 55 | 56 | it('can zlib (deflate)', async () => { 57 | const [ 58 | zopfliCompressed, 59 | normalCompressed, 60 | ] = await Promise.all([ 61 | promisify(normalDeflate)(input), 62 | zlib(input), 63 | ]); 64 | 65 | expect(zopfliCompressed.length).toBeLessThan(normalCompressed.length); 66 | 67 | const decompressed = await promisify(inflate)(zopfliCompressed); 68 | 69 | expect(decompressed).toEqual(input); 70 | }); 71 | 72 | it('checks for `Uint8Array` input', async () => { 73 | try { 74 | await zlib('blah'); 75 | } catch (err) { 76 | return expect(( 77 | () => { 78 | throw err; 79 | } 80 | )).toThrow('data must be a Uint8Array'); 81 | } 82 | 83 | throw new Error('test failed'); 84 | }); 85 | 86 | it('checks for proper format given to `zopfli` function', async () => { 87 | await expect(zopfli('bad format', 'not a Unit8Array')).rejects.toThrow('format is invalid'); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-zopfli", 3 | "version": "1.0.2", 4 | "description": "🗜 WebAssembly compiled Zopfli library", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/dfrankland/wasm-zopfli.git" 8 | }, 9 | "keywords": [ 10 | "wasm", 11 | "zopfli", 12 | "gzip", 13 | "deflate", 14 | "zlib", 15 | "rust", 16 | "compression" 17 | ], 18 | "author": "Dylan Frankland", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/dfrankland/wasm-zopfli/issues" 22 | }, 23 | "homepage": "https://github.com/dfrankland/wasm-zopfli#readme", 24 | "main": "./dist/index.js", 25 | "module": "./dist/index.mjs", 26 | "browser": "./dist/browser.js", 27 | "files": [ 28 | "dist/index.js", 29 | "dist/index.mjs", 30 | "dist/browser.js" 31 | ], 32 | "scripts": { 33 | "build": "rollup -c", 34 | "pretest": "npm run build", 35 | "test": "jest", 36 | "lint": "eslint -c ./package.json ./*.js ./src/**/*.js ./benchmark/*.js ./benchmark/src/**/*.js" 37 | }, 38 | "devDependencies": { 39 | "@babel/core": "^7.0.0-beta.35", 40 | "@babel/plugin-transform-runtime": "^7.0.0-beta.35", 41 | "@babel/preset-env": "^7.0.0-beta.35", 42 | "@babel/preset-stage-0": "^7.0.0-beta.35", 43 | "babel-eslint": "^8.0.3", 44 | "babel-jest": "^21.2.0", 45 | "babel-preset-env": "^1.6.1", 46 | "babel-preset-stage-0": "^6.24.1", 47 | "eslint": "^4.13.1", 48 | "eslint-config-airbnb": "^16.1.0", 49 | "eslint-plugin-import": "^2.8.0", 50 | "eslint-plugin-jest": "^21.4.2", 51 | "eslint-plugin-jsx-a11y": "^6.0.3", 52 | "eslint-plugin-react": "^7.5.1", 53 | "jest": "^21.3.0-beta.15", 54 | "rollup": "^0.52.1", 55 | "rollup-plugin-babel": "^4.0.0-beta.0", 56 | "rollup-plugin-commonjs": "^8.2.3", 57 | "rollup-plugin-docker": "^0.1.0", 58 | "rollup-plugin-node-resolve": "^3.0.0", 59 | "rollup-plugin-replace": "^2.0.0", 60 | "rollup-plugin-wasm-module": "0.0.3" 61 | }, 62 | "dependencies": { 63 | "@babel/runtime": "^7.0.0-beta.35", 64 | "kaffeerost": "0.0.1" 65 | }, 66 | "babel": { 67 | "only": [ 68 | "./src/**/*.js" 69 | ], 70 | "presets": [ 71 | [ 72 | "env", 73 | { 74 | "targets": { 75 | "node": "current" 76 | } 77 | } 78 | ], 79 | "stage-0" 80 | ] 81 | }, 82 | "eslintConfig": { 83 | "parser": "babel-eslint", 84 | "extends": "airbnb", 85 | "env": { 86 | "browser": true, 87 | "jest/globals": true 88 | }, 89 | "rules": { 90 | "import/no-extraneous-dependencies": [ 91 | "error", 92 | { 93 | "devDependencies": [ 94 | "**/rollup.config.js", 95 | "**/__tests__/**/*" 96 | ] 97 | } 98 | ] 99 | }, 100 | "plugins": [ 101 | "jest" 102 | ] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from 'rollup-plugin-node-resolve'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import babel from 'rollup-plugin-babel'; 4 | import docker from 'rollup-plugin-docker'; 5 | import wasmModule from 'rollup-plugin-wasm-module'; 6 | import replace from 'rollup-plugin-replace'; 7 | import { resolve as resolvePath, dirname } from 'path'; 8 | import { dependencies } from './package.json'; 9 | 10 | const HOST = '/host'; 11 | const DIST = '/dist'; 12 | 13 | const baseConfig = ({ 14 | output, 15 | targets, 16 | external, 17 | replacers, 18 | runtimeHelpers, 19 | }) => ({ 20 | input: './src/index.js', 21 | output, 22 | plugins: [ 23 | nodeResolve(), 24 | commonjs({ 25 | namedExports: { 26 | 'node_modules/kaffeerost/index.js': ['wrap'], 27 | }, 28 | }), 29 | ...(replacers ? [null] : []).map(() => ( 30 | replace(replacers) 31 | )), 32 | ...(targets ? [null] : []).map(() => ( 33 | babel({ 34 | include: ['./src/**/*.js'], 35 | babelrc: false, 36 | runtimeHelpers, 37 | presets: [ 38 | [ 39 | '@babel/preset-env', 40 | { 41 | modules: false, 42 | targets, 43 | }, 44 | ], 45 | '@babel/preset-stage-0', 46 | ], 47 | plugins: (runtimeHelpers ? [null] : []).map(( 48 | () => '@babel/plugin-transform-runtime' 49 | )), 50 | }) 51 | )), 52 | docker({ 53 | include: ['**/Cargo.toml'], 54 | options: { 55 | image: 'rustlang/rust:nightly', 56 | createOptions: { 57 | Binds: [`/:${HOST}`], 58 | }, 59 | command: path => [ 60 | 'sh', 61 | '-c', 62 | ` 63 | rustup target add wasm32-unknown-unknown \ 64 | && \ 65 | mkdir ${DIST} \ 66 | && \ 67 | cd ${resolvePath(HOST, `.${dirname(path)}`)} \ 68 | && \ 69 | CARGO_TARGET_DIR=${DIST} \ 70 | cargo build \ 71 | --release \ 72 | --target wasm32-unknown-unknown 73 | `, 74 | ], 75 | paths: { 76 | main: resolvePath(DIST, './wasm32-unknown-unknown/release/wasm-zopfli.wasm'), 77 | }, 78 | }, 79 | }), 80 | wasmModule({ 81 | include: ['**/Cargo.toml'], 82 | }), 83 | ], 84 | external, 85 | }); 86 | 87 | export default [ 88 | baseConfig({ 89 | output: { 90 | file: './dist/index.mjs', 91 | format: 'es', 92 | }, 93 | external: Object.keys(dependencies), 94 | }), 95 | baseConfig({ 96 | output: { 97 | file: './dist/index.js', 98 | format: 'cjs', 99 | }, 100 | targets: { 101 | node: '8', 102 | }, 103 | external: Object.keys(dependencies), 104 | }), 105 | baseConfig({ 106 | output: { 107 | file: './dist/browser.js', 108 | format: 'umd', 109 | name: 'wasmZopfli', 110 | globals: { 111 | 'text-encoding': 'window', 112 | }, 113 | }, 114 | // https://caniuse.com/#feat=wasm 115 | targets: { 116 | edge: '16', 117 | firefox: '52', 118 | chrome: '57', 119 | safari: '11', 120 | ios: '11.2', 121 | android: '62', 122 | }, 123 | external: ['text-encoding'], 124 | replacers: { 125 | 'process.env': '(typeof process === \'object\' && typeof process.env === \'object\' ? process.env : {})', 126 | }, 127 | runtimeHelpers: true, 128 | }), 129 | ].filter(( 130 | process.env.TARGET ? ( 131 | ({ output: { format } }) => process.env.TARGET === format 132 | ) : ( 133 | () => true 134 | ) 135 | )); 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wasm-zopfli 2 | 3 | WebAssembly compiled Zopfli library. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | npm install -S wasm-zopfli 9 | ``` 10 | 11 | > The awesome thing about `wasm-zopfli` is that it does not need to compile or 12 | > download any prebuilt binaries! 13 | 14 | ## Usage 15 | 16 | Because WebAssembly is supported on both Node.js and several browsers, 17 | `wasm-zopfli` is super easy to use. 18 | 19 | ### Node.js 20 | 21 | An example of compressing something and saving it to a file via Node.js. 22 | 23 | ```js 24 | import { gzip } from 'wasm-zopfli'; 25 | import { writeFile } from 'fs'; 26 | import { promisify } from 'util'; 27 | 28 | const writeFileAsync = promisify(writeFile); 29 | 30 | const content = Buffer.from('Hello, world!', 'utf8'); 31 | 32 | (async () => { 33 | try { 34 | const compressedContent = await gzip(content); 35 | await writeFileAsync('./hello_world.txt.gz', compressedContent); 36 | } catch (err) { 37 | console.error(err); 38 | } 39 | })(); 40 | ``` 41 | 42 | ### Browser 43 | 44 | An example of compressing something and downloading it from the browser. 45 | 46 | ```js 47 | import { gzip } from 'wasm-zopfli'; 48 | 49 | const content = new TextEncoder('utf-8').encode('Hello, world!'); 50 | 51 | (async () => { 52 | try { 53 | const compressedContent = await gzip(content); 54 | 55 | const file = new File([compressedContent], 'hello_world.txt.gz', { type: 'application/gzip' }); 56 | 57 | const link = document.createElement('a'); 58 | link.setAttribute('href', URL.createObjectURL(file)); 59 | link.setAttribute('download', file.name); 60 | link.click(); 61 | } catch (err) { 62 | console.error(err); 63 | } 64 | })(); 65 | ``` 66 | 67 | ## Documentation 68 | 69 | ### deflate(data) 70 | 71 | * `data` [``][mdn uint8array] 72 | 73 | Compress `data` using deflate. This is is referred to as "deflate raw" by 74 | Node.js' documentation. 75 | 76 | ### gzip(data) 77 | 78 | * `data` [``][mdn uint8array] 79 | 80 | Compress `data` using gzip. 81 | 82 | ### zlib(data) 83 | 84 | * `data` [``][mdn uint8array] 85 | 86 | Compress `data` using zlib. This is is referred to as "deflate" by Node.js' 87 | documentation. 88 | 89 | ### zopfli(format, data) 90 | 91 | * `format` `` | `` | `` 92 | * `data` [``][mdn uint8array] 93 | 94 | The function that `deflate`, `gzip`, and `zlib` wrap. Pass any of the constants 95 | below and data to compress. 96 | 97 | ### FORMAT_DEFLATE 98 | 99 | Constant, reference, for compressing data with `zopfli` using deflate. 100 | 101 | ### FORMAT_GZIP 102 | 103 | Constant, reference, for compressing data with `zopfli` using gzip. 104 | 105 | ### FORMAT_ZLIB 106 | 107 | Constant, reference, for compressing data with `zopfli` using zlib. 108 | 109 | [mdn uint8array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array 110 | 111 | ## Benchmark 112 | 113 | Want to see how fast this is? [Go to the benchmark directory][benchmark] to see 114 | results, instructions on running your own benchmark, and more. 115 | 116 | [benchmark]: https://github.com/dfrankland/wasm-zopfli/tree/master/benchmark 117 | 118 | ## Development 119 | 120 | To build `wasm-zopfli` you will need to [install Docker][docker install], and 121 | pull [`rustlang/rust:nightly`][rust nightly]. After that all that is needed is 122 | to do the following: 123 | 124 | 1. Install all dependencies. 125 | 126 | ```bash 127 | npm install 128 | ``` 129 | 130 | 2. Build the module. 131 | 132 | ```bash 133 | npm run build 134 | ``` 135 | 136 | 3. Test the module. 137 | 138 | ```bash 139 | npm test 140 | ``` 141 | 142 | [docker install]: https://docs.docker.com/engine/installation/ 143 | [rust nightly]: https://hub.docker.com/r/rustlang/rust/ 144 | --------------------------------------------------------------------------------