瀏覽代碼

Finishing demo

Arjun Barrett 4 年之前
父節點
當前提交
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 CodeBox from './components/code-box';
 
 const App: FC = () => {
+  const [files, setFiles] = useState<File[]>([])
   return (
     <>
       <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.
           <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.
+          <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>
-        <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 deflate(buf: Uint8Array, opts?: { level: number }): 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;
 }

+ 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;
 
 type Preset = {
   fflate: 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;
+}
+

文件差異過大導致無法顯示
+ 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];
         } else {
           const buf = UZIP.encode(zip);
-          wk.postMessage(buf, [buf.buffer]);
+          wk.postMessage(new Uint8Array(buf), [buf]);
         }
       };
     } else if (type == 'unzip') {
       onmessage = (ev: MessageEvent<Uint8Array>) => {
-        const bufs = UZIP.parse(ev.data);
+        const bufs = UZIP.parse(ev.data.buffer);
         const outBufs: ArrayBuffer[] = [];
         for (const k in bufs) {
           outBufs.push(bufs[k]);
+          bufs[k] = new Uint8Array(bufs[k]);
         }
         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);
     // 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);
 }
@@ -1041,7 +1041,7 @@ function AsyncCmpStrm<T>(opts: T, cb?: AsyncFlateStreamHandler): T;
  */
 function AsyncCmpStrm<T>(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;
   return opts as T;
 }
@@ -1060,7 +1060,7 @@ export class Deflate {
   constructor(opts: DeflateOptions, cb?: FlateStreamHandler);
   constructor(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.o = (opts as DeflateOptions) || {};
   }
@@ -1113,7 +1113,7 @@ export class AsyncDeflate {
       bDflt,
       () => [astrm, Deflate]
     ], 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);
     }, 6);
   }
@@ -1357,7 +1357,7 @@ export class AsyncGzip {
       gze,
       () => [astrm, Deflate, Gzip]
     ], 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);
     }, 8);
   }
@@ -1600,7 +1600,7 @@ export class AsyncZlib {
       zle,
       () => [astrm, Deflate, Zlib]
     ], 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);
     }, 10);
   }

部分文件因文件數量過多而無法顯示