Browse Source

Finishing demo

Arjun Barrett 4 years ago
parent
commit
0f119a721a

+ 10 - 5
src/demo/App.tsx

@@ -1,7 +1,9 @@
-import React, { FC } from 'react';
+import React, { FC, useState } from 'react';
 import FilePicker from './components/file-picker';
 import FilePicker from './components/file-picker';
+import CodeBox from './components/code-box';
 
 
 const App: FC = () => {
 const App: FC = () => {
+  const [files, setFiles] = useState<File[]>([])
   return (
   return (
     <>
     <>
       <div style={{
       <div style={{
@@ -48,11 +50,14 @@ const App: FC = () => {
           Despite utilizing multiple cores, supporting data streams, and being very memory efficient, fflate is compatible with both Node.js and browsers as old as IE11.
           Despite utilizing multiple cores, supporting data streams, and being very memory efficient, fflate is compatible with both Node.js and browsers as old as IE11.
           <br /><br />
           <br /><br />
           You can read more about fflate on <a href="//github.com/package/fflate">GitHub</a>. Try the demo below to see its performance for yourself.
           You can read more about fflate on <a href="//github.com/package/fflate">GitHub</a>. Try the demo below to see its performance for yourself.
+          <br />
+          <span style={{ fontSize: '0.5em' }}>I added a <i>lot</i> of sugar (around 4 hundred lines) to the UZIP and Pako APIs to make the demo clean and asynchronous, but the fflate API is unmodified.</span>
         </div>
         </div>
-        <FilePicker allowDirs onFiles={f => {
-          console.log(f);
-        }} onError={console.log} onDrag={() => {}} />
-      </div>
+        <div>
+          <FilePicker allowDirs onFiles={setFiles} onError={console.log} onDrag={() => {}} />
+          <CodeBox files={files} />
+        </div>
+       </div>
     </>
     </>
   );
   );
 }
 }

+ 2 - 2
src/demo/augment.d.ts

@@ -4,8 +4,8 @@ declare module 'uzip' {
     function inflateRaw(buf: Uint8Array, out?: Uint8Array): Uint8Array;
     function inflateRaw(buf: Uint8Array, out?: Uint8Array): Uint8Array;
     function deflate(buf: Uint8Array, opts?: { level: number }): Uint8Array;
     function deflate(buf: Uint8Array, opts?: { level: number }): Uint8Array;
     function inflate(buf: Uint8Array, out?: Uint8Array): Uint8Array;
     function inflate(buf: Uint8Array, out?: Uint8Array): Uint8Array;
-    function encode(files: Record<string, Uint8Array>, noCmpr?: boolean): Uint8Array;
-    function parse(buf: Uint8Array): Record<string, Uint8Array>;
+    function encode(files: Record<string, Uint8Array>, noCmpr?: boolean): ArrayBuffer;
+    function parse(buf: ArrayBuffer): Record<string, ArrayBuffer>;
   }
   }
   export = UZIP;
   export = UZIP;
 }
 }

+ 140 - 6
src/demo/components/code-box/index.tsx

@@ -1,14 +1,148 @@
-import React, { FC } from 'react';
-import streamify from './stream-adapter';
+import React, { FC, FormEvent, useState } from 'react';
+import exec from './sandbox';
+
+declare const Prism: {
+  highlightElement(el: Element): void;
+};
 
 
 const canStream = 'stream' in File.prototype;
 const canStream = 'stream' in File.prototype;
 
 
 type Preset = {
 type Preset = {
   fflate: string;
   fflate: string;
   uzip: string;
   uzip: string;
-  jszip: string;
+  pako: string;
 };
 };
 
 
-const presets: Record<string, string> = {
-  
-}
+const presets: Record<string, Preset> = {
+  'Basic GZIP compression': {
+    fflate: `
+var file = files[0];
+fileToU8(file, function(buf) {
+  fflate.gzip(buf, {
+    level: 6,
+    // These are optional, but fflate supports the metadata
+    mtime: file.lastModified,
+    filename: file.name
+  }, function(err, data) {
+    // Hope you're not causing any errors in the demo ;)
+    callback(data);
+  });
+});`,
+    uzip: `
+var file = files[0];
+fileToU8(file, function(buf) {
+  // UZIP doesn't natively support GZIP, but I patched in support for it.
+  // In other words, you're better off using fflate for GZIP.
+
+  // Also, UZIP runs synchronously on the main thread. It relies on global
+  // state, so you can't even run it in the background without causing bugs.
+
+  // But just for the sake of a performance comparison, try it out.
+  uzipWorker.gzip(buf, function(err, data) {
+    callback(data);
+  });
+});`,
+    pako: `
+var file = files[0];
+fileToU8(file, function(buf) {
+  // Unlike UZIP, Pako natively supports GZIP, and it doesn't rely on global
+  // state. However, it's still 46kB for this basic functionality as opposed
+  // to fflate's 7kB, not to mention the fact that there's no easy way to use
+  // it asynchronously. I had to add a worker proxy for this to work.
+
+  pakoWorker.gzip(buf, function(err, data) {
+    callback(data);
+  });
+}); `
+  }
+}
+
+if (canStream) {
+  presets['Streaming GZIP compression'] = {
+    fflate: `
+const { AsyncGzip } = fflate;
+const file = files[0];
+const gzipStream = new AsyncGzip({ level: 6 });
+// We can stream the file through GZIP to reduce memory usage
+const fakeResponse = new Response(
+  file.stream().pipeThrough(toNativeStream(gzipStream))
+);
+fakeResponse.arrayBuffer().then(buf => {
+  callback(new Uint8Array(buf));
+});`,
+    uzip: `
+// UZIP doesn't support streaming to any extent`,
+    pako: `
+// Hundreds of lines of code to make this run on a Worker...
+const file = files[0];
+// In case this wasn't clear already, Pako doesn't actually support this,
+// you need to create a custom async stream. I suppose you could copy the
+// code used in this demo.
+const gzipStream = pakoWorker.createGzip();
+const fakeResponse = new Response(
+  file.stream().pipeThrough(toNativeStream(gzipStream))
+);
+fakeResponse.arrayBuffer().then(buf => {
+  callback(new Uint8Array(buf));
+});`
+  };
+}
+
+const CodeBox: FC<{files: File[]}> = ({ files }) => {
+  const [{ fflate, uzip, pako }, setCodes] = useState(presets['Streaming GZIP compression']);
+  const onInput = (ev: FormEvent<HTMLInputElement>) => {
+    const codes: Preset ={
+      fflate,
+      uzip,
+      pako
+    };
+    codes[this as unknown as string] = ev.currentTarget.innerText;
+    setCodes(codes);
+  }
+  return (
+    <div style={{
+      display: 'flex',
+      flexDirection: 'column',
+      justifyContent: 'space-between',
+      alignItems: 'center'
+    }}>
+      <div style={{
+        display: 'flex',
+        flexDirection: 'row',
+        justifyContent: 'space-between',
+        whiteSpace: 'pre-wrap',
+        textAlign: 'left'
+      }}>
+        <div>
+          fflate:
+          <code contentEditable className="lang-javascript" onInput={onInput.bind('fflate')}>{fflate}</code>
+        </div>
+        <div>
+          UZIP (shimmed):
+          <code contentEditable className="lang-javascript" onInput={onInput.bind('uzip')}>{uzip}</code>
+        </div>
+        <div>
+          Pako (shimmed):
+          <code contentEditable className="lang-javascript" onInput={onInput.bind('pako')}>{pako}</code>
+        </div>
+      </div>
+      <button onClick={() => {
+        let ts = Date.now();
+        exec(fflate, files, out => {
+          console.log('fflate took', Date.now() - ts);
+          ts = Date.now();
+          exec(uzip, files, out => {
+            console.log('uzip took', Date.now() - ts);
+            ts = Date.now();
+            exec(pako, files, out => {
+              console.log('pako took', Date.now() - ts);
+            })
+          })
+        });
+      }}>Run</button>
+    </div>
+    
+  ); 
+}
+
+export default CodeBox;

+ 125 - 0
src/demo/components/code-box/prism.css

@@ -0,0 +1,125 @@
+/* PrismJS 1.22.0
+https://prismjs.com/download.html#themes=prism-tomorrow&languages=clike+javascript */
+/**
+ * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
+ * Based on https://github.com/chriskempson/tomorrow-theme
+ * @author Rose Pritchard
+ */
+
+code[class*="language-"],
+pre[class*="language-"] {
+	color: #ccc;
+	background: none;
+	font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+	font-size: 1em;
+	text-align: left;
+	white-space: pre;
+	word-spacing: normal;
+	word-break: normal;
+	word-wrap: normal;
+	line-height: 1.5;
+
+	-moz-tab-size: 4;
+	-o-tab-size: 4;
+	tab-size: 4;
+
+	-webkit-hyphens: none;
+	-moz-hyphens: none;
+	-ms-hyphens: none;
+	hyphens: none;
+
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+	padding: 1em;
+	margin: .5em 0;
+	overflow: auto;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+	background: #2d2d2d;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+	padding: .1em;
+	border-radius: .3em;
+	white-space: normal;
+}
+
+.token.comment,
+.token.block-comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+	color: #999;
+}
+
+.token.punctuation {
+	color: #ccc;
+}
+
+.token.tag,
+.token.attr-name,
+.token.namespace,
+.token.deleted {
+	color: #e2777a;
+}
+
+.token.function-name {
+	color: #6196cc;
+}
+
+.token.boolean,
+.token.number,
+.token.function {
+	color: #f08d49;
+}
+
+.token.property,
+.token.class-name,
+.token.constant,
+.token.symbol {
+	color: #f8c555;
+}
+
+.token.selector,
+.token.important,
+.token.atrule,
+.token.keyword,
+.token.builtin {
+	color: #cc99cd;
+}
+
+.token.string,
+.token.char,
+.token.attr-value,
+.token.regex,
+.token.variable {
+	color: #7ec699;
+}
+
+.token.operator,
+.token.entity,
+.token.url {
+	color: #67cdcc;
+}
+
+.token.important,
+.token.bold {
+	font-weight: bold;
+}
+.token.italic {
+	font-style: italic;
+}
+
+.token.entity {
+	cursor: help;
+}
+
+.token.inserted {
+	color: green;
+}
+

File diff suppressed because it is too large
+ 2 - 0
src/demo/components/code-box/prism.js


+ 82 - 0
src/demo/components/code-box/sandbox.ts

@@ -0,0 +1,82 @@
+import * as fflate from '../../../../';
+import toNativeStream from './stream-adapter';
+
+type Callback = (...args: unknown[]) => void;
+type WorkerProxy = Record<string, Callback>;
+const createWorkerProxy = (lib: string, keys: string[]): WorkerProxy => {
+  const p: WorkerProxy = {};
+  for (const k of keys) {
+    const base = function(cb: (...args: unknown[]) => void) {
+      const w = new Worker('../../util/workers.ts');
+      w.postMessage([lib, k]);
+      w.onmessage = function() {
+        const args: unknown[] = Array.prototype.slice.call(arguments);
+        args.unshift(null);
+        cb.apply(null, args);
+      }
+      w.onerror = err => cb(err);
+      return w;
+    }
+    if (k != 'zip' && k != 'unzip') {
+      p[k] = function(dat, cb) {
+        const w = base(cb as Callback);
+        w.postMessage([dat, true], [(dat as Uint8Array).buffer]);
+      }
+      p['create' + k.slice(0, 1).toUpperCase() + k.slice(1)] = function() {
+        let trueCb = arguments[0];
+        const w = base((err, dat, final) => {
+          trueCb(err, dat, final);
+        });
+        const out = {
+          ondata: undefined,
+          push(v: Uint8Array, f: boolean) {
+            if (!out.ondata) throw 'no callback';
+            trueCb = out.ondata;
+            w.postMessage([v, f], [v.buffer]);
+          },
+          terminate() {
+            w.terminate();
+          }
+        }
+        return out;
+      }
+    } else {
+      p[k] = function() {
+        const w = base(arguments[arguments.length - 1]);
+        const bufs: ArrayBuffer[] = [];
+        for (const k in arguments[0]) {
+          bufs.push((arguments[k] = new Uint8Array(arguments[k])).buffer);
+        }
+        w.postMessage(Array.prototype.slice.call(arguments, 0, -1), bufs);
+      }
+    }
+  }
+  return p;
+}
+
+const keys = ['zip', 'unzip', 'deflate', 'inflate', 'gzip', 'gunzip', 'zlib', 'unzlib'];
+
+const uzipWorker = createWorkerProxy('uzip', keys);
+const pakoWorker = createWorkerProxy('pako', keys);
+const fr = new FileReader();
+const fileToU8 = (file: File, cb: (out: Uint8Array) => void) => {
+  fr.onload = () => {
+    cb(new Uint8Array(fr.result as ArrayBuffer));
+  }
+  fr.readAsArrayBuffer(file);
+};
+
+const exec = (code: string, files: File[], callback: Callback): unknown => {
+  const scope = {
+    fflate,
+    uzipWorker,
+    pakoWorker,
+    toNativeStream,
+    callback,
+    fileToU8,
+    files
+  };
+  return new Function('"use strict";' + Object.keys(scope).map(k => 'var ' + k + ' = this["' + k + '"];').join('') + code).call(scope);
+}
+
+export default exec;

+ 3 - 2
src/demo/util/workers.ts

@@ -104,15 +104,16 @@ onmessage = (ev: MessageEvent<[string, string]>) => {
           zip[ev.data[0]] = ev.data[1];
           zip[ev.data[0]] = ev.data[1];
         } else {
         } else {
           const buf = UZIP.encode(zip);
           const buf = UZIP.encode(zip);
-          wk.postMessage(buf, [buf.buffer]);
+          wk.postMessage(new Uint8Array(buf), [buf]);
         }
         }
       };
       };
     } else if (type == 'unzip') {
     } else if (type == 'unzip') {
       onmessage = (ev: MessageEvent<Uint8Array>) => {
       onmessage = (ev: MessageEvent<Uint8Array>) => {
-        const bufs = UZIP.parse(ev.data);
+        const bufs = UZIP.parse(ev.data.buffer);
         const outBufs: ArrayBuffer[] = [];
         const outBufs: ArrayBuffer[] = [];
         for (const k in bufs) {
         for (const k in bufs) {
           outBufs.push(bufs[k]);
           outBufs.push(bufs[k]);
+          bufs[k] = new Uint8Array(bufs[k]);
         }
         }
         wk.postMessage(bufs, outBufs);
         wk.postMessage(bufs, outBufs);
       }
       }

+ 6 - 6
src/index.ts

@@ -648,7 +648,7 @@ const dflt = (dat: Uint8Array, lvl: number, plvl: number, pre: number, post: num
     }
     }
     pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
     pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
     // this is the easiest way to avoid needing to maintain state
     // this is the easiest way to avoid needing to maintain state
