diff --git a/docz/docs/04-getting-started/03-demos/09-bundler.md b/docz/docs/04-getting-started/03-demos/09-bundler.md
index db1a7e0..c92870a 100644
--- a/docz/docs/04-getting-started/03-demos/09-bundler.md
+++ b/docz/docs/04-getting-started/03-demos/09-bundler.md
@@ -771,6 +771,194 @@ Click on "Click here to export" to generate a file.
+## SystemJS
+
+With configuration, SystemJS supports both browser and NodeJS deployments.
+
+:::caution
+
+This demo was written against SystemJS 0.19, the most popular SystemJS version.
+used with Angular applications. In the years since the release, Angular and
+other tools using SystemJS have switched to Webpack.
+
+:::
+
+
+
+
+SystemJS fails by default because the library does not export anything in the
+web browser. The `meta` configuration option can be used to expose `XLSX`:
+
+```js
+SystemJS.config({
+ meta: {
+ 'xlsx': {
+ exports: 'XLSX' // <-- tell SystemJS to expose the XLSX variable
+ }
+ },
+ map: {
+ 'xlsx': 'https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js',
+ 'fs': '', // <--|
+ 'crypto': '', // <--| suppress native node modules
+ 'stream': '' // <--|
+ }
+});
+SystemJS.import('main.js'); // load `main.js`
+```
+
+The `main.js` script can freely `require("xlsx")`.
+
+:::caution Web Workers
+
+Web Workers can load the SystemJS library with `importScripts`, but the imported
+code cannot assign the original worker's `onmessage` callback. The recommended
+approach is to expose a global from the required script, For example, supposing
+the shared name is `_cb`, the primary worker script would call the callback:
+
+```js title="worker.js"
+/* main worker script */
+importScripts('system.js');
+
+SystemJS.config({ /* ... browser config ... */ });
+
+onmessage = function(evt) {
+ SystemJS.import('workermain.js').then(function() { _cb(evt); });
+};
+```
+
+The worker script would define and expose the function:
+
+```js title="workermain.js"
+/* Loaded with SystemJS import */
+var XLSX = require('xlsx');
+
+_cb = function(evt) { /* ... do work here ... */ };
+```
+
+:::
+
+
+
+
+:::caution
+
+While SystemJS works in NodeJS, the built-in `require` should be preferred.
+
+:::
+
+The NodeJS module entrypoint is `xlsx/xlsx.js` and should be mapped:
+
+```js
+SystemJS.config({
+ map: {
+ "xlsx": "./node_modules/xlsx/xlsx.js"
+ }
+});
+```
+
+The standalone scripts require a hint that the script assigns a global:
+
+```js
+SystemJS.config({
+ meta: {
+ "standalone": { format: "global" }
+ },
+ map: {
+ "standalone": "xlsx.full.min.js"
+ }
+});
+```
+
+
+
+Complete Example (click to show)
+
+
+
+
+The [Live demo](pathname:///systemjs/systemjs.html) loads SystemJS from the
+CDN, uses it to load the standalone script from the SheetJS CDN and emulate
+a `require` implementation when loading [`main.js`](pathname:///systemjs/main.js)
+
+"View Source" works on the main HTML page and the `main.js` script.
+
+
+
+
+1) Install the dependencies:
+
+
{`\
+$ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz systemjs@0.19`}
+
+
+2) Save the following script to `SheetJSystem.js`:
+
+```js title="SheetJSystem.js"
+const SystemJS = require('systemjs');
+
+// highlight-start
+SystemJS.config({
+ map: {
+ 'xlsx': 'node_modules/xlsx/xlsx.js',
+ 'fs': '@node/fs',
+ 'crypto': '@node/crypto',
+ 'stream': '@node/stream'
+ }
+});
+// highlight-end
+
+SystemJS.import('xlsx').then(async function(XLSX) {
+
+ /* fetch JSON data and parse */
+ const url = "https://sheetjs.com/executive.json";
+ const raw_data = await (await fetch(url)).json();
+
+ /* filter for the Presidents */
+ const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
+
+ /* flatten objects */
+ const rows = prez.map(row => ({
+ name: row.name.first + " " + row.name.last,
+ birthday: row.bio.birthday
+ }));
+
+ /* generate worksheet and workbook */
+ const worksheet = XLSX.utils.json_to_sheet(rows);
+ const workbook = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
+
+ /* fix headers */
+ XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
+
+ /* calculate column width */
+ const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
+ worksheet["!cols"] = [ { wch: max_width } ];
+
+ /* create an XLSX file and try to save to Presidents.xlsx */
+ XLSX.writeFile(workbook, "Presidents.xlsx");
+
+});
+```
+
+3) Run in NodeJS:
+
+```bash
+node SheetJSystem.js
+```
+
+If the demo worked, `Presidents.xlsx` will be created.
+
+:::note
+
+As it uses `fetch`, this demo requires Node 18.
+
+:::
+
+
+
+
+
+
## Vite
:::caution
diff --git a/docz/static/systemjs/main.js b/docz/static/systemjs/main.js
new file mode 100644
index 0000000..8bfe87a
--- /dev/null
+++ b/docz/static/systemjs/main.js
@@ -0,0 +1,129 @@
+/* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
+/*jshint browser:true */
+/*global XLSX */
+var XLSX = require('xlsx');
+
+var global_wb;
+
+var process_wb = (function() {
+ var OUT = document.getElementById('out');
+ var HTMLOUT = document.getElementById('htmlout');
+
+ var get_format = (function() {
+ var radios = document.getElementsByName( "format" );
+ return function() {
+ for(var i = 0; i < radios.length; ++i) if(radios[i].checked || radios.length === 1) return radios[i].value;
+ };
+ })();
+
+ var to_json = function to_json(workbook) {
+ var result = {};
+ workbook.SheetNames.forEach(function(sheetName) {
+ var roa = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]);
+ if(roa.length) result[sheetName] = roa;
+ });
+ return JSON.stringify(result, 2, 2);
+ };
+
+ var to_csv = function to_csv(workbook) {
+ var result = [];
+ workbook.SheetNames.forEach(function(sheetName) {
+ var csv = XLSX.utils.sheet_to_csv(workbook.Sheets[sheetName]);
+ if(csv.length){
+ result.push("SHEET: " + sheetName);
+ result.push("");
+ result.push(csv);
+ }
+ });
+ return result.join("\n");
+ };
+
+ var to_fmla = function to_fmla(workbook) {
+ var result = [];
+ workbook.SheetNames.forEach(function(sheetName) {
+ var formulae = XLSX.utils.get_formulae(workbook.Sheets[sheetName]);
+ if(formulae.length){
+ result.push("SHEET: " + sheetName);
+ result.push("");
+ result.push(formulae.join("\n"));
+ }
+ });
+ return result.join("\n");
+ };
+
+ var to_html = function to_html(workbook) {
+ HTMLOUT.innerHTML = "";
+ workbook.SheetNames.forEach(function(sheetName) {
+ var htmlstr = XLSX.write(workbook, {sheet:sheetName, type:'string', bookType:'html'});
+ HTMLOUT.innerHTML += htmlstr;
+ });
+ return "";
+ };
+
+ return function process_wb(wb) {
+ global_wb = wb;
+ var output = "";
+ switch(get_format()) {
+ case "form": output = to_fmla(wb); break;
+ case "html": output = to_html(wb); break;
+ case "json": output = to_json(wb); break;
+ default: output = to_csv(wb);
+ }
+ if(OUT.innerText === undefined) OUT.textContent = output;
+ else OUT.innerText = output;
+ if(typeof console !== 'undefined') console.log("output", new Date());
+ };
+})();
+
+var setfmt = window.setfmt = function setfmt() { if(global_wb) process_wb(global_wb); };
+
+var b64it = window.b64it = (function() {
+ var tarea = document.getElementById('b64data');
+ return function b64it() {
+ if(typeof console !== 'undefined') console.log("onload", new Date());
+ var wb = XLSX.read(tarea.value, {type:'base64', WTF:false});
+ process_wb(wb);
+ };
+})();
+
+var do_file = (function() {
+ return function do_file(files) {
+ var f = files[0];
+ var reader = new FileReader();
+ reader.onload = function(e) {
+ if(typeof console !== 'undefined') console.log("onload", new Date());
+ var data = e.target.result;
+ data = new Uint8Array(data);
+ process_wb(XLSX.read(data, {type: 'array'}));
+ };
+ reader.readAsArrayBuffer(f);
+ };
+})();
+
+(function() {
+ var drop = document.getElementById('drop');
+ if(!drop.addEventListener) return;
+
+ function handleDrop(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ do_file(e.dataTransfer.files);
+ }
+
+ function handleDragover(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ e.dataTransfer.dropEffect = 'copy';
+ }
+
+ drop.addEventListener('dragenter', handleDragover, false);
+ drop.addEventListener('dragover', handleDragover, false);
+ drop.addEventListener('drop', handleDrop, false);
+})();
+
+(function() {
+ var xlf = document.getElementById('xlf');
+ if(!xlf.addEventListener) return;
+ function handleFile(e) { do_file(e.target.files); }
+ xlf.addEventListener('change', handleFile, false);
+})();
diff --git a/docz/static/systemjs/systemjs.html b/docz/static/systemjs/systemjs.html
new file mode 100644
index 0000000..18a4288
--- /dev/null
+++ b/docz/static/systemjs/systemjs.html
@@ -0,0 +1,77 @@
+
+
+
+
+