From e50dde6b20be71cd8fb4068d207277f04a627c12 Mon Sep 17 00:00:00 2001
From: SheetJS <dev@sheetjs.com>
Date: Wed, 19 Oct 2022 06:05:59 -0400
Subject: [PATCH] worker

---
 .../01-installation/06-deno.md                |   2 -
 docz/docs/03-demos/02-mobile.md               |   4 +-
 docz/docs/03-demos/03-desktop.md              |   6 +-
 docz/docs/03-demos/05-database.md             |  10 +-
 docz/docs/03-demos/07-worker.md               | 219 ++++++++++++++++++
 docz/docs/03-demos/11-angular.md              |   2 +-
 docz/docs/03-demos/19-bundler.md              |   5 +-
 docz/docs/03-demos/33-localfile.md            |  18 --
 docz/docs/03-demos/45-git.md                  |   2 +-
 docz/docs/03-demos/index.md                   |   4 +-
 docz/docs/06-solutions/01-input.md            |   8 +-
 docz/docs/08-api/index.md                     |   4 +-
 docz/docs/09-miscellany/03-source.md          |   9 +-
 docz/docs/09-miscellany/05-contributing.md    |   2 +-
 docz/static/live/fetch.html                   |  23 ++
 docz/static/react/fetch.html                  |  38 +++
 docz/static/vue/fetch.html                    |  43 ++++
 17 files changed, 355 insertions(+), 44 deletions(-)
 create mode 100644 docz/docs/03-demos/07-worker.md
 create mode 100644 docz/static/live/fetch.html
 create mode 100644 docz/static/react/fetch.html
 create mode 100644 docz/static/vue/fetch.html