-    if (!lst) pos = wfblk(w, ++pos, et);
+    if (!lst) pos = wfblk(w, pos, et);
   }
   }
   return slc(o, 0, pre + shft(pos) + post);
   return slc(o, 0, pre + shft(pos) + post);
 }
 }
@@ -1041,7 +1041,7 @@ function AsyncCmpStrm<T>(opts: T, cb?: AsyncFlateStreamHandler): T;
  */
  */
 function AsyncCmpStrm<T>(cb?: AsyncFlateStreamHandler): T;
 function AsyncCmpStrm<T>(cb?: AsyncFlateStreamHandler): T;
 function AsyncCmpStrm<T>(opts?: T | AsyncFlateStreamHandler, cb?: AsyncFlateStreamHandler): T {
 function AsyncCmpStrm<T>(opts?: T | AsyncFlateStreamHandler, cb?: AsyncFlateStreamHandler): T {
-  if (!cb) cb = opts as AsyncFlateStreamHandler, opts = {} as T;
+  if (!cb && typeof opts == 'function') cb = opts as AsyncFlateStreamHandler, opts = {} as T;
   this.ondata = cb as AsyncFlateStreamHandler;
   this.ondata = cb as AsyncFlateStreamHandler;
   return opts as T;
   return opts as T;
 }
 }
