forked from sheetjs/docs.sheetjs.com
		
	
		
			
	
	
		
			219 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			219 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|  | --- | ||
|  | title: Web Workers | ||
|  | --- | ||
|  | 
 | ||
|  | Parsing and writing large spreadsheets takes time. During the process, if the | ||
|  | SheetJS library is running in the web browser, the website may freeze. | ||
|  | 
 | ||
|  | Workers provide a way to off-load the hard work so that the website does not | ||
|  | freeze during processing. | ||
|  | 
 | ||
|  | :::note Browser Compatibility | ||
|  | 
 | ||
|  | IE10+ and modern browsers support basic Web Workers. Some APIs like `fetch` were | ||
|  | added later.  Feature testing is highly recommended. | ||
|  | 
 | ||
|  | ::: | ||
|  | 
 | ||
|  | ## Installation
 | ||
|  | 
 | ||
|  | In all cases, `importScripts` can load the [Standalone scripts](../getting-started/installation/standalone) | ||
|  | 
 | ||
|  | ```js | ||
|  | importScripts("https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"); | ||
|  | ``` | ||
|  | 
 | ||
|  | For production use, it is highly encouraged to download and host the script. | ||
|  | 
 | ||
|  | ## Downloading a Remote File
 | ||
|  | 
 | ||
|  | :::note | ||
|  | 
 | ||
|  | `fetch` was enabled in Web Workers in Chrome 42 and Safari 10.3 | ||
|  | 
 | ||
|  | ::: | ||
|  | 
 | ||
|  | Typically the Web Worker performs the `fetch` operation, processes the workbook, | ||
|  | and sends a final result to the main browser context for processing. | ||
|  | 
 | ||
|  | In the following example, the script: | ||
|  | 
 | ||
|  | - downloads <https://sheetjs.com/pres.numbers> in a Web Worker | ||
|  | - loads the SheetJS library and parses the file in the Worker | ||
|  | - generates an HTML string of the first table in the Worker | ||
|  | - sends the string to the main browser context | ||
|  | - adds the HTML to the page in the main browser context | ||
|  | 
 | ||
|  | ```jsx live | ||
|  | function SheetJSFetchDLWorker() { | ||
|  |   const [html, setHTML] = React.useState(""); | ||
|  | 
 | ||
|  |   return ( <> | ||
|  |     <button onClick={() => { | ||
|  |       /* this mantra embeds the worker source in the function */ | ||
|  |       const worker = new Worker(URL.createObjectURL(new Blob([`\ | ||
|  | /* load standalone script from CDN */ | ||
|  | importScripts("https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"); | ||
|  | 
 | ||
|  | /* this callback will run once the main context sends a message */ | ||
|  | self.addEventListener('message', async(e) => { | ||
|  |   try { | ||
|  |     /* Fetch file */ | ||
|  |     const res = await fetch("https://sheetjs.com/pres.numbers"); | ||
|  |     const ab = await res.arrayBuffer(); | ||
|  | 
 | ||
|  |     /* Parse file */ | ||
|  |     const wb = XLSX.read(ab); | ||
|  |     const ws = wb.Sheets[wb.SheetNames[0]]; | ||
|  | 
 | ||
|  |     /* Generate HTML */ | ||
|  |     const html = XLSX.utils.sheet_to_html(ws); | ||
|  | 
 | ||
|  |     /* Reply with result */ | ||
|  |     postMessage({html: html}); | ||
|  |   } catch(e) { | ||
|  |     /* Pass the error message back */ | ||
|  |     postMessage({html: String(e.message || e).bold() }); | ||
|  |   } | ||
|  | }, false); | ||
|  |       `]))); | ||
|  |       /* when the worker sends back the HTML, add it to the DOM */ | ||
|  |       worker.onmessage = function(e) { setHTML(e.data.html); }; | ||
|  |       /* post a message to the worker */ | ||
|  |       worker.postMessage({}); | ||
|  |     }}><b>Click to Start</b></button> | ||
|  |     <div dangerouslySetInnerHTML={{__html: html}}/> | ||
|  |   </> ); | ||
|  | } | ||
|  | ``` | ||
|  | 
 | ||
|  | ## Creating a Local File
 | ||
|  | 
 | ||
|  | :::caution `XLSX.writeFile` | ||
|  | 
 | ||
|  | `XLSX.writeFile` will not work in Web Workers!  Raw file data can be passed from | ||
|  | the Web Worker to the main browser context for downloading. | ||
|  | 
 | ||
|  | ::: | ||
|  | 
 | ||
|  | In the following example, the script: | ||
|  | 
 | ||
|  | - generates a workbook object in the Web Worker | ||
|  | - generates a XLSB file using `XLSX.write` in the Web Worker | ||
|  | - sends the file (`Uint8Array`) to the main browser context | ||
|  | - performs a download action in the main browser context | ||
|  | 
 | ||
|  | ```jsx live | ||
|  | function SheetJSWriteFileWorker() { | ||
|  |   const [html, setHTML] = React.useState(""); | ||
|  | 
 | ||
|  |   return ( <> | ||
|  |     <button onClick={() => { setHTML(""); | ||
|  |       /* this mantra embeds the worker source in the function */ | ||
|  |       const worker = new Worker(URL.createObjectURL(new Blob([`\ | ||
|  | /* load standalone script from CDN */ | ||
|  | importScripts("https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"); | ||
|  | 
 | ||