diff --git a/docz/docs/02-getting-started/01-installation/06-deno.md b/docz/docs/02-getting-started/01-installation/06-deno.md
index a873416..91dd604 100644
--- a/docz/docs/02-getting-started/01-installation/06-deno.md
+++ b/docz/docs/02-getting-started/01-installation/06-deno.md
@@ -35,8 +35,6 @@ The `@deno-types` comment instructs Deno to use the type definitions.
 Older releases are technically available on [deno.land/x](https://deno.land/x/)
 but the Deno registry is out of date.
 
-[This is a known registry bug](https://github.com/denoland/dotland/issues/2072)
-
 <https://cdn.sheetjs.com/> is the authoritative source for SheetJS scripts.
 
 :::
diff --git a/docz/docs/03-demos/02-mobile.md b/docz/docs/03-demos/02-mobile.md
index 6f068ed..86a67c5 100644
--- a/docz/docs/03-demos/02-mobile.md
+++ b/docz/docs/03-demos/02-mobile.md
@@ -327,7 +327,7 @@ This example tries to separate the library-specific functions.
 
 0) **Follow the official React Native CLI Guide!**
 
-Development Environment Guide: <http://reactnative.dev/docs/environment-setup>
+Development Environment Guide: <https://reactnative.dev/docs/environment-setup>
 
 Follow the instructions for iOS and for Android.  They will cover installation
 and system configuration.  By the end, you should be able to run the sample app
@@ -676,7 +676,7 @@ on an iPhone SE 3rd generation.
 NativeScript will not safely transmit binary or UTF-8 strings. XLSB, NUMBERS,
 XLSX, XLS, ODS, SYLK, and DBF exports are known to be mangled.
 
-[This is a known NativeScript bug](https://github.com/NativeScript/NativeScript/issues/9586)
+This is a known NativeScript bug.
 
 This demo will focus on ASCII CSV files.  Once the bug is resolved, XLSX and
 other formats will be supported.
diff --git a/docz/docs/03-demos/03-desktop.md b/docz/docs/03-demos/03-desktop.md
index a6e7619..10e3636 100644
--- a/docz/docs/03-demos/03-desktop.md
+++ b/docz/docs/03-demos/03-desktop.md
@@ -142,7 +142,7 @@ This demo was tested against Electron 19.0.5 on an Intel Mac (`darwin-x64`).
 <details><summary><b>Complete Example</b> (click to show)</summary>
 
 This demo includes a drag-and-drop box as well as a file input box, mirroring
-the [SheetJS Data Preview Live Demo](http://oss.sheetjs.com/sheetjs/)
+the [SheetJS Data Preview Live Demo](https://oss.sheetjs.com/sheetjs/)
 
 The core data in this demo is an editable HTML table.  The readers build up the
 table using `sheet_to_html` (with `editable:true` option) and the writers scrape
@@ -336,7 +336,7 @@ backwards compatibility multiple times.  A summary of changes is noted below.
 
 Electron 6.x changed the `dialog` API. Methods like `showSaveDialog` originally
 returned an array of strings, but now returns a `Promise`.  This change was not
-documented. [Electron issue](https://github.com/electron/electron/issues/24438)
+documented.
 
 Electron 9.0.0 and later require the preference `nodeIntegration: true` in order
 to `require('xlsx')` in the renderer process.
@@ -1381,7 +1381,7 @@ At the time of writing, the latest supported React Native version was `v0.64.3`
 NodeJS `v16` is required.  There are OS-specific tools for downgrading:
 
 - [`nvm-windows`](https://github.com/coreybutler/nvm-windows/releases) Windows
-- [`nvm`](https://github.com/nvm-sh/nvm/) Linux, MacOS, WSL, etc.
+- [`n`](https://github.com/tj/n/) Linux, MacOS, WSL, etc.
 
 :::
 
diff --git a/docz/docs/03-demos/05-database.md b/docz/docs/03-demos/05-database.md
index 9f23960..05b1120 100644
--- a/docz/docs/03-demos/05-database.md
+++ b/docz/docs/03-demos/05-database.md
@@ -275,9 +275,9 @@ export as XLSX.
 <Tabs>
   <TabItem value="nodejs" label="NodeJS">
 
-[The `better-sqlite3` module](https://www.npmjs.com/package/better-sqlite3)
-provides a very simple API for working with SQLite databases.  `Statement#all`
-runs a prepared statement and returns an array of JS objects.
+The **`better-sqlite3`** module provides a very simple API for working with
+SQLite databases. `Statement#all` runs a prepared statement and returns an array
+of JS objects.
 
 1) Install the dependencies:
 
@@ -440,7 +440,7 @@ WebSQL was a popular SQL-based in-browser database available on Chrome.  In
 practice, it is powered by SQLite, and most simple SQLite-compatible queries
 work as-is in WebSQL.
 
-The public demo <http://sheetjs.com/sql> generates a database from workbook.
+The public demo <https://sheetjs.com/sql> generates a database from workbook.
 
 Importing data from spreadsheets is straightforward using the `generate_sql`
 helper function from ["Building Schemas"](#building-schemas-from-worksheets):
@@ -783,7 +783,7 @@ MongoDB is a popular document-oriented database engine.
 It is straightforward to treat collections as worksheets.  Each object maps to
 a row in the table.
 
-The official NodeJS connector is [`mongodb` on NPM](https://npm.im/mongodb).
+The official NodeJS connector is **`mongodb`**.
 
 Worksheets can be generated from collections by using `Collection#find`.  A
 `projection` can suppress the object ID field:
diff --git a/docz/docs/03-demos/07-worker.md b/docz/docs/03-demos/07-worker.md
new file mode 100644
index 0000000..5d1f50d
--- /dev/null
+++ b/docz/docs/03-demos/07-worker.md
@@ -0,0 +1,219 @@
+---
+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}}/>
+  </> );
+}
+```
\ No newline at end of file
diff --git a/docz/docs/03-demos/11-angular.md b/docz/docs/03-demos/11-angular.md
index a7668ba..77d02e2 100644
--- a/docz/docs/03-demos/11-angular.md
+++ b/docz/docs/03-demos/11-angular.md
@@ -230,7 +230,7 @@ Google and the Angular team.
 
 The Angular tooling provides no easy way to switch between versions!
 
-[This is a known Angular problem](https://github.com/angular/angular-cli/issues/9047)
+This is a known Angular problem.
 
 To work around this, [`SheetJSAngular.zip`](pathname:///angular/SheetJSAngular.zip)
 is a skeleton project designed to play nice with each Angular version.
diff --git a/docz/docs/03-demos/19-bundler.md b/docz/docs/03-demos/19-bundler.md
index 2c11196..8278ea3 100644
--- a/docz/docs/03-demos/19-bundler.md
+++ b/docz/docs/03-demos/19-bundler.md
@@ -349,9 +349,8 @@ Parcel should play nice with SheetJS out of the box.
 
 :::warning Parcel Bug
 
-Errors of the form `Could not statically evaluate fs call` stem from a
-[parcel bug](https://github.com/parcel-bundler/parcel/pull/523). Upgrade to
-Parcel version 1.5.0 or later.
+Errors of the form `Could not statically evaluate fs call` stem from a Parcel
+bug. Upgrade to Parcel version 1.5.0 or later.
 
 :::
 
diff --git a/docz/docs/03-demos/33-localfile.md b/docz/docs/03-demos/33-localfile.md
index 5222255..6e11b9f 100644
--- a/docz/docs/03-demos/33-localfile.md
+++ b/docz/docs/03-demos/33-localfile.md
@@ -68,24 +68,6 @@ const u8 = XLSX.write(workbook, { type: "buffer", bookType: "xlsx" });
 const blob = new Blob([u8], { type: "application/vnd.ms-excel" });
 ```
 
-## Web Workers
-
-:::warning
-
-**None of the browser methods work from Web Worker contexts!**
-
-Data operations with the Web APIs must happen in the browser main thread.
-
-:::
-
-Web Workers and main thread can transfer `ArrayBuffer` or `Uint8Array` objects.
-
-When generating a file, the worker will call `XLSX.write` with type `buffer`
-and transfer the result to the main thread to initiate a download.
-
-When parsing a file, the main thread will use the web API to read a `File` or
-`Blob`, extract the underlying `ArrayBuffer` and transfer to the Web Worker.
-
 ## HTML5 Download Attribute
 
 _Writing Files_
diff --git a/docz/docs/03-demos/45-git.md b/docz/docs/03-demos/45-git.md
index 545342e..f3b4f92 100644
--- a/docz/docs/03-demos/45-git.md
+++ b/docz/docs/03-demos/45-git.md
@@ -176,7 +176,7 @@ import * as XLSX from 'https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs';
 ```
 
 The official registry endpoint <https://deno.land/x/sheetjs> is out of date.
-[This is a known registry bug](https://github.com/denoland/dotland/issues/2072)
+This is a known registry bug.
 
 :::
 
diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md
index 60c8c31..6c42e7a 100644
--- a/docz/docs/03-demos/index.md
+++ b/docz/docs/03-demos/index.md
@@ -12,11 +12,12 @@ run in the web browser, demos will include interactive examples.
 
 - [`XMLHttpRequest and fetch`](./network)
 - [`Clipboard Data`](./clipboard)
+- [`Web Workers`](./worker)
 - [`Typed Arrays for Machine Learning`](./ml)
+- [`Local File Access`](./localfile)
 - [`LocalStorage and SessionStorage`](./database#localstorage-and-sessionstorage)
 - [`Web SQL Database`](./database#websql)
 - [`IndexedDB`](./database#indexeddb)
-- [`Local File Access`](./localfile)
 
 ### Frameworks
 
@@ -36,6 +37,7 @@ run in the web browser, demos will include interactive examples.
 - [`vue3-table-lite`](./grid#vue3-table-lite)
 - [`angular-ui-grid`](./grid#angular-ui-grid)
 - [`material ui`](./grid#material-ui-table)
+
 ### Platforms and Integrations
 
 - [`Command-Line Tools`](./cli)
diff --git a/docz/docs/06-solutions/01-input.md b/docz/docs/06-solutions/01-input.md
index 0c038d9..05ef799 100644
--- a/docz/docs/06-solutions/01-input.md
+++ b/docz/docs/06-solutions/01-input.md
@@ -301,7 +301,7 @@ The [`oldie` demo](../demos/legacy#internet-explorer) shows an IE-compatible fal
   <TabItem value="nodejs" label="NodeJS">
 
 `read` can accept a NodeJS buffer.  `readFile` can read files generated by a
-HTTP POST request body parser like [`formidable`](https://npm.im/formidable):
+HTTP POST request body parser like **`formidable`**:
 
 ```js
 const XLSX = require("xlsx");
@@ -444,7 +444,7 @@ const workbook = XLSX.read(data);
 
 For broader compatibility, third-party modules are recommended.
 
-[`request`](https://npm.im/request) requires a `null` encoding to yield Buffers:
+**`request`** requires a `null` encoding to yield Buffers:
 
 ```js
 var XLSX = require("xlsx");
@@ -458,7 +458,7 @@ request({url: url, encoding: null}, function(err, resp, body) {
 });
 ```
 
-[`axios`](https://axios-http.com/) works the same way in browser and in NodeJS:
+**`axios`** works the same way in browser and in NodeJS:
 
 ```js
 const XLSX = require("xlsx");
@@ -861,7 +861,7 @@ chrome.runtime.onMessage.addListener(function(msg, sender, cb) {
   <summary><b>NodeJS HTML Tables without a browser</b> (click to show)</summary>
 
 NodeJS does not include a DOM implementation and Puppeteer requires a hefty
-Chromium build.  [`jsdom`](https://npm.im/jsdom) is a lightweight alternative:
+Chromium build.  **`jsdom`** is a lightweight alternative:
 
 ```js
 const XLSX = require("xlsx");
diff --git a/docz/docs/08-api/index.md b/docz/docs/08-api/index.md
index 4bbb7cb..25c09de 100644
--- a/docz/docs/08-api/index.md
+++ b/docz/docs/08-api/index.md
@@ -10,9 +10,9 @@ title: API Reference
 
 `XLSX.version` is the version of the library (added by the build script).
 
-`XLSX.SSF` is an embedded version of the [format library](https://github.com/SheetJS/sheetjs/tree/master/packages/ssf).
+`XLSX.SSF` is an embedded version of the [format library](https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/packages/ssf).
 
-`XLSX.CFB` is an embedded version of the [container library](https://github.com/sheetjs/js-cfb).
+`XLSX.CFB` is an embedded version of the [container library](https://git.sheetjs.com/sheetjs/js-cfb).
 
 ## Parsing functions
 
diff --git a/docz/docs/09-miscellany/03-source.md b/docz/docs/09-miscellany/03-source.md
index 5560dcd..69f7706 100644
--- a/docz/docs/09-miscellany/03-source.md
+++ b/docz/docs/09-miscellany/03-source.md
@@ -6,7 +6,14 @@ hide_table_of_contents: true
 
 The official source code repository is <https://git.sheetjs.com/sheetjs/sheetjs>
 
-Mirrors:
+:::note Mirrors
+
+Older snapshots of the source code repository are available at various hosts:
 
 - [GitHub](https://github.com/sheetjs/sheetjs)
 - [GitLab](https://gitlab.com/sheetjs/sheetjs)
+- [BitBucket](https://bitbucket.org/sheetjs/sheetjs)
+
+<https://git.sheetjs.com/sheetjs/sheetjs> is the authoritative repository.
+
+:::
\ No newline at end of file
diff --git a/docz/docs/09-miscellany/05-contributing.md b/docz/docs/09-miscellany/05-contributing.md
index e1bfa41..1a907d2 100644
--- a/docz/docs/09-miscellany/05-contributing.md
+++ b/docz/docs/09-miscellany/05-contributing.md
@@ -5,7 +5,7 @@ sidebar_position: 5
 # Contributing
 
 Due to the precarious nature of the Open Specifications Promise, it is very
-important to ensure code is cleanroom.  [Contribution Notes](https://raw.githubusercontent.com/SheetJS/sheetjs/master/CONTRIBUTING.md)
+important to ensure code is cleanroom.  [Contribution Notes](https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/CONTRIBUTING.md)
 
 <details>
   <summary><b>File organization</b> (click to show)</summary>
diff --git a/docz/static/live/fetch.html b/docz/static/live/fetch.html
new file mode 100644
index 0000000..ca5cb0a
--- /dev/null
+++ b/docz/static/live/fetch.html
@@ -0,0 +1,23 @@
+<body>
+  <style>TABLE { border-collapse: collapse; } TD { border: 1px solid; }</style>
+  <div id="tavolo"></div>
+  <script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
+  <script type="text/javascript">
+(async() => {
+  /* fetch and parse workbook -- see the fetch example for details */
+  const workbook = XLSX.read(await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer());
+
+  let output = [];
+  /* loop through the worksheet names in order */
+  workbook.SheetNames.forEach(name => {
+    /* generate HTML from the corresponding worksheets */
+    const worksheet = workbook.Sheets[name];
+    const html = XLSX.utils.sheet_to_html(worksheet);
+    /* add a header with the title name followed by the table */
+    output.push(`<H3>${name}</H3>${html}`);
+  });
+  /* write to the DOM at the end */
+  tavolo.innerHTML = output.join("\n");
+})();
+  </script>
+</body>
diff --git a/docz/static/react/fetch.html b/docz/static/react/fetch.html
new file mode 100644
index 0000000..8d0b40a
--- /dev/null
+++ b/docz/static/react/fetch.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!-- sheetjs (C) 2013-present  SheetJS http://sheetjs.com -->
+<!-- vim: set ts=2: -->
+<html lang="en" style="height: 100%">
+<body>
+<style>TABLE { border-collapse: collapse; } TD { border: 1px solid; }</style>
+<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
+<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
+<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
+<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
+<div id="root"></div>
+
+<script type="text/babel">
+function Titel(props) { return ( <h3>{props.name}</h3> ); }
+
+function Tabeller(props) {
+  /* the workbook object is the state */
+  const [workbook, setWorkbook] = React.useState(XLSX.utils.book_new());
+
+  React.useEffect(() => { (async() => {
+    /* fetch and parse workbook -- see the fetch example for details */
+    const wb = XLSX.read(await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer());
+    setWorkbook(wb);
+  })(); });
+
+  return workbook.SheetNames.map((name, idx) => ( <div key={idx}>
+    <Titel name={name} />
+    <div dangerouslySetInnerHTML={{
+      /* this __html mantra is needed to set the inner HTML */
+      __html: XLSX.utils.sheet_to_html(workbook.Sheets[name])
+    }} />
+  </div>));
+}
+
+ReactDOM.render( <Tabeller/> , root );
+</script>
+</body>
+</html>
diff --git a/docz/static/vue/fetch.html b/docz/static/vue/fetch.html
new file mode 100644
index 0000000..b9f7690
--- /dev/null
+++ b/docz/static/vue/fetch.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<!-- sheetjs (C) 2013-present  SheetJS http://sheetjs.com -->
+<!-- vim: set ts=2: -->
+<html lang="en" style="height: 100%">
+<body>
+<style>TABLE { border-collapse: collapse; } TD { border: 1px solid; }</style>
+<script type="importmap">{
+  "imports": {
+    "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
+    "xlsx": "https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs"
+  }
+}</script>
+<div id="root"></div>
+
+<script type="module">
+import { createApp, reactive } from 'vue';
+import { read, utils } from 'xlsx';
+
+const S5SComponent = {
+  mounted() { (async() => {
+    /* fetch and parse workbook -- see the fetch example for details */
+    const workbook = read(await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer());
+    /* loop through the worksheet names in order */
+    workbook.SheetNames.forEach(name => {
+      /* generate HTML from the corresponding worksheets */
+      const html = utils.sheet_to_html(workbook.Sheets[name]);
+      /* add to state */
+      this.wb.wb.push({ name, html });
+    });
+  })(); },
+  /* this state mantra is required for array updates to work */
+  setup() { return { wb: reactive({ wb: [] }) }; },
+  template: `
+  <div v-for="ws in wb.wb" :key="ws.name">
+    <h3>{{ ws.name }}</h3>
+    <div v-html="ws.html"></div>
+  </div>`
+};
+
+createApp(S5SComponent).mount('#root');
+</script>
+</body>
+</html>