@@ -1060,7 +1060,7 @@ export class Deflate {
   constructor(opts: DeflateOptions, cb?: FlateStreamHandler);
   constructor(opts: DeflateOptions, cb?: FlateStreamHandler);
   constructor(cb?: FlateStreamHandler);
   constructor(cb?: FlateStreamHandler);
   constructor(opts?: DeflateOptions | FlateStreamHandler, cb?: FlateStreamHandler) {
   constructor(opts?: DeflateOptions | FlateStreamHandler, cb?: FlateStreamHandler) {
-    if (!cb) cb = opts as FlateStreamHandler, opts = {};
+    if (!cb && typeof opts == 'function') cb = opts as FlateStreamHandler, opts = {};
     this.ondata = cb;
     this.ondata = cb;
     this.o = (opts as DeflateOptions) || {};
     this.o = (opts as DeflateOptions) || {};
   }
   }
@@ -1113,7 +1113,7 @@ export class AsyncDeflate {
       bDflt,
       bDflt,
       () => [astrm, Deflate]
       () => [astrm, Deflate]
     ], this as unknown as Astrm, AsyncCmpStrm.call(this, opts, cb), ev => {
     ], this as unknown as Astrm, AsyncCmpStrm.call(this, opts, cb), ev => {
-      const strm = new Deflate(ev.data, true as unknown as FlateStreamHandler);
+      const strm = new Deflate(ev.data);
       onmessage = astrm(strm);
       onmessage = astrm(strm);
     }, 6);
     }, 6);
   }
   }