|  | /* this callback will run once the main context sends a message */ | ||
|  | self.addEventListener('message', async(e) => { | ||
|  |   try { | ||
|  |     /* Create a new Workbook (in this case, from a CSV string) */ | ||
|  |     const csv = \`\ | ||
|  | SheetJS,in,Web,Workers | ||
|  | வணக்கம்,สวัสดี,你好,가지마 | ||
|  | 1,2,3,4\`; | ||
|  |     const wb = XLSX.read(csv, { type: "string" }); | ||
|  | 
 | ||
|  |     /* Write XLSB data */ | ||
|  |     const u8 = XLSX.write(wb, { bookType: "xlsb", type: "buffer" }); | ||
|  | 
 | ||
|  |     /* Reply with result */ | ||
|  |     postMessage({data: u8}); | ||
|  |   } catch(e) { | ||
|  |     /* Pass the error message back */ | ||
|  |     postMessage({error: String(e.message || e).bold() }); | ||
|  |   } | ||
|  | }, false); | ||
|  |       `]))); | ||
|  |       /* when the worker sends back the data, create a download */ | ||
|  |       worker.onmessage = function(e) { | ||
|  |         if(e.data.error) return setHTML(e.data.error); | ||
|  | 
 | ||
|  |         /* this mantra is the standard HTML5 download attribute technique */ | ||
|  |         const a = document.createElement("a"); | ||
|  |         a.download = "SheetJSWriteFileWorker.xlsb"; | ||
|  |         a.href = URL.createObjectURL(new Blob([e.data.data])); | ||
|  |         document.body.appendChild(a); | ||
|  |         a.click(); | ||
|  |         document.body.removeChild(a); | ||
|  |       }; | ||
|  |       /* post a message to the worker */ | ||
|  |       worker.postMessage({}); | ||
|  |     }}><b>Click to Start</b></button> | ||
|  |     <div dangerouslySetInnerHTML={{__html: html}}/> | ||
|  |   </> ); | ||
|  | } | ||
|  | ``` | ||
|  | 
 | ||
|  | ## User-Submitted File
 | ||
|  | 
 | ||
|  | :::note | ||
|  | 
 | ||
|  | Typically `FileReader` is used in the main browser context. In Web Workers, the | ||
|  | synchronous version `FileReaderSync` is more efficient. | ||
|  | 
 | ||
|  | ::: | ||
|  | 
 | ||
|  | In the following example, the script: | ||
|  | 
 | ||
|  | - waits for the user to drag-drop a file into a DIV | ||
|  | - sends the `File` object to the Web Worker | ||
|  | - loads the SheetJS library and parses the file in the Worker | ||
|  | - generates an HTML string of the first table in the Worker | ||
|  | - sends the string to the main browser context | ||
|  | - adds the HTML to the page in the main browser context | ||
|  | 
 | ||
|  | ```jsx live | ||
|  | function SheetJSDragDropWorker() { | ||
|  |   const [html, setHTML] = React.useState(""); | ||
|  |   /* suppress default behavior for dragover and drop */ | ||
|  |   function suppress(e) { e.stopPropagation(); e.preventDefault(); } | ||
|  |   return ( <> | ||
|  |     <div onDragOver={suppress} onDrop={(e) => { | ||
|  |       suppress(e); | ||
|  | 
 | ||
|  |       /* this mantra embeds the worker source in the function */ | ||
|  |       const worker = new Worker(URL.createObjectURL(new Blob([`\ | ||
|  | /* load standalone script from CDN */ | ||
|  | importScripts("https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"); | ||
|  | 
 | ||
|  | /* this callback will run once the main context sends a message */ | ||
|  | self.addEventListener('message', async(e) => { | ||
|  |   try { | ||
|  |     /* Read file data */ | ||
|  |     const ab = new FileReaderSync().readAsArrayBuffer(e.data.file); | ||
|  | 
 | ||
|  |     /* Parse file */ | ||
|  |     const wb = XLSX.read(ab); | ||
|  |     const ws = wb.Sheets[wb.SheetNames[0]]; | ||
|  | 
 | ||
|  |     /* Generate HTML */ | ||
|  |     const html = XLSX.utils.sheet_to_html(ws); | ||
|  | 
 | ||
|  |     /* Reply with result */ | ||
|  |     postMessage({html: html}); | ||
|  |   } catch(e) { | ||
|  |     /* Pass the error message back */ | ||
|  |     postMessage({html: String(e.message || e).bold() }); | ||
|  |   } | ||
|  | }, false); | ||
|  |       `]))); | ||
|  |       /* when the worker sends back the HTML, add it to the DOM */ | ||
|  |       worker.onmessage = function(e) { setHTML(e.data.html); }; | ||
|  |       /* post a message with the first File to the worker */ | ||
|  |       worker.postMessage({ file: e.dataTransfer.files[0] }); | ||
|  |     }}>Drag a file to this DIV to process!</div> | ||
|  |     <div dangerouslySetInnerHTML={{__html: html}}/> | ||
|  |   </> ); | ||
|  | } | ||
|  | ``` |