UNPKG

fast-base64

Version:

Fastest possible base64 encoding/decoding using WebAssembly

151 lines (147 loc) 8.76 kB
// inline-worker:__inline-worker function inlineWorker(scriptText) { let blob = new Blob([scriptText], { type: "text/javascript" }); let url = URL.createObjectURL(blob); let worker = new Worker(url); URL.revokeObjectURL(url); return worker; } // wasm.worker.js function Worker2() { return inlineWorker('var u="AGFzbQEAAAABDAJgAn9/AGADf39/AAIVAQdpbXBvcnRzBm1lbW9yeQIDAIAIAwMCAAEHHwIMYmFzZTY0MmJ5dGVzAAAMYnl0ZXMyYmFzZTY0AAEKjgUCxQICAn8BeyAAIQIgACEDA0AgAv0ABAAhBCAEQQT9D/1uIARBwAD9D/0oQcUA/Q/9Tv1xIARB4AD9D/0oQQb9D/1O/XEgBEEr/Q/9I0EP/Q/9Tv1uIARBL/0P/SNBDP0P/U79biEEIAT9DD8AAAA/AAAAPwAAAD8AAAD9TkEC/asBIAT9DAAwAAAAMAAAADAAAAAwAAD9TkEM/a0B/VAgBP0MAA8AAAAPAAAADwAAAA8AAP1OQQT9qwEgBP0MAAA8AAAAPAAAADwAAAA8AP1OQQr9rQH9UP1QIAT9DAAAAwAAAAMAAAADAAAAAwD9TkEG/asBIAT9DAAAAD8AAAA/AAAAPwAAAD/9TkEI/a0B/VD9UP0MAAECBAUGCAkKDA0OEBAQEP0OIQQgAyAE/QsEACACQRBqIQIgA0EMaiEDIAIgAUkNAAsLxAICAn8BeyACIQMgACEEA0AgBP0ABAD9DAABAhADBAUQBgcIEAkKCxD9DiEFIAX9DPwAAAD8AAAA/AAAAPwAAAD9TkEC/a0BIAX9DAMAAAADAAAAAwAAAAMAAAD9TkEM/asBIAX9DADwAAAA8AAAAPAAAADwAAD9TkEE/a0B/VD9UCAF/QwADwAAAA8AAAAPAAAADwAA/U5BCv2rASAF/QwAAMAAAADAAAAAwAAAAMAA/U5BBv2tAf1Q/VAgBf0MAAA/AAAAPwAAAD8AAAA/AP1OQQj9qwH9UCEFIAVBwQD9D/1uIAVBGf0P/ShBBv0P/U79biAFQTP9D/0oQcsA/Q/9Tv1xIAVBPv0P/SNBD/0P/U79cSAFQT/9D/0jQQz9D/1O/XEhBSADIAX9CwQAIANBEGohAyAEQQxqIQQgBCABSQ0ACws=";var b="AGFzbQEAAAABDAJgAn9/AGADf39/AAITAQdpbXBvcnRzBm1lbW9yeQIAAAMDAgABBx8CDGJhc2U2NDJieXRlcwAADGJ5dGVzMmJhc2U2NAABCrcEApgCAQJ/IAAhAwNAIAMgAEEDai0AACICQQRqIAJBwABLQcUAbGsgAkHgAEtBBmxrIAJBK0ZBD2xqIAJBL0ZBDGxqIAAtAAAiAkEEaiACQcAAS0HFAGxrIAJB4ABLQQZsayACQStGQQ9saiACQS9GQQxsakESdCAAQQFqLQAAIgJBBGogAkHAAEtBxQBsayACQeAAS0EGbGsgAkErRkEPbGogAkEvRkEMbGpBDHRyIABBAmotAAAiAkEEaiACQcAAS0HFAGxrIAJB4ABLQQZsayACQStGQQ9saiACQS9GQQxsakEGdHJyIgJBEHY6AAAgA0EBaiACQQh2OgAAIANBAmogAjoAACADQQNqIQMgAEEEaiIAIAFJDQALC5oCAQJ/A0AgAiAAQQJqLQAAIAAtAABBEHQgAEEBai0AAEEIdHJyIgRBEnYiA0HBAGogA0EZS0EGbGogA0EzS0HLAGxrIANBPkZBD2xrIANBP0ZBDGxrOgAAIAJBAWogBEEMdkE/cSIDQcEAaiADQRlLQQZsaiADQTNLQcsAbGsgA0E+RkEPbGsgA0E/RkEMbGs6AAAgAkECaiAEQQZ2QT9xIgNBwQBqIANBGUtBBmxqIANBM0tBywBsayADQT5GQQ9sayADQT9GQQxsazoAACACQQNqIARBP3EiA0HBAGogA0EZS0EGbGogA0EzS0HLAGxrIANBPkZBD2xrIANBP0ZBDGxrOgAAIAJBBGohAiAAQQNqIgAgAUkNAAsL";var i=WebAssembly,m={},j={};function y(r,{fallback:n}){let e=Math.random().toString();return s=>w(e,r,s,n)}async function w(r,n,e={},s){let t=j[r];if(t===void 0||!O(e,t.importObject)){let l=m[r];if(l)t=l.then(A=>i.instantiate(A,e));else{let A=p(n),o=i.instantiate(A,e).catch(a=>{if(s===void 0)throw a;return console.warn(a),console.warn("falling back to version without experimental feature"),A=p(s),i.instantiate(A,e)});m[r]=o.then(a=>a.module),t=o.then(a=>a.instance)}t.importObject=e,j[r]=t}return(await t).exports}var L=new i.Memory({initial:1});function O(r,n){let e=Object.keys(r),s=e.length;for(let t=0;t<s;t++)if(!N(r[e[t]],n[e[t]]))return!1;return s===Object.keys(n).length}function N(r,n){if(r===n)return!0;let e=Object.keys(r),s=e.length;for(let t=0;t<s;t++)if(r[e[t]]!==n[e[t]])return!1;return s===Object.keys(n).length}var P="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",f=Object.fromEntries(Array.from(P).map((r,n)=>[r.charCodeAt(0),n]));f[61]=0;function p(r){r=r.replace(/=/g,"");let n=r.length,e=n%4,s=(n>>2)*3+(e&&e-1),t=new TextEncoder().encode(r+"===");for(let l=0,A=0;l<n;l+=4,A+=3){let o=(f[t[l]]<<18)+(f[t[l+1]]<<12)+(f[t[l+2]]<<6)+f[t[l+3]];t[A]=o>>16,t[A+1]=o>>8&255,t[A+2]=o&255}return new Uint8Array(t.buffer,0,s)}function d(r){postMessage([-1,!0,Object.keys(r)]),addEventListener("message",async({data:[n,e,s]})=>{try{let t=await r[e](...s),l=F([t]);postMessage([n,!0,t],l)}catch(t){postMessage([n,!1,""+t])}})}function F(r){let n=[];for(let e of r)e instanceof ArrayBuffer||e instanceof MessagePort||globalThis.ImageBitmap&&e instanceof ImageBitmap||globalThis.OffscreenCanvas&&e instanceof OffscreenCanvas?n.push(e):ArrayBuffer.isView(e)&&e.buffer instanceof ArrayBuffer&&n.push(e.buffer);return n}d({toBytes:T,toBase64:x});var K=new TextEncoder,v=new TextDecoder,k=y(u,{fallback:b});async function T(r,n,e){let s=e.length,{base642bytes:t}=await k({imports:{memory:r}}),l=new Uint8Array(r.buffer,n.byteOffset,s);l.set(K.encode(e)),t(l.byteOffset,l.byteOffset+s)}async function x(r,n){let e=n.byteLength-2,s=e%3,t=Math.floor(e/3)*4+(s&&s+1),l=e+2,{bytes2base64:A}=await k({imports:{memory:r}}),o=new Uint8Array(r.buffer,n.byteOffset,l);o[e]=0,o[e+1]=0,A(n.byteOffset,n.byteOffset+e,n.byteOffset+l);let a=new Uint8Array(r.buffer,n.byteOffset+l,t).slice(),c=v.decode(a);return s===1&&(c+="=="),s===2&&(c+="="),c}\n'); } // worker-tools.js async function workerImport(worker, knownImports) { let id = 0; let promises = {}; promises[-1] = !knownImports && Resolvable(); worker.addEventListener("message", ({ data: [id2, isSuccess, result] }) => { if (isSuccess) promises[id2]?.resolve(result); else promises[id2]?.reject(result); delete promises[id2]; }); let imports = knownImports ?? await promises[-1].promise; const caller = (funcName) => (...args) => { let thisId = id++; promises[thisId] = Resolvable(); let transfer = getTransferables(args); worker.postMessage([thisId, funcName, args], transfer); return promises[thisId].promise; }; return Object.fromEntries(imports.map((name) => [name, caller(name)])); } function Resolvable() { let resolvable = {}; resolvable.promise = new Promise((resolve, reject) => { resolvable.resolve = resolve; resolvable.reject = reject; }); return resolvable; } function getTransferables(args) { let transfer = []; for (let x of args) { if (x instanceof ArrayBuffer || x instanceof MessagePort || globalThis.ImageBitmap && x instanceof ImageBitmap || globalThis.OffscreenCanvas && x instanceof OffscreenCanvas) { transfer.push(x); } else if (ArrayBuffer.isView(x) && x.buffer instanceof ArrayBuffer) { transfer.push(x.buffer); } } return transfer; } // wasm-threads.js var nWorkers = 4; var workerImports = Promise.all(new Array(nWorkers).fill(0).map(() => workerImport(Worker2()))); async function toBytes(base64) { base64 = base64.replace(/=/g, ""); let n = base64.length; let rem = n % 4; let k = rem && rem - 1; let m = (n >> 2) * 3 + k; let [memory2, view] = allocate(n + nWorkers * 16); let j = Math.floor(n / nWorkers); j -= j % 4; let slices = new Array(nWorkers); for (let i = 0; i < nWorkers - 1; i++) { slices[i] = [i * j, (i + 1) * j]; } slices[nWorkers - 1] = [(nWorkers - 1) * j, n]; let workers = await workerImports; await Promise.all(slices.map(([j0, j1], i) => { let workerView = { byteLength: j1 - j0 + 16, byteOffset: view.byteOffset + j0 + i * 16 }; let base64Worker = base64.slice(j0, j1); return workers[i].toBytes(memory2, workerView, base64Worker); })); let bytes = new Uint8Array(memory2.buffer, view.byteOffset, n); let decoded = bytes.slice(0, m); free(memory2, view); return decoded; } async function toBase64(bytes) { let m = bytes.length; let N = Math.ceil(m / 3) * 4; let [memory2, view] = allocate(N + m + 2 * nWorkers); let array = new Uint8Array(memory2.buffer, view.byteOffset, view.byteLength); let j = Math.floor(m / nWorkers); j -= j % 3; let slices = new Array(nWorkers); for (let i = 0; i < nWorkers - 1; i++) { slices[i] = [i * j, (i + 1) * j]; } slices[nWorkers - 1] = [(nWorkers - 1) * j, m]; let workers = await workerImports; let parts = await Promise.all(slices.map(([j0, j1], i) => { let workerView = { byteLength: j1 - j0 + 2, byteOffset: view.byteOffset + j0 + i * 2 }; array.set(bytes.slice(j0, j1), workerView.byteOffset); return workers[i].toBase64(memory2, workerView); })); free(memory2, view); return parts.join(""); } var bytesPerPage = 65536; var MAX_PERSISTENT_BYTES = 1e6; var memory = new WebAssembly.Memory({ initial: 1, maximum: 1024, shared: true }); var offsets = [0]; function allocate(n) { let lastOffset = offsets[offsets.length - 1]; if (lastOffset + n > memory.buffer.byteLength) { const missingPages = Math.ceil((lastOffset + n - memory.buffer.byteLength) / bytesPerPage); memory.grow(missingPages); } offsets.push(lastOffset + n); return [memory, { byteOffset: lastOffset, byteLength: n }]; } function free(myMemory, { byteOffset: start, byteLength: n }) { if (myMemory !== memory) { return; } let i = offsets.indexOf(start + n); if (i !== -1) offsets.splice(i, 1); if (memory.buffer.byteLength >= MAX_PERSISTENT_BYTES) { setTimeout(() => { memory = new WebAssembly.Memory({ initial: 1, maximum: 1024, shared: true }); offsets = [0]; }, 0); } } export { toBase64, toBytes };