@@ -1357,7 +1357,7 @@ export class AsyncGzip {
       gze,
       gze,
       () => [astrm, Deflate, Gzip]
       () => [astrm, Deflate, Gzip]
     ], this as unknown as Astrm, AsyncCmpStrm.call(this, opts, cb), ev => {
     ], this as unknown as Astrm, AsyncCmpStrm.call(this, opts, cb), ev => {
-      const strm = new Gzip(ev.data, true as unknown as FlateStreamHandler);
+      const strm = new Gzip(ev.data);
       onmessage = astrm(strm);
       onmessage = astrm(strm);
     }, 8);
     }, 8);
   }
   }
@@ -1600,7 +1600,7 @@ export class AsyncZlib {
       zle,
       zle,
       () => [astrm, Deflate, Zlib]
       () => [astrm, Deflate, Zlib]
     ], this as unknown as Astrm, AsyncCmpStrm.call(this, opts, cb), ev => {
     ], this as unknown as Astrm, AsyncCmpStrm.call(this, opts, cb), ev => {
-      const strm = new Zlib(ev.data, true as unknown as FlateStreamHandler);
+      const strm = new Zlib(ev.data);
       onmessage = astrm(strm);
       onmessage = astrm(strm);
     }, 10);
     }, 10);
   }
   }

Some files were not shown because too many files changed in this diff