UNPKG

web-worker-helper

Version:

Utilities for running tasks on worker threads

134 lines (118 loc) 3.92 kB
/* global importScripts */ import { global, isWorker } from '../env-utils/globals'; import { assert } from '../env-utils/assert'; const loadLibraryPromises: Record<string, Promise<any>> = {}; // promises /** * Dynamically loads a library ("module") * * - wasm library: Array buffer is returned * - js library: Parse JS is returned * * Method depends on environment * - browser - script element is created and installed on document * - worker - eval is called on global context * - node - file is required * * @param libraryUrl * @param moduleName * @param options */ export async function loadLibrary( libraryUrl: string, moduleName: string | null = null, options: object = {} ): Promise<any> { if (moduleName) { libraryUrl = getLibraryUrl(libraryUrl, moduleName, options); } // Ensure libraries are only loaded once loadLibraryPromises[libraryUrl] = loadLibraryPromises[libraryUrl] || loadLibraryFromFile(libraryUrl); return await loadLibraryPromises[libraryUrl]; } // TODO - sort out how to resolve paths for main/worker and dev/prod export function getLibraryUrl(library: string, moduleName?: string, options?: any): string { // Check if already a URL if (library.startsWith('http')) { return library; } // Allow application to import and supply libraries through `options.modules` const modules = options.modules || {}; if (modules[library]) { return modules[library]; } // load from external scripts if (options.CDN) { assert(options.CDN.startsWith('http')); return `${options.CDN}/${moduleName}/dist/libs/${library}`; } // TODO - loading inside workers requires paths relative to worker script location... if (isWorker) { return `../src/libs/${library}`; } return `modules/${moduleName}/src/libs/${library}`; } async function loadLibraryFromFile(libraryUrl: string): Promise<any> { if (libraryUrl.endsWith('wasm')) { const response = await fetch(libraryUrl); return await response.arrayBuffer(); } if (isWorker) { return importScripts(libraryUrl); } // TODO - fix - should be more secure than string parsing since observes CORS // if (isBrowser) { // return await loadScriptFromFile(libraryUrl); // } const response = await fetch(libraryUrl); const scriptSource = await response.text(); return loadLibraryFromString(scriptSource, libraryUrl); } /* async function loadScriptFromFile(libraryUrl) { const script = document.createElement('script'); script.src = libraryUrl; return await new Promise((resolve, reject) => { script.onload = data => { resolve(data); }; script.onerror = reject; }); } */ // TODO - Needs security audit... // - Raw eval call // - Potentially bypasses CORS // Upside is that this separates fetching and parsing // we could create a`LibraryLoader` or`ModuleLoader` function loadLibraryFromString(scriptSource: string, id: string): null | any { if (isWorker) { // Use lvalue trick to make eval run in global scope eval.call(global, scriptSource); // eslint-disable-line no-eval // https://stackoverflow.com/questions/9107240/1-evalthis-vs-evalthis-in-javascript return null; } const script = document.createElement('script'); script.id = id; // most browsers like a separate text node but some throw an error. The second method covers those. try { script.appendChild(document.createTextNode(scriptSource)); } catch (e) { script.text = scriptSource; } document.body.appendChild(script); return null; } // TODO - technique for module injection into worker, from THREE.DracoLoader... /* function combineWorkerWithLibrary(worker, jsContent) { var fn = wWorker.toString(); var body = [ '// injected', jsContent, '', '// worker', fn.substring(fn.indexOf('{') + 1, fn.lastIndexOf('}')) ].join('\n'); this.workerSourceURL = URL.createObjectURL(new Blob([body])); } */