Compare commits

..

10 Commits

Author SHA1 Message Date
TomDo1234
85b6afe901 Removed 14 footnote 2024-04-03 18:38:06 +11:00
TomDo1234
be1192f0d3 Done with worker doc 2024-04-03 18:22:39 +11:00
TomDo1234
46523eb75a Done 2 2024-04-03 17:47:53 +11:00
TomDo1234
7f64cfa3c4 Done 2024-04-03 17:46:37 +11:00
TomDo1234
065a7d8cac Working on it 2024-04-03 16:57:36 +11:00
TomDo1234
0821d57f5a Finished with Workers, now on to R2 2024-04-03 15:56:14 +11:00
TomDo1234
6153655888 Done with the basics 2024-04-03 15:42:25 +11:00
TomDo1234
5a8d89bfba mention c3 2024-04-03 13:05:56 +11:00
TomDo1234
65ec4fb2e6 Working on CF page 2 2024-04-03 11:16:16 +11:00
TomDo1234
1aa1226316 Working on CF page 2024-04-03 11:12:07 +11:00
476 changed files with 6304 additions and 30811 deletions

2
.gitignore vendored

@ -4,5 +4,3 @@ package-lock.json
pnpm-lock.yaml
/docs
node_modules
.idea
.vscode

@ -20,10 +20,7 @@ sql
# Excel-related terms
A1
A1-Style
A2
A7
AutoFilter
B7
BIFF12
BIFF2
BIFF3
@ -44,17 +41,14 @@ FM3
FMT
FODS
FoxPro
Gmail
IEEE754
JSON
Macrosheet
Macrosheets
Multiplan
NodeMailer
ODF
ODS
OData
ORM
OpenDocument
OpenFormula
PRN
@ -115,7 +109,6 @@ tooltips
# Other terms
1.x
2.x
2FA
3.x
4.x
5.x
@ -123,27 +116,21 @@ tooltips
7.x
8.x
9.x
AArch64
APIs
APK
ARM64
ActiveX
Airtable
AlaSQL
AngularJS
AppleScript
ArrayBuffer
AstroJS
Auth
BOM
Base64
Base64-encoded
Big5
BitBucket
Blazor
Booleans
Browserify
BunJS
Bundlers
CDN
CEP
@ -151,105 +138,72 @@ CLI
CMS
CORS
CPAN
CRA
CRX
CS6
CTRL
CapacitorJS
Chakra
ChakraCore
CheerioJS
ClearScript
CocoaPods
CommonJS
Cordova
DOM
DPI
DanfoJS
DataFrame
DataGrid
Deno
DenoDOM
DexieJS
Dojo
Downloadify
Drash
Duktape
ERP
ES3
ES5
ES6
ESBuild
ESM
ETH
Eleventy
ElysiaJS
Endian
Ethercalc
ExecJS
ExpressJS
ExtendScript
FastifyJS
FerretDB
Fastify
FileReader
FileReaderSync
FileSaver
GBK
GTX
GatsbyJS
Ghidra
GitHub
GitLab
Goja
GraalJS
Gradle
GraphQL
GraphiQL
HTML
HTML5
HTTP
HTTPS
HappyDOM
Homebrew
HonoJS
IANA
IE
IE10
IE11
IE6
IE8
IE9
IMAP
InDesign
IndexedDB
Integrations
JDK
JE
JS
JSC
JSDOM
JSX
JWT
JavaScriptCore
Javet
JerryScript
Jint
Kaioken
Kaioponent
Kaioponents
KeyDB
KnexJS
Knex
KnockoutJS
LLC
LTS
LWC
LangChain
LangChainJS
Lifecycle
LocalStorage
LowDB
Lume
MUI
MVC
MVVM
MacOS
@ -257,9 +211,7 @@ MariaDB
Mathematica
Meridiem
MongoDB
MuJS
MySQL
NASM
NPM
NW.js
Nashorn
@ -267,7 +219,6 @@ NativeScript
NestJS
NetSuite
NeutralinoJS
Nexe
NextJS
NoSQL
NodeJS
@ -276,19 +227,14 @@ Nunjucks
Nuxt
NuxtJS
OSA
OpenSSL
OpenJDK
PPI
ParcelJS
PhantomJS
PhoneGap
Photoshop
Polars
PostgreSQL
PouchDB
PowerShell
Preact
PreactJS
QuickJS
R1
R2
@ -298,23 +244,15 @@ RDBMS
README
RESTlets
RSS
RTX
ReactJS
Redis
RequireJS
RhinoJS
Roadmap
Rollup
RollupJS
Ryzen
S3
SDK
SMS
SMTP
SQLite
SSG
SSL
SSR
SWC
SWF
Schemas
@ -323,38 +261,28 @@ SessionStorage
Shift-JIS
SlimerJS
Snowpack
Stata
SuiteScript
SuiteScripts
Suitelets
SvelteJS
SvelteKit
SystemJS
Tauri
Temurin
TensorFlow
UI
UI5
UNPKG
URI
UTF-16
UTF-8
UUID
UXP
Uint8Array
V2
V8
VBScript
VRAM
VSCodium
Valkey
Vendoring
Vercel
Vite
ViteJS
VueJS
VueJS-friendly
WASM
WMR
WSL
WebAssembly
@ -363,9 +291,7 @@ WebKit
WebSQL
Webpack
Win10
Win11
XHR
XMLDOM
XMLHttpRequest
XP
Xcode
@ -394,9 +320,7 @@ frontmatter
globals
iOS
iWork
jQuery
javascript
jujutsu
lifecycle
localForage
macOS
@ -405,13 +329,11 @@ microcontrollers
middleware
minified
minifier
mitigations
namespace
natively
nodejs
npm
parsers
polychotomous
pre-built
pre-generated
prepend
@ -432,7 +354,6 @@ transpile
transpiled
transpiling
uncheck
uncomment
unidimensional
unminified
unpkg

1
CNAME Normal file

@ -0,0 +1 @@
docs.sheetjs.com

@ -4,6 +4,7 @@ build:
cd docz; npx -y pnpm build; cd ..
rm -rf docs
mv docz/build/ docs
cp CNAME docs
cp _headers docs
.PHONY: init
@ -12,7 +13,7 @@ init:
.PHONY: dev
dev:
cd docz; npm run start -- --host=0.0.0.0 --no-open; cd ..
cd docz; npm run start -- --host=0.0.0.0; cd ..
.PHONY: serve
serve:

@ -21,75 +21,6 @@ $ make spell # spell check (.spelling custom dictionary)
$ make graph # build format graph and legend
```
### Documentation Markup
The original documentation used [GFM](https://github.github.com/gfm/).
Pages currently use MDX v2.
<details>
<summary><b>MDX Notes</b> (click to show)</summary>
**Multi-line tags**
Markdown and MDX v1 accept the following:
```
<details><summary><b>MDX Notes</b> (click to show)</summary>
Note
</details>
```
This is no longer valid in MDX v2. The `<summary>` part must be separated:
```
<details>
<summary><b>MDX Notes</b> (click to show)</summary>
Note
</details>
```
**Shortlinks**
Markdown and MDX v1 support shortlinks:
```
Scripts are available at <https://cdn.sheetjs.com>
```
This is no longer valid in MDX v2. Autolinks should be used:
```
Scripts are available at https://cdn.sheetjs.com
```
**Variables**
Patterns such as
```
<a href={`Foo${current}`}>Foo{current}</a>
```
do not work in MDX v2. Instead, string literals and concatenation must be used:
```
<a href={"Foo" + current + ""}>{"Foo" + current + ""}</a>
```
**Tables**
MDX inconsistently requires different indentation levels for `TD` / `TH`, `TR`,
`THEAD` / `TBODY` / `TFOOT`, and `TABLE` tags. Unconventional indentation is
intentional.
</details>
### Engine Compatibility Tables
`docz/src/data/engines.xls` is an XLML workbook that controls the compatibility
@ -139,12 +70,8 @@ function SheetJSTestDropbox() {
## Other Notes
`static/shim.js` shims the following functions:
- `Object.hasOwn`
`src/theme/Admonition` was swizzled from 3.2.1 to enable `pass` for hiding
`src/theme/Admonition` was swizzled from 2.4.1 to enable `pass` for hiding
header text. See Docusaurus issue 8568 for more details.
`src/theme/prism-include-languages.js` was swizzled from 3.2.1 to support the
`src/theme/prism-include-languages.js` was swizzled from 2.4.1 to support the
Liquid language. See Docusaurus issue 6872 for more details.

3
docz/README.md Normal file

@ -0,0 +1,3 @@
# docs.sheetjs.com
<https://docs.sheetjs.com/>

@ -1,13 +0,0 @@
import url from './engines.xls';
import React, { useEffect, useState } from 'react';
const BindingData = () => {
const [binding, setBinding] = useState("");
useEffect(() => { (async() => {
const html = await (await fetch(url)).json();
setBinding(html["Bindings"]);
})(); }, []);
return ( <p dangerouslySetInnerHTML={{__html: binding}}/> );
};
export default BindingData;

@ -1,13 +0,0 @@
import url from './cli.xls';
import React, { useEffect, useState } from 'react';
const FrameworkData = () => {
const [fw, setFW] = useState("");
useEffect(() => { (async() => {
const html = await (await fetch(url)).json();
setFW(html["Frameworks"]);
})(); }, []);
return ( <p dangerouslySetInnerHTML={{__html: fw}}/> );
};
export default FrameworkData;

@ -1,133 +0,0 @@
<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
<WindowHeight>10620</WindowHeight>
<WindowWidth>11020</WindowWidth>
<WindowTopX>2260</WindowTopX>
<WindowTopY>19600</WindowTopY>
<ActiveSheet>1</ActiveSheet>
<ProtectStructure>False</ProtectStructure>
<ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>
<Styles>
<Style ss:ID="Default" ss:Name="Normal">
<Alignment ss:Vertical="Bottom"/>
<Borders/>
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000"/>
<Interior/>
<NumberFormat/>
<Protection/>
</Style>
<Style ss:ID="s16">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#1C1E21"/>
</Style>
<Style ss:ID="s17">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000" ss:Bold="1"/>
</Style>
<Style ss:ID="s19">
<Alignment ss:Horizontal="Center" ss:Vertical="Bottom"/>
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000" ss:Bold="1"/>
</Style>
<Style ss:ID="s20">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#467886" ss:Underline="Single"/>
</Style>
</Styles>
<Worksheet ss:Name="Frameworks">
<Table ss:ExpandedColumnCount="7" ss:ExpandedRowCount="11" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="16">
<Column ss:Index="2" ss:Width="24"/>
<Column ss:Width="31"/>
<Column ss:Width="24"/>
<Column ss:Width="31"/>
<Column ss:Width="24"/>
<Column ss:Width="31"/>
<Row>
<Cell ss:StyleID="s17"><Data ss:Type="String"></Data></Cell>
<Cell ss:MergeAcross="1" ss:StyleID="s19"><Data ss:Type="String">MacOS</Data></Cell>
<Cell ss:MergeAcross="1" ss:StyleID="s19"><Data ss:Type="String">Windows</Data></Cell>
<Cell ss:MergeAcross="1" ss:StyleID="s19"><Data ss:Type="String">Linux</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s17"><Data ss:Type="String">Framework</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">x64</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">ARM</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">x64</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">ARM</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">x64</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">ARM</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/cli/nexe"><Data ss:Type="String">nexe</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/cli/boxednode"><Data ss:Type="String">boxednode</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/cli/pkg"><Data ss:Type="String">pkg</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/cli/nodesea#complete-example"><Data ss:Type="String">NodeJS SEA</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/cli/bunsea#complete-example"><Data ss:Type="String">BunJS SEA</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/cli/denosea#complete-example"><Data ss:Type="String">Deno SEA</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/cli/txiki"><Data ss:Type="String">txiki.js</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/v8#snapshots"><Data ss:Type="String">V8 Engine</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
</Table>
</Worksheet>
</Workbook>

@ -1,13 +0,0 @@
import url from './desktop.xls';
import React, { useEffect, useState } from 'react';
const FrameworkData = () => {
const [fw, setFW] = useState("");
useEffect(() => { (async() => {
const html = await (await fetch(url)).json();
setFW(html["Frameworks"]);
})(); }, []);
return ( <p dangerouslySetInnerHTML={{__html: fw}}/> );
};
export default FrameworkData;

@ -1,115 +0,0 @@
<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
<WindowHeight>10620</WindowHeight>
<WindowWidth>11020</WindowWidth>
<WindowTopX>2260</WindowTopX>
<WindowTopY>19600</WindowTopY>
<ActiveSheet>1</ActiveSheet>
<ProtectStructure>False</ProtectStructure>
<ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>
<Styles>
<Style ss:ID="Default" ss:Name="Normal">
<Alignment ss:Vertical="Bottom"/>
<Borders/>
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000"/>
<Interior/>
<NumberFormat/>
<Protection/>
</Style>
<Style ss:ID="s16">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#1C1E21"/>
</Style>
<Style ss:ID="s17">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000" ss:Bold="1"/>
</Style>
<Style ss:ID="s19">
<Alignment ss:Horizontal="Center" ss:Vertical="Bottom"/>
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000" ss:Bold="1"/>
</Style>
<Style ss:ID="s20">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#467886" ss:Underline="Single"/>
</Style>
</Styles>
<Worksheet ss:Name="Frameworks">
<Table ss:ExpandedColumnCount="7" ss:ExpandedRowCount="10" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="16">
<Column ss:Index="2" ss:Width="24"/>
<Column ss:Width="31"/>
<Column ss:Width="24"/>
<Column ss:Width="31"/>
<Column ss:Width="24"/>
<Column ss:Width="31"/>
<Row>
<Cell ss:StyleID="s17"><Data ss:Type="String"></Data></Cell>
<Cell ss:MergeAcross="1" ss:StyleID="s19"><Data ss:Type="String">MacOS</Data></Cell>
<Cell ss:MergeAcross="1" ss:StyleID="s19"><Data ss:Type="String">Windows</Data></Cell>
<Cell ss:MergeAcross="1" ss:StyleID="s19"><Data ss:Type="String">Linux</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s17"><Data ss:Type="String">Framework</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">x64</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">ARM</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">x64</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">ARM</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">x64</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">ARM</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/desktop/electron#complete-example"><Data ss:Type="String">Electron</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/desktop/nwjs#complete-example"><Data ss:Type="String">NW.js</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/desktop/wails#complete-example"><Data ss:Type="String">Wails</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/desktop/tauri#complete-example"><Data ss:Type="String">Tauri</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/desktop/neutralino#complete-example"><Data ss:Type="String">NeutralinoJS</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/desktop/reactnative"><Data ss:Type="String">React Native</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
<Cell ss:StyleID="s16"></Cell>
<Cell ss:StyleID="s16"></Cell>
</Row>
</Table>
</Worksheet>
</Workbook>

@ -1,13 +1,21 @@
import { read, utils } from 'xlsx';
import url from './engines.xls';
import React, { useEffect, useState } from 'react';
const EngineData = () => {
const [engines, setEngines] = useState("");
const [binding, setBinding] = useState("");
useEffect(() => { (async() => {
const html = await (await fetch(url)).json();
setEngines(html["Engines"]);
const wb = read(await (await fetch(url)).arrayBuffer(), { dense: true });
setEngines(utils.sheet_to_html(wb.Sheets["Engines"]));
setBinding(utils.sheet_to_html(wb.Sheets["Bindings"]));
})(); }, []);
return ( <p dangerouslySetInnerHTML={{__html: engines}}/> );
return ( <>
<p>The following engines have been tested in their native languages:</p>
<div dangerouslySetInnerHTML={{__html: engines}}/>
<p>The following bindings have been tested:</p>
<div dangerouslySetInnerHTML={{__html: binding}}/>
</> );
};
export default EngineData;

@ -1,6 +1,10 @@
<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40">
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
<WindowHeight>10620</WindowHeight>
<WindowWidth>11020</WindowWidth>
@ -20,21 +24,21 @@
<Protection/>
</Style>
<Style ss:ID="s16">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#1C1E21"/>
<Font ss:FontName="Arial" x:Family="Swiss" ss:Size="12" ss:Color="#1C1E21"/>
</Style>
<Style ss:ID="s17">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000" ss:Bold="1"/>
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000"
ss:Bold="1"/>
</Style>
<Style ss:ID="s19">
<Alignment ss:Horizontal="Center" ss:Vertical="Bottom"/>
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000" ss:Bold="1"/>
</Style>
<Style ss:ID="s20">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#467886" ss:Underline="Single"/>
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000"
ss:Bold="1"/>
</Style>
</Styles>
<Worksheet ss:Name="Engines">
<Table ss:ExpandedColumnCount="8" ss:ExpandedRowCount="18" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="16">
<Table ss:ExpandedColumnCount="8" ss:ExpandedRowCount="15" x:FullColumns="1"
x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="16">
<Column ss:Index="3" ss:Width="24"/>
<Column ss:Width="31"/>
<Column ss:Width="24"/>
@ -58,7 +62,7 @@
<Cell ss:StyleID="s17"><Data ss:Type="String">ARM</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/duktape#complete-example"><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">C</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
@ -68,7 +72,7 @@
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/v8#complete-example"><Data ss:Type="String">V8</Data></Cell>
<Cell><Data ss:Type="String">V8</Data></Cell>
<Cell><Data ss:Type="String">C++</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
@ -78,27 +82,17 @@
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/rhino#complete-example"><Data ss:Type="String">Rhino</Data></Cell>
<Cell><Data ss:Type="String">Rhino</Data></Cell>
<Cell><Data ss:Type="String">Java</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/jsc#complete-example"><Data ss:Type="String">JSC</Data></Cell>
<Cell><Data ss:Type="String">C++</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/jint#integration-example"><Data ss:Type="String">Jint</Data></Cell>
<Cell><Data ss:Type="String">Jint</Data></Cell>
<Cell><Data ss:Type="String">C#</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
@ -108,7 +102,7 @@
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/goja#complete-example"><Data ss:Type="String">Goja</Data></Cell>
<Cell><Data ss:Type="String">Goja</Data></Cell>
<Cell><Data ss:Type="String">Go</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
@ -118,17 +112,17 @@
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/nashorn#complete-example"><Data ss:Type="String">Nashorn</Data></Cell>
<Cell><Data ss:Type="String">Nashorn</Data></Cell>
<Cell><Data ss:Type="String">Java</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/quickjs#integration-example"><Data ss:Type="String">QuickJS</Data></Cell>
<Cell><Data ss:Type="String">QuickJS</Data></Cell>
<Cell><Data ss:Type="String">C</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
@ -138,27 +132,27 @@
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/hermes#integration-example"><Data ss:Type="String">Hermes</Data></Cell>
<Cell><Data ss:Type="String">Hermes</Data></Cell>
<Cell><Data ss:Type="String">C++</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/chakra#integration-example"><Data ss:Type="String">ChakraCore</Data></Cell>
<Cell><Data ss:Type="String">ChakraCore</Data></Cell>
<Cell><Data ss:Type="String">C++</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/boa#complete-example"><Data ss:Type="String">Boa</Data></Cell>
<Cell><Data ss:Type="String">Boa</Data></Cell>
<Cell><Data ss:Type="String">Rust</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
@ -168,54 +162,34 @@
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/perl#complete-example"><Data ss:Type="String">JE</Data></Cell>
<Cell><Data ss:Type="String">JE</Data></Cell>
<Cell><Data ss:Type="String">Perl</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/jerryscript#integration-example"><Data ss:Type="String">JerryScript</Data></Cell>
<Cell><Data ss:Type="String">JerryScript</Data></Cell>
<Cell><Data ss:Type="String">C</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/graaljs#complete-example"><Data ss:Type="String">GraalJS</Data></Cell>
<Cell><Data ss:Type="String">GraalJS</Data></Cell>
<Cell><Data ss:Type="String">Java</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/mujs#integration-example"><Data ss:Type="String">MuJS</Data></Cell>
<Cell><Data ss:Type="String">C</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✱</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/jurassic#integration-example"><Data ss:Type="String">Jurassic.NET</Data></Cell>
<Cell><Data ss:Type="String">C#</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"/>
</Row>
</Table>
<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">
@ -244,7 +218,8 @@
</WorksheetOptions>
</Worksheet>
<Worksheet ss:Name="Bindings">
<Table ss:ExpandedColumnCount="8" ss:ExpandedRowCount="20" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="16">
<Table ss:ExpandedColumnCount="8" ss:ExpandedRowCount="12" x:FullColumns="1"
x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="16">
<Column ss:Index="3" ss:Width="24"/>
<Column ss:Width="31"/>
<Column ss:Width="24"/>
@ -268,57 +243,47 @@
<Cell ss:StyleID="s17"><Data ss:Type="String">ARM</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/duktape#perl"><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">Perl</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/duktape#php"><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">PHP</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/duktape#python"><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">Python</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/duktape#rust"><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">Rust</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/duktape#zig"><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">Duktape</Data></Cell>
<Cell><Data ss:Type="String">Zig</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/v8#rust"><Data ss:Type="String">V8</Data></Cell>
<Cell><Data ss:Type="String">V8</Data></Cell>
<Cell><Data ss:Type="String">Rust</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
@ -328,62 +293,22 @@
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/v8#java"><Data ss:Type="String">V8</Data></Cell>
<Cell><Data ss:Type="String">Java</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/v8#c"><Data ss:Type="String">V8</Data></Cell>
<Cell><Data ss:Type="String">C#</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/v8#python"><Data ss:Type="String">V8</Data></Cell>
<Cell><Data ss:Type="String">Python</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/jsc#swift"><Data ss:Type="String">JSC</Data></Cell>
<Cell><Data ss:Type="String">JSC</Data></Cell>
<Cell><Data ss:Type="String">Swift</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/jsc#rust"><Data ss:Type="String">JSC</Data></Cell>
<Cell><Data ss:Type="String">Rust</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"/>
<Cell ss:StyleID="s16"/>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/rb#complete-example"><Data ss:Type="String">ExecJS</Data></Cell>
<Cell><Data ss:Type="String">ExecJS</Data></Cell>
<Cell><Data ss:Type="String">Ruby</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>

@ -1,13 +0,0 @@
import url from './mobile.xls';
import React, { useEffect, useState } from 'react';
const FrameworkData = () => {
const [fw, setFW] = useState("");
useEffect(() => { (async() => {
const html = await (await fetch(url)).json();
setFW(html["Frameworks"]);
})(); }, []);
return ( <p dangerouslySetInnerHTML={{__html: fw}}/> );
};
export default FrameworkData;

@ -1,165 +0,0 @@
<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
<WindowHeight>10620</WindowHeight>
<WindowWidth>11020</WindowWidth>
<WindowTopX>2260</WindowTopX>
<WindowTopY>19600</WindowTopY>
<ActiveSheet>1</ActiveSheet>
<ProtectStructure>False</ProtectStructure>
<ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>
<Styles>
<Style ss:ID="Default" ss:Name="Normal">
<Alignment ss:Vertical="Bottom"/>
<Borders/>
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000"/>
<Interior/>
<NumberFormat/>
<Protection/>
</Style>
<Style ss:ID="s16">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#1C1E21"/>
</Style>
<Style ss:ID="s17">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000" ss:Bold="1"/>
</Style>
<Style ss:ID="s19">
<Alignment ss:Horizontal="Center" ss:Vertical="Bottom"/>
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000" ss:Bold="1"/>
</Style>
<Style ss:ID="s20">
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#467886" ss:Underline="Single"/>
</Style>
</Styles>
<Worksheet ss:Name="Frameworks">
<Table ss:ExpandedColumnCount="8" ss:ExpandedRowCount="18" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="16">
<Column ss:Index="3" ss:Width="24"/>
<Column ss:Width="31"/>
<Column ss:Width="24"/>
<Column ss:Width="31"/>
<Column ss:Width="24"/>
<Column ss:Width="31"/>
<Row>
<Cell ss:StyleID="s17"><Data ss:Type="String"></Data></Cell>
<Cell ss:MergeAcross="1" ss:StyleID="s19"><Data ss:Type="String">Real Device</Data></Cell>
<Cell ss:MergeAcross="1" ss:StyleID="s19"><Data ss:Type="String">MacOS Sim</Data></Cell>
<Cell ss:MergeAcross="1" ss:StyleID="s19"><Data ss:Type="String">Windows Sim</Data></Cell>
<Cell ss:MergeAcross="1" ss:StyleID="s19"><Data ss:Type="String">Linux Sim</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s17"><Data ss:Type="String">Platform</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">iOS</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">Android</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">iOS</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">Android</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">iOS</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">Android</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">iOS</Data></Cell>
<Cell ss:StyleID="s17"><Data ss:Type="String">Android</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/mobile/reactnative"><Data ss:Type="String">React Native</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/mobile/nativescript"><Data ss:Type="String">NativeScript</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/mobile/capacitor"><Data ss:Type="String">CapacitorJS</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/mobile/ionic"><Data ss:Type="String">Ionic</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"> </Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/mobile/flutter"><Data ss:Type="String">Dart + Flutter</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"> </Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/mobile/quasar"><Data ss:Type="String">Quasar</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"> </Data></Cell>
</Row>
<Row>
<Cell ss:StyleID="s20" ss:HRef="/docs/demos/mobile/lynx"><Data ss:Type="String">Lynx</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String">✘</Data></Cell>
<Cell ss:StyleID="s16"><Data ss:Type="String"> </Data></Cell>
</Row>
</Table>
<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">
<PageSetup>
<Header x:Margin="0.3"/>
<Footer x:Margin="0.3"/>
<PageMargins x:Bottom="0.75" x:Left="0.7" x:Right="0.7" x:Top="0.75"/>
</PageSetup>
<FreezePanes/>
<FrozenNoSplit/>
<SplitHorizontal>2</SplitHorizontal>
<TopRowBottomPane>2</TopRowBottomPane>
<ActivePane>2</ActivePane>
<Panes>
<Pane>
<Number>3</Number>
</Pane>
<Pane>
<Number>2</Number>
<ActiveRow>12</ActiveRow>
<ActiveCol>5</ActiveCol>
</Pane>
</Panes>
<ProtectObjects>False</ProtectObjects>
<ProtectScenarios>False</ProtectScenarios>
</WorksheetOptions>
</Worksheet>
</Workbook>

@ -1,5 +1,4 @@
---
title: Standalone Browser Scripts
pagination_prev: getting-started/index
pagination_next: getting-started/examples/index
sidebar_position: 1
@ -10,13 +9,15 @@ sidebar_custom_props:
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
Each standalone release script is available at https://cdn.sheetjs.com/.
# Standalone Browser Scripts
Each standalone release script is available at <https://cdn.sheetjs.com/>.
<p>The current version is {current} and can be referenced as follows:</p>
<CodeBlock language="html">{`\
<!-- use version ${current} -->
<script type="text/javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>`}
<script lang="javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>`}
</CodeBlock>
:::tip pass
@ -27,55 +28,54 @@ new versions are released!
:::
:::danger pass
:::warning pass
A number of services host older versions of the SheetJS libraries. Due to
syncing issues, they are generally out of date.
**The SheetJS CDN** https://cdn.sheetjs.com/ **is the authoritative source**
**The SheetJS CDN** <https://cdn.sheetjs.com/> **is the authoritative source**
**for SheetJS scripts**
:::
## Browser Scripts
`xlsx.full.min.js` is the complete standalone script. It includes support for
`xlsx.full.min.js` is the complete standalone script. It includes support for
reading and writing many spreadsheet formats.
<CodeBlock language="html">{`\
<!-- use xlsx.full.min.js from version ${current} -->
<script type="text/javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>`}
<script lang="javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>`}
</CodeBlock>
`xlsx.mini.min.js` is a slimmer build that omits the following features:
- CSV and SYLK encodings (directly affecting users outside of the United States)
- XLSB / XLS / Lotus 1-2-3 / SpreadsheetML 2003 / Apple Numbers file formats
- [Stream utility functions](/docs/api/stream)
A slimmer build is generated at `dist/xlsx.mini.min.js`. Compared to full build:
- codepage library skipped (no support for XLS encodings)
- no support for XLSB / XLS / Lotus 1-2-3 / SpreadsheetML 2003 / Numbers
- node stream utils removed
<details>
<summary><b>How to integrate the mini build</b> (click to show)</summary>
<details><summary><b>How to integrate the mini build</b> (click to show)</summary>
A single script tag should be added at the top of the HTML page:
Replace references to `xlsx.full.min.js` with `xlsx.mini.min.js`. Starting from
scratch, a single script tag should be added at the top of the HTML page:
<CodeBlock language="html">{`\
<!-- use xlsx.mini.min.js from version ${current} -->
<script type="text/javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.mini.min.js"></script>`}
<script lang="javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.mini.min.js"></script>`}
</CodeBlock>
</details>
### Vendoring
For general stability, making a local copy of SheetJS scripts ("vendoring") is
strongly recommended. Vendoring decouples websites from SheetJS infrastructure.
For general stability, "vendoring" scripts is the recommended approach:
<ol start="1"><li><p>Download the script (<code parentName="pre">xlsx.full.min.js</code>) for
the desired version. The current version is available at <a href={"https://cdn.sheetjs.com/xlsx-" + current + "/package/dist/xlsx.full.min.js"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/package/dist/xlsx.full.min.js"}</a></p></li></ol>
<p>1) Download the script (<code parentName="pre">xlsx.full.min.js</code>) for
the desired version. The current version is available at <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>https://cdn.sheetjs.com/xlsx-{current}/package/dist/xlsx.full.min.js</a></p>
2) Move the script to a `public` folder with other scripts.
3) Reference the vendored script from HTML pages:
3) Reference the local script from HTML pages:
```html
<script src="/public/xlsx.full.min.js"></script>
@ -89,15 +89,16 @@ For broad compatibility with JavaScript engines, the library is written using
ECMAScript 3 language dialect. A "shim" script provides implementations of
functions for older browsers and environments.
Due to SSL compatibility issues, older versions of IE will not be able to use
the CDN scripts directly. They should be downloaded and saved to a public path:
Due to SSL compatibility issues, older versions of IE will not be able to
use the CDN scripts directly. They should be downloaded and saved to a public
directory in the site:
<ul>
<li>Standalone: <a href={"https://cdn.sheetjs.com/xlsx-" + current + "/package/dist/xlsx.mini.min.js"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/package/dist/xlsx.mini.min.js"}</a></li>
<li>Shim: <a href={"https://cdn.sheetjs.com/xlsx-" + current + "/package/dist/shim.min.js"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/package/dist/shim.min.js"}</a></li>
<li>Standalone: <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.mini.min.js`}>https://cdn.sheetjs.com/xlsx-{current}/package/dist/xlsx.mini.min.js</a></li>
<li>Shim: <a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js`}>https://cdn.sheetjs.com/xlsx-{current}/package/dist/shim.min.js</a></li>
</ul>
A `script` reference to the shim must be added before the standalone script:
Add a `script` reference to the shim before the standalone script:
```html
<!-- add the shim first -->
@ -116,79 +117,14 @@ importScripts("https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js"
importScripts("https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js");`}
</CodeBlock>
### Type Checker
:::danger VS Code Telemetry and Data Exfiltration
The official builds of Visual Studio Code ("VS Code" or "VSCode") embed
telemetry and send information to Microsoft servers.
**[VSCodium](https://vscodium.com/) is a telemetry-free fork of VS Code.**
When writing code that may process personally identifiable information (PII),
the SheetJS team strongly encourages building VS Code from source or using IDEs
that do not exfiltrate data.
:::
The type checker integrated in VSCodium and VS Code does not currently provide
type hints when using the standalone build. Using the JSDoc `@type` directive
coupled with type imports, VSCodium will recognize the types:
![VSCodium types](pathname:///files/standalone-types.png)
<ol start="1">
<li><p>Download the types (<code parentName="pre">index.d.ts</code>) for
the desired version. The current version is available at <a href={"https://cdn.sheetjs.com/xlsx-" + current + "/package/types/index.d.ts"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/package/types/index.d.ts"}</a></p></li>
</ol>
2) Rename the types file to `xlsx.d.ts`. It does not need to reside in the same
folder as the standalone script.
3) In the browser script referencing the global, prepend the following lines:
```js title="Prepend this fragment in each source file referencing the XLSX global"
/** @type {import("./xlsx")} */
const XLSX = globalThis.XLSX;
```
4) If the `xlsx.d.ts` file is in a different folder, change the argument to the
`import` method to reflect the relative path. For example, given the structure:
```text title="Folder Structure"
- /vendor
- /vendor/xlsx.ts
- /src
- /src/app.js
```
`/src/app.js` must refer to the types as `../vendor/xlsx`:
```js title="Preamble for /src/app.js when types are at /vendor/xlsx.d.ts"
// highlight-next-line
/** @type {import("../vendor/xlsx")} */
const XLSX = globalThis.XLSX;
```
The `.d.ts` file extension must be omitted.
:::warning pass
JSDoc types using the `@import` directive are not supported in `<script>` tags.
**This is a known bug with VS Code!**
:::
## ECMAScript Module Imports
:::info pass
:::caution pass
This section refers to imports in HTML pages using `<script type="module">`.
The ["Frameworks and Bundlers"](/docs/getting-started/installation/frameworks)
section covers imports in projects using bundlers (ViteJS) or frameworks
(Kaioken / ReactJS / Angular / VueJS / Svelte)
This section refers to imports using `script type="module"`. For imports in
modern projects using Webpack or React or Angular or VueJS, the installation is
described [in "Frameworks and Bundlers"](/docs/getting-started/installation/frameworks).
:::
@ -267,13 +203,14 @@ xport.addEventListener("click", async() => {
## Bower
:::danger pass
:::warning pass
Bower is deprecated and the maintainers recommend using other tools.
:::
The Bower package manager supports tarballs from the SheetJS CDN:
The Bower package manager plays nice with the CDN tarballs:
<CodeBlock language="bash">{`\
npx bower install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}

@ -1,10 +1,9 @@
---
title: Frameworks and Bundlers
pagination_prev: getting-started/index
pagination_next: getting-started/examples/index
sidebar_position: 2
sidebar_custom_props:
summary: Kaioken, Angular, React, VueJS, ViteJS, Webpack, etc.
summary: Angular, React, VueJS, Webpack, etc.
---
import current from '/version.js';
@ -12,11 +11,13 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
Each standalone release package is available at https://cdn.sheetjs.com/. The
NodeJS package is designed to be used with frameworks and bundlers. It is a
# Frameworks and Bundlers
Each standalone release package is available at <https://cdn.sheetjs.com/>. The
NodeJS package is designed to be used with frameworks and bundlers. It is a
proper ECMAScript Module release which can be optimized with developer tools.
<p><a href={"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}</a> is the URL for version {current}</p>
<p><a href={`https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}>https://cdn.sheetjs.com/xlsx-{current}/xlsx-{current}.tgz</a> is the URL for version {current}</p>
## Installation
@ -40,24 +41,6 @@ pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`
yarn remove xlsx
yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
:::caution pass
Newer releases of Yarn may throw an error:
```
Usage Error: It seems you are trying to add a package using a https:... url; we now require package names to be explicitly specified.
Try running the command again with the package name prefixed: yarn add my-package@https:...
```
The workaround is to prepend the URL with `xlsx@`:
<CodeBlock language="bash">{`\
yarn add xlsx@https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
:::
</TabItem>
</Tabs>
@ -67,7 +50,7 @@ Once installed, the library can be imported under the name `xlsx`:
import { read, writeFileXLSX } from "xlsx";
```
The ["Bundlers" demo](/docs/demos/frontend/bundler) includes complete examples.
The ["Bundlers" demo](/docs/demos/bundler) includes examples for specific tools.
:::tip pass
@ -85,7 +68,7 @@ Snyk security tooling may report errors involving "Prototype Pollution":
Prototype Pollution [Medium Severity][https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926]
```
As noted in the [Snyk report](https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926):
As noted in the [Snyk report](https://web.archive.org/web/20231129100639/https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926):
> The issue is resolved in version 0.19.3
@ -98,14 +81,14 @@ Until Snyk fixes the bugs, the official recommendation is to
### Legacy Endpoints
:::danger pass
:::warning pass
Older releases are technically available on the public npm registry as `xlsx`,
but the registry is out of date. The latest version on that registry is 0.18.5
This is a known registry bug
**The SheetJS CDN** https://cdn.sheetjs.com/ **is the authoritative source**
**The SheetJS CDN** <https://cdn.sheetjs.com/> **is the authoritative source**
**for SheetJS modules.**
For existing projects, the easiest approach is to uninstall and reinstall:
@ -148,8 +131,7 @@ in `package.json` can control module resolution:
### Vendoring
For general stability, making a local copy of SheetJS modules ("vendoring") is
strongly recommended. Vendoring decouples projects from SheetJS infrastructure.
For general stability, "vendoring" modules is the recommended approach:
0) Remove any existing dependency on a project named `xlsx`:
@ -171,33 +153,13 @@ yarn remove xlsx`}
</TabItem>
</Tabs>
<ol start="1"><li><p>Download the tarball (<code parentName="pre">xlsx-{current}.tgz</code>) for the desired version. The current
version is available at <a href={"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}</a></p></li></ol>
<p>1) Download the tarball (<code parentName="pre">xlsx-{current}.tgz</code>) for the desired version. The current
version is available at <a href={`https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}>https://cdn.sheetjs.com/xlsx-{current}/xlsx-{current}.tgz</a></p>
<CodeBlock language="bash">{`\
curl -O https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
2) Create a `vendor` subfolder at the root of your project and move the tarball
to that folder. Add it to your project repository.
2) Create a `vendor` subfolder at the root of your project:
```bash
mkdir -p vendor
```
3) Move the tarball from step (1) to the `vendor` folder:
<CodeBlock language="bash">{`\
mv xlsx-${current}.tgz vendor`}
</CodeBlock>
4) If the project is managed with a version control system, add the tarball to
the source repository. The Git VCS supports the `add` subcommand:
<CodeBlock language="bash">{`\
git add vendor/xlsx-${current}.tgz`}
</CodeBlock>
5) Install the tarball using a package manager:
3) Install the tarball using a package manager:
<Tabs groupId="pm">
<TabItem value="npm" label="npm">
@ -214,23 +176,6 @@ pnpm install --save file:vendor/xlsx-${current}.tgz`}
<CodeBlock language="bash">{`\
yarn add file:vendor/xlsx-${current}.tgz`}
</CodeBlock>
:::caution pass
Newer releases of Yarn may throw an error:
<CodeBlock language="text">{`\
Usage Error: The file:vendor/xlsx-${current}.tgz string didn't match the required format (package-name@range). Did you perhaps forget to explicitly reference the package name?`}
</CodeBlock>
The workaround is to prepend the URI with `xlsx@`:
<CodeBlock language="bash">{`\
yarn add xlsx@file:vendor/xlsx-${current}.tgz`}
</CodeBlock>
:::
</TabItem>
</Tabs>
@ -258,13 +203,13 @@ var XLSX = require("xlsx");
var read = XLSX.read, utils = XLSX.utils;
```
The ["Bundlers" demo](/docs/demos/frontend/bundler) includes complete examples.
The ["Bundlers" demo](/docs/demos/bundler) includes examples for specific tools.
### Dynamic Imports
Dynamic imports with `import()` will only download scripts when they are needed.
:::danger pass
:::warning pass
Dynamic `import` will always download the full contents of the imported scripts!
@ -281,8 +226,7 @@ import { utils, writeFileXLSX } from "xlsx";
export { utils, writeFileXLSX };
```
Bundlers will typically optimize the script and only add the requested features.
A dynamic import of the wrapper will load the optimized wrapper script:
A dynamic import of the wrapper script will only load the requested features:
```js
async function export_data() {

@ -12,9 +12,9 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
Package tarballs are available on https://cdn.sheetjs.com.
Package tarballs are available on <https://cdn.sheetjs.com>.
<p><a href={"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}</a> is the URL for version {current}</p>
<p><a href={`https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}>https://cdn.sheetjs.com/xlsx-{current}/xlsx-{current}.tgz</a> is the URL for version {current}</p>
## Installation
@ -38,24 +38,6 @@ pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`
yarn remove xlsx
yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
:::caution pass
Newer releases of Yarn may throw an error:
```
Usage Error: It seems you are trying to add a package using a https:... url; we now require package names to be explicitly specified.
Try running the command again with the package name prefixed: yarn add my-package@https:...
```
The workaround is to prepend the URL with `xlsx@`:
<CodeBlock language="bash">{`\
yarn add xlsx@https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
:::
</TabItem>
</Tabs>
@ -75,7 +57,7 @@ Snyk security tooling may report errors involving "Prototype Pollution":
Prototype Pollution [Medium Severity][https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926]
```
As noted in the [Snyk report](https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926):
As noted in the [Snyk report](https://web.archive.org/web/20231129100639/https://security.snyk.io/vuln/SNYK-JS-XLSX-5457926):
> The issue is resolved in version 0.19.3
@ -88,14 +70,14 @@ Until Snyk fixes the bugs, the official recommendation is to
### Legacy Endpoints
:::danger pass
:::warning pass
Older releases are technically available on the public npm registry as `xlsx`,
but the registry is out of date. The latest version on that registry is 0.18.5
This is a known registry bug
**The SheetJS CDN** https://cdn.sheetjs.com/ **is the authoritative source**
**The SheetJS CDN** <https://cdn.sheetjs.com/> **is the authoritative source**
**for SheetJS modules.**
For existing projects, the easiest approach is to uninstall and reinstall:
@ -138,8 +120,7 @@ in `package.json` can control module resolution:
### Vendoring
For general stability, making a local copy of SheetJS modules ("vendoring") is
strongly recommended. Vendoring decouples projects from SheetJS infrastructure.
For general stability, "vendoring" modules is the recommended approach:
0) Remove any existing dependency on a project named `xlsx`:
@ -161,33 +142,13 @@ yarn remove xlsx`}
</TabItem>
</Tabs>
<ol start="1"><li><p>Download the tarball (<code parentName="pre">xlsx-{current}.tgz</code>) for the desired version. The current
version is available at <a href={"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}</a></p></li></ol>
<p>1) Download the tarball (<code parentName="pre">xlsx-{current}.tgz</code>) for the desired version. The current
version is available at <a href={`https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}>https://cdn.sheetjs.com/xlsx-{current}/xlsx-{current}.tgz</a></p>
<CodeBlock language="bash">{`\
curl -O https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
2) Create a `vendor` subfolder at the root of your project and move the tarball
to that folder. Add it to your project repository.
2) Create a `vendor` subfolder at the root of your project:
```bash
mkdir -p vendor
```
3) Move the tarball from step (1) to the `vendor` folder:
<CodeBlock language="bash">{`\
mv xlsx-${current}.tgz vendor`}
</CodeBlock>
4) If the project is managed with a version control system, add the tarball to
the source repository. The Git VCS supports the `add` subcommand:
<CodeBlock language="bash">{`\
git add vendor/xlsx-${current}.tgz`}
</CodeBlock>
5) Install the tarball using a package manager:
3) Install the tarball using a package manager:
<Tabs groupId="pm">
<TabItem value="npm" label="npm">
@ -204,23 +165,6 @@ pnpm install --save file:vendor/xlsx-${current}.tgz`}
<CodeBlock language="bash">{`\
yarn add file:vendor/xlsx-${current}.tgz`}
</CodeBlock>
:::caution pass
Newer releases of Yarn may throw an error:
<CodeBlock language="text">{`\
Usage Error: The file:vendor/xlsx-${current}.tgz string didn't match the required format (package-name@range). Did you perhaps forget to explicitly reference the package name?`}
</CodeBlock>
The workaround is to prepend the URI with `xlsx@`:
<CodeBlock language="bash">{`\
yarn add xlsx@file:vendor/xlsx-${current}.tgz`}
</CodeBlock>
:::
</TabItem>
</Tabs>
@ -239,7 +183,7 @@ The package supports CommonJS `require` and ESM `import` module systems.
### CommonJS `require`
By default, the module supports `require` and it will automatically add support
for encodings, streams and file system access:
for streams and file system access:
```js
var XLSX = require("xlsx");
@ -251,66 +195,6 @@ The package also ships with `xlsx.mjs`, a script compatible with the ECMAScript
module system. When using the ESM build in NodeJS, some dependencies must be
loaded manually.
:::danger ECMAScript Module Limitations
The original ECMAScript module specification only supported top-level imports:
```js
import { Readable } from 'stream';
```
If a module is unavailable, there is no way for scripts to gracefully fail or
ignore the error. This presents an insurmountable challenge for libraries.
To contrast, the SheetJS CommonJS modules gracefully handle missing dependencies
since `require` failures are errors that the library can catch and handle.
---
Patches to the specification added two different solutions to the problem:
- "dynamic imports" will throw errors that can be handled by libraries. Dynamic
imports will taint APIs that do not use Promise-based methods.
```js
/* Readable will be undefined if stream cannot be imported */
const Readable = await (async() => {
try {
return (await import("stream"))?.Readable;
} catch(e) { /* silently ignore error */ }
})();
```
- "import maps" control module resolution, allowing library users to manually
shunt unsupported modules.
**These patches were released after browsers adopted ESM!** A number of browsers
and other platforms support top-level imports but do not support the patches.
---
For the ESM build, there were four unpalatable options:
A) Generate a module script for browsers, a module script for ViteJS, a module
script for [Deno](/docs/getting-started/installation/deno), and a module script
for NodeJS and [BunJS](/docs/getting-started/installation/bun).
B) Remove all optional features, including support for non-English legacy files.
C) Add all optional features, effectively making the features mandatory.
D) Introduce special methods for optional dependency injection.
The SheetJS team chose option (D). NodeJS native modules are still automatically
loaded in the CommonJS build, but NodeJS ESM scripts must now load and pass the
dependencies to the library using special methods.
---
**It is strongly recommended to use CommonJS in NodeJS scripts!**
:::
#### Filesystem Operations
The `set_fs` method accepts a `fs` instance for reading and writing files using
@ -327,7 +211,7 @@ XLSX.set_fs(fs);
#### Stream Operations
The `set_readable` method accepts a `stream.Readable` instance for use in stream
methods including [`XLSX.stream.to_csv`](/docs/api/stream):
methods such as `XLSX.stream.to_csv`:
```js
import * as XLSX from 'xlsx';
@ -353,7 +237,7 @@ XLSX.set_cptable(cpexcel);
#### NextJS
:::danger pass
:::warning pass
`fs` cannot be imported from the top level in NextJS pages. This will not work:
@ -366,8 +250,6 @@ import * as fs from 'fs'; // this import will fail
set_fs(fs);
```
**This is a design flaw in NextJS!**
:::
For server-side file processing, `fs` should be loaded with a dynamic import

@ -1,5 +1,4 @@
---
title: AMD (define)
pagination_prev: getting-started/index
pagination_next: getting-started/examples/index
sidebar_position: 4
@ -10,11 +9,13 @@ sidebar_custom_props:
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
Each standalone release script is available at https://cdn.sheetjs.com/.
# AMD (define)
Each standalone release script is available at <https://cdn.sheetjs.com/>.
`xlsx.full.min.js` supports AMD with name `xlsx` out of the box.
<p><a href={"https://cdn.sheetjs.com/xlsx-" + current + "/package/dist/xlsx.full.min.js"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/package/dist/xlsx.full.min.js"}</a> is the URL for {current}</p>
<p><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>https://cdn.sheetjs.com/xlsx-{current}/package/dist/xlsx.full.min.js</a> is the URL for {current}</p>
:::note pass
@ -64,74 +65,27 @@ define(['N/file', 'xlsx'], function(file, XLSX) {
**More details are included in the [NetSuite demo](/docs/demos/cloud/netsuite#installation)**
:::caution Oracle Bugs
[NetSuite users reported](https://git.sheetjs.com/sheetjs/sheetjs/issues/3097)
errors that stem from an Oracle issue. A sample error message is shown below.
```
Fail to evaluate script: com.netsuite.suitescript.scriptobject.GraalValueAdapter@68d0f09d
```
**This is a NetSuite bug. Only Oracle can fix the bug!**
It is strongly encouraged to escalate the issue with Oracle support.
NetSuite users have reported success with the following workaround:
1) Open the script in a text editor and search for `define(` in the code.
There will be exactly one instance:
```js
define("xlsx",function(){
```
Replace the `xlsx` with `sheetjs`:
```js
define("sheetjs",function(){
```
2) Use the new name in the JSON configuration:
```json title="JsLibraryConfig.json"
{
"paths": {
// highlight-next-line
"sheetjs": "/path/to/xlsx.full.min"
}
}
```
3) Use the new name in the array argument to the `define` function call:
```js title="SuiteScript"
/**
* @NApiVersion 2.x
* ... more options ...
// highlight-next-line
* @NAmdConfig ./JsLibraryConfig.json
*/
// highlight-next-line
define(['N/file', 'sheetjs'], function(file, XLSX) {
// ^^^^^^^ ^^^^
// new module name same variable
// ... use XLSX here ...
});
```
:::
## SAP UI5
OpenUI5 and SAPUI5 installation instructions are covered in the dedicated
["OpenUI5 / SAPUI5" demo](/docs/demos/frontend/openui5#installation).
After downloading the script, it can be uploaded to the UI5 project and loaded
in the `sap.ui.define` call:
SheetJS standalone scripts can be loaded in two ways:
```js
sap.ui.define([
/* ... other libraries ... */
"path/to/xlsx.full.min"
], function(/* ... variables for the other libraries ... */, XLSX) {
// use XLSX here
})
```
- [`sap.ui.define`](/docs/demos/frontend/openui5#installation-define)
- [HTML SCRIPT tag](/docs/demos/frontend/openui5#installation-html)
:::warning pass
**Copy and pasting code does not work** for SheetJS scripts as they contain
Unicode characters that may be mangled. The standalone script should be
downloaded and manually uploaded to the project.
:::
## RequireJS
@ -207,8 +161,8 @@ require([
#### Asynchronous Loading
When `async` is enabled, Dojo will only understand the name `xlsx`. `dojoConfig`
can map package names to scripts:
When `async` is enabled, Dojo will only understand the name `xlsx`. The config
object can map package names to scripts:
<CodeBlock language="html">{`\
<script>

@ -1,5 +1,4 @@
---
title: ExtendScript
pagination_prev: getting-started/index
pagination_next: getting-started/examples/index
sidebar_position: 5
@ -9,15 +8,13 @@ sidebar_custom_props:
import current from '/version.js';
ExtendScript is a dialect of JavaScript used in Photoshop and InDesign scripts.
# ExtendScript
Each standalone release script is available at https://cdn.sheetjs.com/.
Each standalone release script is available at <https://cdn.sheetjs.com/>.
`xlsx.extendscript.js` is a special ExtendScript-compatible build. The script is
carefully assembled to work around ExtendScript quirks. Due to bugs in various
JavaScript minifiers and tools, scripts cannot be compressed or post-processed.
`xlsx.extendscript.js` is an ExtendScript build for Photoshop and InDesign.
<p><a href={"https://cdn.sheetjs.com/xlsx-" + current + "/package/dist/xlsx.extendscript.js"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/package/dist/xlsx.extendscript.js"}</a> is the URL for {current}</p>
<p><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.extendscript.js`}>https://cdn.sheetjs.com/xlsx-{current}/package/dist/xlsx.extendscript.js</a> is the URL for {current}</p>
After downloading the script, it can be directly referenced with `#include`:
@ -41,15 +38,12 @@ path is application-specific.
| Photoshop | `\Presets\Scripts` within the Application folder |
| InDesign | Windows > Utilities > Scripts, click `☰` > "Reveal in Explorer" |
:::note CEP and UXP usage
:::note CEP usage
The ExtendScript build should be used when performing spreadsheet operations
from the host context (within a `jsx` script file).
**CEP**: [The standalone scripts](/docs/getting-started/installation/standalone)
should be added to CEP extension HTML.
**UXP**: [The standalone scripts](/docs/getting-started/installation/standalone)
can be loaded directly in UXP scripts using the `require` function.
[The standalone scripts](/docs/getting-started/installation/standalone) should
be added to CEP extension HTML.
:::

@ -1,5 +1,4 @@
---
title: Deno
pagination_prev: getting-started/index
pagination_next: getting-started/examples/index
sidebar_position: 6
@ -12,9 +11,9 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
Deno is a JavaScript runtime that can import scripts from URLs.
# Deno
Module scripts and type definitions are available at https://cdn.sheetjs.com/.
Module scripts and type definitions are available at <https://cdn.sheetjs.com/>.
Using the URL imports, `deno run` will automatically download scripts and types:
@ -23,8 +22,7 @@ Using the URL imports, `deno run` will automatically download scripts and types:
import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';`}
</CodeBlock>
The module URL is the ECMAScript Module build on the SheetJS CDN. `@deno-types`
instructs Deno to use the type definitions from the SheetJS CDN.
The `@deno-types` comment instructs Deno to use the type definitions.
:::caution Deno support is considered experimental.
@ -70,11 +68,11 @@ and the types URLs should be updated at the same time:
#### Deno Registry
:::danger pass
:::warning pass
The official Deno registry is out of date. This is a registry bug.
**The SheetJS CDN** https://cdn.sheetjs.com/ **is the authoritative source**
**The SheetJS CDN** <https://cdn.sheetjs.com/> **is the authoritative source**
**for SheetJS modules.**
:::

@ -4,7 +4,7 @@ pagination_prev: getting-started/index
pagination_next: getting-started/examples/index
sidebar_position: 7
sidebar_custom_props:
summary: Load NodeJS modules using CommonJS or ESM
summary: Load NodeJS-style modules using CommonJS or ESM
---
import current from '/version.js';
@ -12,9 +12,9 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
Package tarballs are available on https://cdn.sheetjs.com.
Package tarballs are available on <https://cdn.sheetjs.com>.
<p><a href={"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}</a> is the URL for version {current}</p>
<p><a href={`https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}>https://cdn.sheetjs.com/xlsx-{current}/xlsx-{current}.tgz</a> is the URL for version {current}</p>
:::caution Bun support is considered experimental.
@ -50,7 +50,8 @@ For general stability, "vendoring" modules is the recommended approach:
bun rm xlsx
```
<ol start="1"><li><p>Download the tarball (<code parentName="pre">xlsx-{current}.tgz</code>) for the desired version. The current version is available at <a href={"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}>{"https://cdn.sheetjs.com/xlsx-" + current + "/xlsx-" + current + ".tgz"}</a></p></li></ol>
<p>1) Download the tarball (<code parentName="pre">xlsx-{current}.tgz</code>) for the desired version. The current
version is available at <a href={`https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}>https://cdn.sheetjs.com/xlsx-{current}/xlsx-{current}.tgz</a></p>
2) Create a `vendor` subfolder at the root of your project and move the tarball
to that folder. Add it to your project repository.
@ -76,7 +77,7 @@ The package supports CommonJS `require` and ESM `import` module systems.
### CommonJS `require`
By default, the module supports `require` and it will automatically add support
for encodings, streams and file system access:
for streams and file system access:
```js
const { readFile } = require("xlsx");
@ -117,18 +118,7 @@ builder requires a proper `package.json` that includes the SheetJS dependency.
:::note Tested Deployments
This demo was last tested in the following deployments:
| Architecture | BunJS | Date |
|:-------------|:---------|:-----------|
| `darwin-x64` | `1.2.8` | 2025-03-31 |
| `darwin-arm` | `1.2.7` | 2025-03-30 |
| `win11-x64` | `1.2.8` | 2025-04-17 |
| `win11-arm` | `1.2.3` | 2025-02-23 |
| `linux-x64` | `1.2.10` | 2025-04-21 |
| `linux-arm` | `1.2.2` | 2025-02-16 |
BunJS on Windows on ARM uses the X64 compatibility layer.
This example was last tested on 2024-02-21 against BunJS 1.0.28 on macOS 14.3.1.
:::
@ -140,61 +130,12 @@ cd sheetjs-bun-dle
echo "{}" > package.json
```
:::caution PowerShell Encoding Errors
The PowerShell file redirect will use the `UTF-16 LE` encoding. Bun does not
support the encoding and will fail to install the package:
```
bun add v1.1.42 (50eec002)
1 | <20><>
^
error: Unexpected <20><>
at <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:1:1
```
The file must be resaved in UTF8 (without BOM) or ASCII.
0) Open `package.json` in VSCodium.
The current encoding is displayed in the lower-right corner:
![VSCodium status bar](pathname:///files/encodium.png)
1) Click the displayed encoding.
2) In the "Select Action" popup, select "Save with Encoding"
3) In the new list, select `UTF-8 utf8`:
![VSCodium encoding](pathname:///files/vscutf8.png)
VSCodium will automatically re-save the file.
:::
1) Install the SheetJS package tarball:
1) Install the library:
<CodeBlock language="bash">{`\
bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
:::caution pass
In some test runs, the command failed with a module resolution error:
<CodeBlock>{`\
error: https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz failed to resolve`}
</CodeBlock>
The workaround is to prepend `xlsx@` to the URL:
<CodeBlock language="bash">{`\
bun install xlsx@https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
:::
2) Save the following script to `SheetJSBun.js`:
```js title="SheetJSBun.js"
@ -206,7 +147,7 @@ import * as fs from 'fs';
XLSX.set_fs(fs);
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
@ -249,24 +190,10 @@ This procedure will generate `app.js`.
4) Remove the module artifacts and original script:
```bash
rm package.json bun.lock bun.lockb SheetJSBun.js
rm package.json bun.lockb SheetJSBun.js
rm -rf ./node_modules
```
:::note pass
PowerShell does not support `rm -rf`. Instead, each file must be removed:
```powershell title="Windows Powershell commands"
rm package.json
rm bun.lock
rm bun.lockb
rm SheetJSBun.js
rm .\\node_modules -r -fo
```
:::
At this point, `app.js` will be the only file in the project folder.
5) Run the script:
@ -276,9 +203,4 @@ bun app.js
```
If the script succeeded, the file `Presidents.xlsx` will be created. That file
can be opened in a spreadsheet editor. If a spreadsheet editor is unavailable,
the contents can be displayed using the `xlsx-cli` tool:
```bash
bunx xlsx-cli Presidents.xlsx
```
can be opened in a spreadsheet editor.

@ -8,7 +8,7 @@ title: Installation
import DocCardList from '@theme/DocCardList';
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
https://cdn.sheetjs.com is the primary software distribution site. Please
<https://cdn.sheetjs.com> is the primary software distribution site. Please
read the installation instructions for your use case:
<ul>{useCurrentSidebarCategory().items.map((item, index) => {

@ -1,5 +1,4 @@
---
title: Export Tutorial
pagination_prev: getting-started/installation/index
pagination_next: getting-started/roadmap
sidebar_position: 2
@ -10,6 +9,8 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
# Export Tutorial
Many modern data sources provide an API to download data in JSON format. Many
users prefer to work in spreadsheet software. SheetJS libraries help bridge the
gap by translating programmer-friendly JSON to user-friendly workbooks.
@ -43,19 +44,18 @@ sequenceDiagram
## Acquire Data
The raw data is available in JSON form[^1]. It has been mirrored at
https://docs.sheetjs.com/executive.json
<https://sheetjs.com/data/executive.json>
### Raw Data
Acquiring the data is straightforward with `fetch`:
```js
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
```
<details>
<summary><b>Code Explanation</b> (click to show)</summary>
<details><summary><b>Code Explanation</b> (click to show)</summary>
`fetch` is a low-level API for downloading data from an endpoint. It separates
the network step from the response parsing step.
@ -177,8 +177,7 @@ the code in more detail.
:::
<details>
<summary><b>Code Explanation</b> (click to show)</summary>
<details><summary><b>Code Explanation</b> (click to show)</summary>
**Verifying if a person was a US President**
@ -222,13 +221,11 @@ const prez = raw_data.filter(row => row.terms.some(term => term.type == "prez"))
### Sorting by First Term
The dataset is sorted in chronological order by the first presidential or vice
presidential term. The Vice President and President in a given term are sorted
alphabetically.
Barack Obama became President and Joseph Biden became Vice President in 2009.
Since "Biden" is alphabetically before "Obama", Biden's data appears first.
The goal is to sort the presidents in order of their initial presidential term.
presidential term. The Vice President and President in a given term are sorted
alphabetically. Joe Biden and Barack Obama were Vice President and President
respectively in 2009. Since "Biden" is alphabetically before "Obama", Biden's
data point appears first. The goal is to sort the presidents in order of their
presidential term.
The first step is adding the first presidential term start date to the dataset.
The following code looks at each president and creates a `start` property that
@ -238,8 +235,7 @@ represents the start of the first presidential term.
prez.forEach(row => row.start = row.terms.find(term => term.type === "prez").start);
```
<details>
<summary><b>Code Explanation</b> (click to show)</summary>
<details><summary><b>Code Explanation</b> (click to show)</summary>
**Finding the first presidential term**
@ -309,8 +305,7 @@ At this point, each row in the `prez` array has a `start` property. Since the
prez.sort((l,r) => l.start.localeCompare(r.start));
```
<details>
<summary><b>Code Explanation</b> (click to show)</summary>
<details><summary><b>Code Explanation</b> (click to show)</summary>
**Comparator Functions and Relative Ordering in JavaScript**
@ -376,8 +371,7 @@ const rows = prez.map(row => ({
}));
```
<details>
<summary><b>Code Explanation</b> (click to show)</summary>
<details><summary><b>Code Explanation</b> (click to show)</summary>
**Wrangling One Data Row**
@ -499,8 +493,7 @@ cell styling and frozen rows.
:::
<details>
<summary><b>Changing Header Names</b> (click to show)</summary>
<details><summary><b>Changing Header Names</b> (click to show)</summary>
By default, `json_to_sheet` creates a worksheet with a header row. In this case,
the headers come from the JS object keys: "name" and "birthday".
@ -514,8 +507,7 @@ XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
</details>
<details>
<summary><b>Changing Column Widths</b> (click to show)</summary>
<details><summary><b>Changing Column Widths</b> (click to show)</summary>
Some of the names are longer than the default column width. Column widths are
set by setting the `"!cols"` worksheet property.[^7]
@ -557,7 +549,7 @@ browser should try to create `Presidents.xlsx`
```jsx live
function Presidents() { return ( <button onClick={async () => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
@ -590,7 +582,7 @@ function Presidents() { return ( <button onClick={async () => {
}}><b>Click to Generate file!</b></button> ); }
```
https://sheetjs.com/pres.html is a hosted version of this demo.
<https://sheetjs.com/pres.html> is a hosted version of this demo.
## Run the Demo Locally
@ -605,7 +597,7 @@ Save the following script to `SheetJSStandaloneDemo.html`:
<script>
(async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
\n\
/* filter for the Presidents */
@ -644,7 +636,7 @@ After saving the file, run a local web server in the folder with the HTML file.
For example, if NodeJS is installed:
```bash
npx -y http-server .
npx http-server .
```
The server process will display a URL (typically `http://127.0.0.1:8080`). Open
@ -679,7 +671,7 @@ const XLSX = require("xlsx");
(async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
@ -739,8 +731,7 @@ Native `fetch` support was added in NodeJS 18. For older versions of NodeJS,
the script will throw an error `fetch is not defined`. A third-party library
like `axios` presents a similar API for fetching data:
<details>
<summary><b>Example using axios</b> (click to show)</summary>
<details><summary><b>Example using axios</b> (click to show)</summary>
Install the dependencies:
@ -757,7 +748,7 @@ const axios = require("axios");
(async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
// highlight-next-line
const raw_data = (await axios(url, {responseType: "json"})).data;
@ -803,8 +794,7 @@ This script will write a new file `Presidents.xlsx` in the same folder.
:::
<details>
<summary><b>Other Server-Side Platforms</b> (click to show)</summary>
<details><summary><b>Other Server-Side Platforms</b> (click to show)</summary>
<Tabs>
<TabItem value="deno" label="Deno">
@ -816,7 +806,7 @@ Save the following script to `SheetJSDeno.ts`:
import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
\n\
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
\n\
/* filter for the Presidents */
@ -873,7 +863,7 @@ Save the following script to `SheetJSNW.html`:
<script>
(async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
\n\
/* filter for the Presidents */
@ -942,25 +932,20 @@ of the React Native documentation before testing the demo.
:::
:::danger pass
:::caution pass
There are a number of potential pitfalls.
The [React Native demo](/docs/demos/mobile/reactnative) lists some issues
encountered in previous test runs and potential resolutions.
**Please reach out to [the SheetJS chat](https://sheetjs.com/chat) if there are
any issues not mentioned in the demo page.**
For Android testing, React Native requires Java 11. It will not work with
current Java releases.
:::
Create a new project by running the following commands in the Terminal:
<CodeBlock language="bash">{`\
npx -y @react-native-community/cli@15 init SheetJSPres --version="0.76.5"
npx -y react-native@0.72.4 init SheetJSPres --version="0.72.4"
cd SheetJSPres
\n\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-native-blob-util@0.21.2`}
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-native-blob-util@0.17.1`}
</CodeBlock>
Save the following to `App.tsx` in the project:
@ -973,7 +958,7 @@ import RNBU from 'react-native-blob-util';
const make_workbook = async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
@ -1040,7 +1025,7 @@ export default App;
:::note pass
The Android demo has been tested in Windows, Arch Linux (Steam Deck) and macOS.
The Android demo has been tested in Windows 10 and in macOS.
:::
@ -1053,13 +1038,13 @@ npx react-native start
Once Metro is ready, it will display the commands:
```
r - reload the app
d - open developer menu
i - run on iOS
a - run on Android
r - reload app
d - open Dev Menu
j - open DevTools
```
Press `a` to run on Android. The app will launch in the emulator.
Press `a` to run on android.
After clicking "Press to Export", the app will show an alert with the location
to the generated file (`/data/user/0/com.sheetjspres/files/Presidents.xlsx`)
@ -1076,32 +1061,11 @@ This command generates `Presidents.xlsx` which can be opened.
:::info Device Testing
["Running on Device"](https://reactnative.dev/docs/running-on-device) in the
React Native docs covers device configuration. To summarize:
1) Enable USB debugging on the Android device.
2) Connect the Android device to the computer with a USB cable.
3) Close any running Android and iOS emulators.
4) Run `npx react-native run-android`
React Native docs covers device configuration.
`Presidents.xlsx` will be copied to the `Downloads` folder. The file is visible
in the Files app and can be opened with the Google Sheets app.
:::
:::caution pass
**This demo worked on multiple local Android devices in local tests.** It is not
guaranteed to run on every Android device or Android version.
The [React Native demo](/docs/demos/mobile/reactnative) lists some issues
encountered in previous test runs and potential resolutions.
Please reach out to [the SheetJS chat](https://sheetjs.com/chat) if there are
any issues not mentioned in the demo page.
:::
</TabItem>
@ -1140,13 +1104,13 @@ The highlighted lines should be added to the iOS project `Info.plist` just
before the last `</dict>` tag:
```xml title="ios/SheetJSPres/Info.plist"
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<!-- highlight-start -->
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<!-- highlight-end -->
</dict>
</plist>
@ -1165,7 +1129,7 @@ see a preview of the data. The Numbers app can open the file.
</TabItem>
</Tabs>
[^1]: https://theunitedstates.io/congress-legislators/executive.json is the
[^1]: <https://theunitedstates.io/congress-legislators/executive.json> is the
original location of the example dataset. The contributors to the dataset
dedicated the content to the public domain.
[^2]: See ["The Executive Branch"](https://github.com/unitedstates/congress-legislators#the-executive-branch)

@ -1,5 +1,4 @@
---
title: Import Tutorial
pagination_prev: getting-started/installation/index
pagination_next: getting-started/roadmap
sidebar_position: 4
@ -10,6 +9,8 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
# Import Tutorial
Many government agencies distribute official data and statistics in workbooks.
SheetJS libraries help translate these files to useful information.
@ -41,7 +42,7 @@ sequenceDiagram
## Download File
The raw data is available in a XLS workbook[^1]. It has been mirrored at
https://docs.sheetjs.com/PortfolioSummary.xls
<https://sheetjs.com/data/PortfolioSummary.xls>
:::info pass
@ -55,12 +56,11 @@ data is not lost in the sands of time.
Downloading the file is straightforward with `fetch`:
```js
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const file = await (await fetch(url)).arrayBuffer();
```
<details>
<summary><b>Code Explanation</b> (click to show)</summary>
<details><summary><b>Code Explanation</b> (click to show)</summary>
`fetch` is a low-level API for downloading data from an endpoint. It separates
the network step from the response parsing step.
@ -180,7 +180,7 @@ function SheetJSheetNames() {
const [names, setNames] = React.useState([]);
React.useEffect(() => { (async() =>{
/* parse workbook */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const file = await (await fetch(url)).arrayBuffer();
const workbook = XLSX.read(file);
/* display sheet names */
@ -211,8 +211,7 @@ recommended to use utility functions to present JS-friendly data structures.
The `sheet_to_html` utility function[^7] generates an HTML table from worksheet
objects. The following live example shows the first 20 rows of data in a table:
<details>
<summary><b>Live example</b> (click to show)</summary>
<details><summary><b>Live example</b> (click to show)</summary>
:::info pass
@ -228,7 +227,7 @@ function SheetJSHTMLView() {
const [__html, setHTML] = React.useState("");
React.useEffect(() => { (async() =>{
/* parse workbook, limiting to 20 rows */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const workbook = XLSX.read(await (await fetch(url)).arrayBuffer(), {sheetRows:20});
/* get first worksheet */
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
@ -247,7 +246,7 @@ The key points from looking at the table are:
- The data starts on row 7
- Rows 5 and 6 are the header rows, with merged cells for common titles
- For yearly data (2007-2012), columns A and B are merged
- For quarterly data (2013Q1 and later), column A stores the year. Cells may be
- For quarterly data (2013Q1 - 2023Q2), column A stores the year. Cells may be
merged vertically to span 4 quarters
## Extract Data
@ -307,15 +306,14 @@ will have holes in cells `A14:A16` (written as `null`):
[null, "Q4", 609.1, 25.6, 423, 20.9, 8.1, 2.9, 1040.2, 39.6]
```
<details>
<summary><b>Live example</b> (click to show)</summary>
<details><summary><b>Live example</b> (click to show)</summary>
```jsx live
function SheetJSAoAHoles() {
const [rows, setRows] = React.useState([]);
React.useEffect(() => { (async() =>{
/* parse workbook */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
/* get first worksheet */
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
@ -348,8 +346,7 @@ the code in more detail.
:::
<details>
<summary><b>Code Explanation</b> (click to show)</summary>
<details><summary><b>Code Explanation</b> (click to show)</summary>
**Analyzing every row in the dataset**
@ -458,15 +455,14 @@ After post-processing, the rows now have proper year fields:
[2013, "Q4", 609.1, 25.6, 423, 20.9, 8.1, 2.9, 1040.2, 39.6]
```
<details>
<summary><b>Live example</b> (click to show)</summary>
<details><summary><b>Live example</b> (click to show)</summary>
```jsx live
function SheetJSAoAFilled() {
const [rows, setRows] = React.useState([]);
React.useEffect(() => { (async() =>{
/* parse workbook */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
/* get first worksheet */
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
@ -488,32 +484,21 @@ function SheetJSAoAFilled() {
### Select Data Rows
At this point, each data row will have the year in column `A` and dollar value
in column `C`. The year (first value in the row) will be between 2007 and 2029.
The value (third value) will be positive. The following function tests a row
against the requirements:
At this point, every data row will have the year in column `A`. Since this year
is between 2007 and 2023, `Array#filter` can be used to select the rows:
```js
const is_valid_row = r =>
r[0] >= 2007 && r[0] <= 2029 // year (column A) is between 2007 and 2029
&& r[2] > 0; // dollar value (column C) is positive
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
```
`Array#filter`, using the previous test, can select the matching rows:
```js
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
```
<details>
<summary><b>Live example</b> (click to show)</summary>
<details><summary><b>Live example</b> (click to show)</summary>
```jsx live
function SheetJSAoAFiltered() {
const [rows, setRows] = React.useState([]);
React.useEffect(() => { (async() =>{
/* parse workbook */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
/* get first worksheet */
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
@ -522,7 +507,7 @@ function SheetJSAoAFiltered() {
var last_year = 0;
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
/* display data */
setRows(rows);
})(); }, []);
@ -541,8 +526,7 @@ Looking at the headers:
The desired data is in column `I`. The column index can be calculated using
`XLSX.utils.decode_col`[^11].
<details>
<summary><b>Column Index calculation</b> (click to show)</summary>
<details><summary><b>Column Index calculation</b> (click to show)</summary>
```jsx live
function SheetJSDecodeCol() {
@ -581,15 +565,14 @@ following row:
{ "FY": 2016, "FQ": "Q1", "total": 1220.3 }
```
<details>
<summary><b>Live example</b> (click to show)</summary>
<details><summary><b>Live example</b> (click to show)</summary>
```jsx live
function SheetJSObjects() {
const [rows, setRows] = React.useState([]);
React.useEffect(() => { (async() =>{
/* parse workbook */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
/* get first worksheet */
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
@ -598,7 +581,7 @@ function SheetJSObjects() {
var last_year = 0;
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
/* display data */
@ -634,7 +617,7 @@ best presented in simple HTML tables[^12]:
### Vanilla JS
https://sheetjs.com/sl.html is a hosted version of this demo.
<https://sheetjs.com/sl.html> is a hosted version of this demo.
Without a framework, HTML table row elements can be programmatically created
with `document.createElement` and added to the table body element. For example,
@ -694,7 +677,7 @@ function StudentAidTotal() {
const [num, setNum] = React.useState(5);
React.useEffect(() => { (async() =>{
/* parse workbook */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
/* get first worksheet */
@ -706,7 +689,7 @@ function StudentAidTotal() {
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
@ -749,7 +732,7 @@ Save the following script to `SheetJSStandaloneDemo.html`:
<script>
(async() => {
/* parse workbook */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
\n\
/* get first worksheet */
@ -761,7 +744,7 @@ Save the following script to `SheetJSStandaloneDemo.html`:
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
\n\
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
\n\
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
@ -781,7 +764,7 @@ After saving the file, run a local web server in the folder with the HTML file.
For example, if NodeJS is installed:
```bash
npx -y http-server .
npx http-server .
```
The server process will display a URL (typically `http://127.0.0.1:8080`). Open
@ -815,7 +798,7 @@ Save the following script to `SheetJSNodeJS.js`:
const XLSX = require("xlsx");
(async() => {
/* parse workbook */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
/* get first worksheet */
@ -827,7 +810,7 @@ const XLSX = require("xlsx");
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
@ -888,7 +871,7 @@ Save the following script to `SheetJSNW.html`:
<script>
(async() => {
/* parse workbook */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const workbook = XLSX.read(await (await fetch(url)).arrayBuffer());
\n\
/* get first worksheet */
@ -900,7 +883,7 @@ Save the following script to `SheetJSNW.html`:
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
\n\
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
\n\
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
@ -967,7 +950,7 @@ uses the native `FlatList` component.
Create a new project by running the following commands in the Terminal:
<CodeBlock language="bash">{`\
npx -y @react-native-community/cli@15 init SheetJSSL --version="0.76.5"
npx react-native@0.72.4 init SheetJSSL --version="0.72.4"
cd SheetJSSL
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
@ -989,7 +972,7 @@ const App = () => {
const [rows, setRows] = React.useState([]);
React.useEffect(() => { (async() =>{
/* parse workbook */
const url = "https://docs.sheetjs.com/PortfolioSummary.xls";
const url = "https://sheetjs.com/data/PortfolioSummary.xls";
const workbook = read(await (await fetch(url)).arrayBuffer());
/* get first worksheet */
@ -1001,7 +984,7 @@ const App = () => {
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2023);
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
@ -1030,7 +1013,7 @@ export default App;
:::note pass
The Android demo has been tested in Windows and macOS.
The Android demo has been tested in Windows 10 and in macOS.
:::
@ -1043,10 +1026,10 @@ npx react-native start
Once Metro is ready, it will display the commands:
```
r - reload the app
d - open developer menu
i - run on iOS
a - run on Android
r - reload app
d - open Dev Menu
j - open DevTools
```
Press `a` to run on Android.
@ -1067,12 +1050,6 @@ This demo runs in iOS and requires a Macintosh computer with Xcode installed.
:::
The native component must be linked:
```bash
cd ios; pod install; cd ..
```
Test the app in the iOS simulator:
```bash
@ -1096,7 +1073,7 @@ When the app is loaded, the data will be displayed in rows.
[^7]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output)
[^8]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^9]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^10]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges)
[^10]: See [`!merges` in "Sheet Objects"](/docs/csf/sheet#worksheet-object)
[^11]: See ["Column Names" in "Addresses and Ranges"](/docs/csf/general#column-names)
[^12]: See ["Array of Objects" in "ReactJS"](/docs/demos/frontend/react#array-of-objects)
[^13]: See ["Running on Device"](https://reactnative.dev/docs/running-on-device) in the React Native documentation for more details.

@ -1,981 +0,0 @@
---
title: Loader Tutorial
pagination_prev: getting-started/installation/index
pagination_next: getting-started/roadmap
sidebar_position: 6
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
Many existing systems and platforms include support for loading data from CSV
files. Many users prefer to work in spreadsheet software and multi-sheet file
formats including XLSX. SheetJS libraries help bridge the gap by translating
complex workbooks to simple CSV data.
The goal of this example is to load spreadsheet data into a vector store and use
a large language model to generate queries based on English language input. The
existing tooling supports CSV but does not support real spreadsheets.
In ["SheetJS Conversion"](#sheetjs-conversion), we will use SheetJS libraries to
generate CSV files for the LangChainJS CSV loader. These conversions can be run
in a preprocessing step without disrupting existing CSV workflows.
In ["SheetJS Loader"](#sheetjs-loader), we will use SheetJS libraries in a
custom `LoadOfSheet` data loader to directly generate documents and metadata.
["SheetJS Loader Demo"](#sheetjs-loader-demo) is a complete demo that uses the
SheetJS Loader to answer questions based on data from a XLS workbook.
:::note Tested Deployments
This demo was tested in the following configurations:
| Platform | Architecture | Date |
|:------------------------------------------------------------------|:-------------|:-----------|
| NVIDIA RTX 5090 (32 GB VRAM) + Ryzen Z1 Extreme (24 GB RAM) | `win11-x64` | 2025-06-17 |
| NVIDIA RTX 4090 (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | 2025-04-17 |
| NVIDIA RTX 4090 (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `linux-x64` | 2025-01-28 |
| AMD RX 7900 XTX (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | 2025-06-20 |
| AMD RX 7900 XTX (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `linux-x64` | 2025-01-29 |
| Intel Arc B580 (12 GB VRAM) + Ryzen Z1 Extreme (24 GB RAM) | `win11-x64` | 2025-06-20 |
| Intel Arc B580 (12 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `linux-x64` | 2025-02-08 |
| Apple M4 Max 16-Core CPU + 40-Core GPU (48 GB unified memory) | `darwin-arm` | 2025-03-06 |
| Apple M3 Ultra 28-Core CPU + 60-Core GPU (96 GB unified memory) | `darwin-arm` | 2025-06-24 |
| Apple M2 Max 12-Core CPU + 30-Core GPU (32 GB unified memory) | `darwin-arm` | 2025-03-25 |
SheetJS users have verified this demo in other configurations:
<details>
<summary><b>Other tested configurations</b> (click to show)</summary>
| Platform | Architecture | Demo |
|:---------------------------------------------------------------------|:-------------|:------------|
| NVIDIA L40 (48 GB VRAM) + i9-13900K (32 GB RAM) | `linux-x64` | LangChainJS |
| NVIDIA RTX 4080 SUPER (16 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 4070 Ti SUPER (16 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 4070 Ti (12 GB VRAM) + Ryzen 7 5800x (64 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 4060 (8 GB VRAM) + Ryzen 7 5700g (32 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 3090 (24 GB VRAM) + Ryzen 9 3900XT (128 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 3080 (12 GB VRAM) + Ryzen 7 5800X (32 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 3070 (8 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS |
| NVIDIA RTX 3060 (12 GB VRAM) + i5-11400 (32 GB RAM) | `win10-x64` | LangChainJS |
| NVIDIA RTX 2080 (12 GB VRAM) + i7-9700K (16 GB RAM) | `win10-x64` | LangChainJS |
| NVIDIA RTX 2070 (8 GB VRAM) + Ryzen 7 3700x (80 GB RAM) | `linux-x64` | LangChainJS |
| NVIDIA RTX 2060 (6 GB VRAM) + Ryzen 5 3600 (32 GB RAM) | `win10-x64` | LangChainJS |
| NVIDIA GTX 1080 (8 GB VRAM) + Ryzen 7 5800x (64 GB RAM) | `win10-x64` | LangChainJS |
| NVIDIA GTX 1070 (8 GB VRAM) + Ryzen 7 7700x (32 GB RAM) | `win11-x64` | LangChainJS |
| AMD RX 6800 XT (16 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS |
| Apple M4 10-Core CPU + 10-Core GPU (24 GB unified memory) | `darwin-arm` | LangChainJS |
</details>
Special thanks to the following users for testing with multiple configurations:
- [Asadbek Karimov](https://asadk.dev/)
- [Rasmus Tengstedt](https://tengstedt.dev/)
- [Joban Dhillon](https://dhillon.dev/)
:::
## CSV Loader
:::note pass
This explanation was verified against LangChainJS 0.2.
:::
Document loaders generate data objects ("documents") and associated metadata
from data sources.
LangChainJS offers a `CSVLoader`[^1] component for loading CSV data from a file:
```js title="Generating Documents from a CSV file"
import { CSVLoader } from "@langchain/community/document_loaders/fs/csv";
const loader = new CSVLoader("pres.csv");
const docs = await loader.load();
console.log(docs);
```
The CSV loader uses the first row to determine column headers and generates one
document per data row. For example, the following CSV holds Presidential data:
```csv
Name,Index
Bill Clinton,42
GeorgeW Bush,43
Barack Obama,44
Donald Trump,45
Joseph Biden,46
```
Each data row is translated to a document whose content is a list of attributes
and values. For example, the third data row is shown below:
<table>
<thead><tr><th>CSV Row</th><th>Document Content</th></tr></thead>
<tbody><tr><td>
```
Name,Index
Barack Obama,44
```
</td><td>
```
Name: Barack Obama
Index: 44
```
</td></tr></tbody>
</table>
The LangChainJS CSV loader will include source metadata in the document:
```js title="Document generated by the CSV loader"
Document {
pageContent: 'Name: Barack Obama\nIndex: 44',
metadata: { source: 'pres.csv', line: 3 }
}
```
## SheetJS Conversion
The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be
imported in NodeJS scripts that use LangChainJS and other JavaScript libraries.
A simple pre-processing step can convert workbooks to CSV files that can be
processed by the existing CSV tooling:
```mermaid
flowchart LR
file[(Workbook\nXLSX/XLS)]
subgraph SheetJS Structures
wb(((SheetJS\nWorkbook)))
ws((SheetJS\nWorksheet))
end
csv(CSV\nstring)
docs[[Documents\nArray]]
file --> |readFile\n\n| wb
wb --> |wb.Sheets\nselect sheet| ws
ws --> |sheet_to_csv\n\n| csv
csv --> |CSVLoader\n\n| docs
linkStyle 0,1,2 color:blue,stroke:blue;
```
**Parsing files from the filesystem**
The SheetJS `readFile` method[^2] can read workbook files. The method accepts a
path and returns a workbook object that conforms to the SheetJS data model[^3].
```js
/* Load SheetJS Libraries */
import { readFile, set_fs } from 'xlsx';
/* Load 'fs' for readFile support */
import * as fs from 'fs';
set_fs(fs);
/* Parse `pres.xlsx` */
// highlight-next-line
const wb = readFile("pres.xlsx");
```
**Inspecting SheetJS workbook and worksheet objects**
Workbook objects represent multi-sheet workbook files. They store individual
worksheet objects and other metadata.
Relevant to this discussion, the workbook object uses the following keys[^7]:
- `SheetNames` is an array of worksheet names
- `Sheets` is an object whose keys are sheet names and whose values are sheet objects.
`SheetNames[0]` is the first worksheet name, so the following snippet will pull
the first worksheet from the workbook:
```js
const first_ws = wb.Sheets[wb.SheetNames[0]];
```
**Exporting SheetJS worksheets to CSV**
Each worksheet in the workbook can be written to CSV text using the SheetJS
`sheet_to_csv`[^4] method. The method accepts a SheetJS worksheet object and
returns a string.
```js
const csv = utils.sheet_to_csv(first_ws);
```
**Complete Script**
For example, the following NodeJS script reads `pres.xlsx` and displays CSV rows
from the first worksheet:
```js title="Print CSV data from the first worksheet"
/* Load SheetJS Libraries */
import { readFile, set_fs, utils } from 'xlsx';
/* Load 'fs' for readFile support */
import * as fs from 'fs';
set_fs(fs);
/* Parse `pres.xlsx` */
const wb = readFile("pres.xlsx");
/* Print CSV rows from first worksheet */
const first_ws = wb.Sheets[wb.SheetNames[0]];
const csv = utils.sheet_to_csv(first_ws);
console.log(csv);
```
### Similar Workflows {#similar-workflows}
:::note pass
A number of demos cover spiritually similar workflows:
- [Stata](/docs/demos/extensions/stata), [MATLAB](/docs/demos/extensions/matlab)
and [Maple](/docs/demos/extensions/maple/) support XLSX data import. The SheetJS
integrations generate clean XLSX workbooks from user-supplied spreadsheets.
- [TensorFlow.js](/docs/demos/math/tensorflow), [Pandas](/docs/demos/math/pandas)
and [Mathematica](/docs/demos/extensions/mathematica) support CSV data import.
The SheetJS integrations generate clean CSVs and use built-in CSV processors.
- The ["Command-Line Tools"](/docs/demos/cli/) demo covers techniques for making
standalone command-line tools for file conversion.
:::
### Single Worksheet
For a single worksheet, a SheetJS pre-processing step can write the CSV rows to
file and the `CSVLoader` can load the newly written file.
<details open>
<summary><b>Code example</b> (click to hide)</summary>
```js title="Pulling data from the first worksheet of a workbook"
import { CSVLoader } from "@langchain/community/document_loaders/fs/csv";
import { readFile, set_fs, utils } from 'xlsx';
/* Load 'fs' for readFile support */
import * as fs from 'fs';
set_fs(fs);
/* Parse `pres.xlsx`` */
const wb = readFile("pres.xlsx");
/* Generate CSV and write to `pres.xlsx.csv` */
const first_ws = wb.Sheets[wb.SheetNames[0]];
const csv = utils.sheet_to_csv(first_ws);
fs.writeFileSync("pres.xlsx.csv", csv);
/* Create documents with CSVLoader */
const loader = new CSVLoader("pres.xlsx.csv");
const docs = await loader.load();
console.log(docs);
// ...
```
</details>
### Workbook
A workbook is a collection of worksheets. Each worksheet can be exported to a
separate CSV. If the CSVs are written to a subfolder, a `DirectoryLoader`[^5]
can process the files in one step.
<details open>
<summary><b>Code example</b> (click to hide)</summary>
In this example, the script creates a subfolder named `csv`. Each worksheet in
the workbook will be processed and the generated CSV will be stored to numbered
files. The first worksheet will be stored to `csv/0.csv`.
```js title="Pulling data from the each worksheet of a workbook"
import { CSVLoader } from "@langchain/community/document_loaders/fs/csv";
import { DirectoryLoader } from "langchain/document_loaders/fs/directory";
import { readFile, set_fs, utils } from 'xlsx';
/* Load 'fs' for readFile support */
import * as fs from 'fs';
set_fs(fs);
/* Parse `pres.xlsx`` */
const wb = readFile("pres.xlsx");
/* Create a folder `csv` */
try { fs.mkdirSync("csv"); } catch(e) {}
/* Generate CSV data for each worksheet */
wb.SheetNames.forEach((name, idx) => {
const ws = wb.Sheets[name];
const csv = utils.sheet_to_csv(ws);
fs.writeFileSync(`csv/${idx}.csv`, csv);
});
/* Create documents with DirectoryLoader */
const loader = new DirectoryLoader("csv", {
".csv": (path) => new CSVLoader(path)
});
const docs = await loader.load();
console.log(docs);
// ...
```
</details>
## SheetJS Loader
The LangChainJS `CSVLoader` does not add any Document metadata and does not
generate any attributes. A custom loader can work around limitations in the CSV
tooling and potentially include metadata that has no CSV equivalent.
```mermaid
flowchart LR
file[(Workbook\nXLSX/XLS)]
subgraph SheetJS Structures
wb(((SheetJS\nWorkbook)))
ws((SheetJS\nWorksheet))
end
aoo[(Array of\nObjects)]
docs[[Documents\nArray]]
file --> |readFile\n\n| wb
wb --> |wb.Sheets\nEach worksheet| ws
ws --> |sheet_to_json\n\n| aoo
aoo --> |new Document\nEach Row| docs
linkStyle 0,1,2 color:blue,stroke:blue;
```
The demo [`LoadOfSheet` loader](pathname:///loadofsheet/loadofsheet.mjs) will
generate one Document per data row across all worksheets. It will also attempt
to build metadata and attributes for use in self-querying retrievers.
```js title="Sample usage"
/* read and parse `data.xlsb` */
const loader = new LoadOfSheet("./data.xlsb");
/* generate documents */
const docs = await loader.load();
/* synthesized attributes for the SelfQueryRetriever */
const attributes = loader.attributes;
```
<details>
<summary><b>Sample SheetJS Loader</b> (click to show)</summary>
This example loader pulls data from each worksheet. It assumes each worksheet
includes one header row and a number of data rows.
```js title="loadofsheet.mjs"
import { Document } from "@langchain/core/documents";
import { BufferLoader } from "langchain/document_loaders/fs/buffer";
import { read, utils } from "xlsx";
/**
* Document loader that uses SheetJS to load documents.
*
* Each worksheet is parsed into an array of row objects using the SheetJS
* `sheet_to_json` method and projected to a `Document`. Metadata includes
* original sheet name, row data, and row index
*/
export default class LoadOfSheet extends BufferLoader {
/** @type {import("langchain/chains/query_constructor").AttributeInfo[]} */
attributes = [];
/**
* Document loader that uses SheetJS to load documents.
*
* @param {string|Blob} filePathOrBlob Source Data
*/
constructor(filePathOrBlob) {
super(filePathOrBlob);
this.attributes = [];
}
/**
* Parse document
*
* NOTE: column labels in multiple sheets are not disambiguated!
*
* @param {Buffer} raw Raw data Buffer
* @param {Document["metadata"]} metadata Document metadata
* @returns {Promise<Document[]>} Array of Documents
*/
async parse(raw, metadata) {
/** @type {Document[]} */
const result = [];
this.attributes = [
{ name: "worksheet", description: "Sheet or Worksheet Name", type: "string" },
{ name: "rowNum", description: "Row index", type: "number" }
];
const wb = read(raw, {type: "buffer", WTF:1});
for(let name of wb.SheetNames) {
const fields = {};
const ws = wb.Sheets[name];
if(!ws) return;
const aoo = utils.sheet_to_json(ws);
aoo.forEach((row, idx) => {
result.push({
pageContent: "Row " + (idx + 1) + " has the following content: \n" + Object.entries(row).map(kv => `- ${kv[0]}: ${kv[1]}`).join("\n") + "\n",
metadata: {
worksheet: name,
rowNum: row["__rowNum__"],
...metadata,
...row
}
});
Object.entries(row).forEach(([k,v]) => { if(v != null) (fields[k] || (fields[k] = {}))[v instanceof Date ? "date" : typeof v] = true } );
});
Object.entries(fields).forEach(([k,v]) => this.attributes.push({
name: k, description: k, type: Object.keys(v).join(" or ")
}));
}
return result;
}
};
```
</details>
### From Text to Binary
Many libraries and platforms offer generic "text" loaders that process files
assuming the UTF-8 encoding. This corrupts many spreadsheet formats including
XLSX, XLSB, XLSM and XLS.
:::note pass
This issue affects many JavaScript tools. Various demos cover workarounds:
- [ViteJS plugins](/docs/demos/static/vitejs#plugins) receive the relative path
to the workbook file and can read the file directly.
- [Webpack plugins](/docs/demos/static/webpack#sheetjs-loader) support a special
`raw` option that instructs the bundler to pass raw binary data.
- [NuxtJS parsers and transformers](/docs/demos/static/nuxtjs) can deduce the
path to the workbook file from internal identifiers.
:::
The `CSVLoader` extends a special `TextLoader` that forces UTF-8 text parsing.
There is a separate `BufferLoader` class, used by the PDF loader, that passes
the raw data using NodeJS `Buffer` objects.
<table>
<thead><tr><th>Binary</th><th>Text</th></tr></thead>
<tbody><tr><td>
```ts title="pdf.ts (structure)"
export class PDFLoader extends BufferLoader {
// ...
public async parse(
raw: Buffer,
metadata: Document["metadata"]
): Promise<Document[]> {
// ...
}
// ...
}
```
</td><td>
```ts title="csv.ts (structure)"
export class CSVLoader extends TextLoader {
// ...
protected async parse(
raw: string
): Promise<string[]> {
// ...
}
// ...
}
```
</td></tr></tbody>
</table>
### NodeJS Buffers
The SheetJS `read` method supports NodeJS Buffer objects directly[^6]:
```js title="Parsing a workbook in a BufferLoader"
import { BufferLoader } from "langchain/document_loaders/fs/buffer";
import { read, utils } from "xlsx";
export default class LoadOfSheet extends BufferLoader {
// ...
async parse(raw, metadata) {
// highlight-next-line
const wb = read(raw, {type: "buffer"});
// At this point, `wb` is a SheetJS workbook object
// ...
}
}
```
The `read` method returns a SheetJS workbook object[^7].
### Generating Content
The SheetJS `sheet_to_json` method[^8] returns an array of data objects whose
keys are drawn from the first row of the worksheet.
<table>
<thead><tr><th>Spreadsheet</th><th>Array of Objects</th></tr></thead>
<tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
</td><td>
```js
[
{ Name: "Bill Clinton", Index: 42 },
{ Name: "GeorgeW Bush", Index: 43 },
{ Name: "Barack Obama", Index: 44 },
{ Name: "Donald Trump", Index: 45 },
{ Name: "Joseph Biden", Index: 46 }
]
```
</td></tr></tbody></table>
The original `CSVLoader` wrote one row for each key-value pair. This text can be
generated by looping over the keys and values of the data row object. The
`Object.entries` helper function simplifies the conversion:
```js
function make_csvloader_doc_from_row_object(row) {
return Object.entries(row).map(([k,v]) => `${k}: ${v}`).join("\n");
}
```
### Generating Documents
The loader must generate row objects for each worksheet in the workbook.
In the SheetJS data model, the workbook object has two relevant fields:
- `SheetNames` is an array of sheet names
- `Sheets` is an object whose keys are sheet names and values are sheet objects.
A `for..of` loop can iterate across the worksheets:
```js title="Looping over a workbook (skeleton)"
const wb = read(raw, {type: "buffer", WTF:1});
for(let name of wb.SheetNames) {
const ws = wb.Sheets[name];
const aoa = utils.sheet_to_json(ws);
// at this point, `aoa` is an array of objects
}
```
This simplified `parse` function uses the snippet from the previous section:
```js title="BufferLoader parse function (skeleton)"
async parse(raw, metadata) {
/* array to hold generated documents */
const result = [];
/* read workbook */
const wb = read(raw, {type: "buffer", WTF:1});
/* loop over worksheets */
for(let name of wb.SheetNames) {
const ws = wb.Sheets[name];
const aoa = utils.sheet_to_json(ws);
/* loop over data rows */
aoa.forEach((row, idx) => {
/* generate a new document and add to the result array */
result.push({
pageContent: Object.entries(row).map(([k,v]) => `${k}: ${v}`).join("\n")
});
});
}
return result;
}
```
### Metadata and Attributes
It is strongly recommended to generate additional metadata and attributes for
self-query retrieval applications.
<details>
<summary><b>Implementation Details</b> (click to show)</summary>
**Metadata**
Metadata is attached to each document object. The following example appends the
raw row data to the document metadata:
```js title="Document with metadata (snippet)"
/* generate a new document and add to the result array */
result.push({
pageContent: Object.entries(row).map(([k,v]) => `${k}: ${v}`).join("\n"),
metadata: {
worksheet: name, // name of the worksheet
rowNum: idx, // data row index
...row // raw row data
}
});
```
**Attributes**
Each attribute object specifies three properties:
- `name` corresponds to the field in the document metadata
- `description` is a description of the field
- `type` is a description of the data type.
While looping through data rows, a simple type check can keep track of the data
type for each column:
```js title="Tracking column types (sketch)"
for(let name of wb.SheetNames) {
/* track column types */
const fields = {};
// ...
aoo.forEach((row, idx) => {
result.push({/* ... */});
/* Check each property */
Object.entries(row).forEach(([k,v]) => {
/* Update fields entry to reflect the new data point */
if(v != null) (fields[k] || (fields[k] = {}))[v instanceof Date ? "date" : typeof v] = true
});
});
// ...
}
```
Attributes can be generated after writing the worksheet data. Storing attributes
in a loader property will make it accessible to scripts that use the loader.
```js title="Adding Attributes to a Loader (sketch)"
export default class LoadOfSheet extends BufferLoader {
// highlight-next-line
attributes = [];
// ...
async parse(raw, metadata) {
// Add the worksheet name and row index attributes
// highlight-start
this.attributes = [
{ name: "worksheet", description: "Sheet or Worksheet Name", type: "string" },
{ name: "rowNum", description: "Row index", type: "number" }
];
// highlight-end
const wb = read(raw, {type: "buffer", WTF:1});
for(let name of wb.SheetNames) {
// highlight-next-line
const fields = {};
// ...
const aoo = utils.sheet_to_json(ws);
aoo.forEach((row, idx) => {
result.push({/* ... */});
/* Check each property */
Object.entries(row).forEach(([k,v]) => {
/* Update fields entry to reflect the new data point */
if(v != null) (fields[k] || (fields[k] = {}))[v instanceof Date ? "date" : typeof v] = true
});
});
/* Add one attribute per metadata field */
// highlight-start
Object.entries(fields).forEach(([k,v]) => this.attributes.push({
name: k, description: k,
/* { number: true, string: true } -> "number or string" */
type: Object.keys(v).join(" or ")
}));
// highlight-end
}
// ...
}
```
</details>
## SheetJS Loader Demo
The demo performs the query "Which rows have over 40 miles per gallon?" against
a [sample cars dataset](pathname:///cd.xls) and displays the results.
:::caution pass
This demo was tested using the Phi-4[^9] in Ollama.
The tested model used up to 10GB VRAM. It is strongly recommended to run the
demo on a GPU with at least 12GB VRAM or a newer Apple Silicon Mac with at least
32GB unified memory.
:::
0) Install pre-requisites:
- [NodeJS LTS (version 20+)](https://nodejs.org/)
- [Ollama](https://ollama.com/download)
:::note pass
Ollama should be installed on the same platform as NodeJS. If NodeJS is run
within WSL, Ollama should also be installed within WSL.
:::
:::danger pass
Intel ARC GPUs require the Intel Extension for PyTorch (IPEX) and a special
version of Ollama that ships with the associated LLM Library (IPEX-LLM).
<details>
<summary><b>ARC Instructions</b> (click to show)</summary>
These instructions are based on the official Intel recommendations.
A) If Ollama for Windows was installed, close the program by right-clicking on
the tray icon and selecting "Quit Ollama".
B) Install Miniforge3[^10], selecting "Just Me" when prompted.
C) Launch a normal Command Prompt and create a Conda environment:
```bash
cd %USERPROFILE%\Documents
mkdir ollama-intel
cd ollama-intel
set PATH=%PATH%;%USERPROFILE%\miniforge3\condabin
conda create -n llm-cpp python=3.11
```
D) Activate the environment in the session and install dependencies:
```bash
conda activate llm-cpp
pip install --pre --upgrade ipex-llm[cpp]
```
Close the window after the installation.
E) Launch a new Administrator Command Prompt and set up Ollama:
```bash
cd %USERPROFILE%\Documents\ollama-intel
set PATH=%PATH%;%USERPROFILE%\miniforge3\condabin
conda activate llm-cpp
init-ollama.bat
```
Close the window.
F) Launch a normal Command Prompt window and run Ollama:
```bash
cd %USERPROFILE%\Documents\ollama-intel
set PATH=%PATH%;%USERPROFILE%\miniforge3\condabin
conda activate llm-cpp
set OLLAMA_NUM_GPU=999
set no_proxy=localhost,127.0.0.1
set ZES_ENABLE_SYSMAN=1
set SYCL_CACHE_PERSISTENT=1
set SYCL_PI_LEVEL_ZERO_USE_IMMEDIATE_COMMANDLISTS=1
ollama serve
```
This window should be kept open throughout the demo.
</details>
:::
After installing dependencies, start a new terminal session.
1) Create a new project:
```bash
mkdir sheetjs-loader
cd sheetjs-loader
npm init -y
```
2) Download the demo scripts:
- [`loadofsheet.mjs`](pathname:///loadofsheet/loadofsheet.mjs)
- [`query.mjs`](pathname:///loadofsheet/query.mjs)
```bash
curl -LO https://docs.sheetjs.com/loadofsheet/query.mjs
curl -LO https://docs.sheetjs.com/loadofsheet/loadofsheet.mjs
```
:::note pass
In PowerShell, the command may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'LO'.
```
`curl.exe` must be invoked directly:
```bash
curl.exe -LO https://docs.sheetjs.com/loadofsheet/query.mjs
curl.exe -LO https://docs.sheetjs.com/loadofsheet/loadofsheet.mjs
```
:::
3) Install the SheetJS NodeJS module:
<CodeBlock language="bash">{`\
npm i --save https://sheet.lol/balls/xlsx-${current}.tgz`}
</CodeBlock>
4) Install dependencies:
```bash
npm i --save @langchain/core@0.3.59 langchain@0.3.28 @langchain/ollama@0.2.2 peggy@3.0.2
```
:::note pass
In some test runs, there were error messages relating to dependency and peer
dependency versions. The `--force` flag will suppress version mismatch errors:
```bash
npm i --save @langchain/core@0.3.59 langchain@0.3.28 @langchain/ollama@0.2.2 peggy@3.0.2 --force
```
:::
5) Download the [cars dataset](pathname:///cd.xls):
```bash
curl -LO https://docs.sheetjs.com/cd.xls
```
:::note pass
In PowerShell, the command may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'LO'.
```
`curl.exe` must be invoked directly:
```bash
curl.exe -LO https://docs.sheetjs.com/cd.xls
```
:::
6) Install the `phi4:14b` model using Ollama:
```bash
ollama pull phi4:14b
```
<details>
<summary><b>Additional steps for Intel GPUs</b> (click to show)</summary>
A different embedding model must be used on Intel GPUs:
A) Install the `nomic-embed-text:latest` model through Ollama:
```bash
ollama pull nomic-embed-text:latest
```
B) Edit `query.mjs` to use the embedding model:
```js title="query.mjs (edit highlighted line)"
const llm = new ChatOllama({ baseUrl: "http://127.0.0.1:11434", model });
// highlight-next-line
const embeddings = new OllamaEmbeddings({ baseUrl: "http://127.0.0.1:11434", model: "nomic-embed-text:latest"});
```
</details>
7) Run the demo script
```bash
node query.mjs
```
The demo performs the query "Which rows have over 40 miles per gallon?". It will
print the following nine results:
```js title="Expected output (order of lines may differ)"
{ Name: 'volkswagen rabbit custom diesel', MPG: 43.1 }
{ Name: 'vw rabbit c (diesel)', MPG: 44.3 }
{ Name: 'renault lecar deluxe', MPG: 40.9 }
{ Name: 'honda civic 1500 gl', MPG: 44.6 }
{ Name: 'datsun 210', MPG: 40.8 }
{ Name: 'vw pickup', MPG: 44 }
{ Name: 'mazda glc', MPG: 46.6 }
{ Name: 'vw dasher (diesel)', MPG: 43.4 }
{ Name: 'vw rabbit', MPG: 41.5 }
```
:::caution pass
Some SheetJS users with older GPUs have reported errors.
If the command fails, please try running the script a second time.
:::
To find the expected results:
- Open the `cd.xls` spreadsheet in Excel
- Select Home > Sort &amp; Filter > Filter in the Ribbon
- Select the filter option for column B (`Miles_per_Gallon`)
- In the popup, select "Greater Than" in the Filter dropdown and type 40
The filtered results should match the following screenshot:
![Expected Results](pathname:///loadofsheet/expected.png)
:::tip pass
The [SheetJS model](/docs/csf) exposes [formulae](/docs/csf/features/formulae)
and other features.
[SheetJS Pro](https://sheetjs.com/pro) builds expose cell styling, images,
charts, tables, and other features.
:::
[^1]: See ["How to load CSV data"](https://js.langchain.com/v0.2/docs/how_to/document_loader_csv) in the LangChain documentation
[^2]: See [`readFile` in "Reading Files"](/docs/api/parse-options)
[^3]: See ["SheetJS Data Model"](/docs/csf/)
[^4]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output)
[^5]: See ["Folders with multiple files"](https://js.langchain.com/v0.2/docs/integrations/document_loaders/file_loaders/directory/) in the LangChain documentation
[^6]: See ["Supported Output Formats" type in "Writing Files"](/docs/api/write-options#supported-output-formats)
[^7]: See ["Workbook Object"](/docs/csf/book)
[^8]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^9]: See [the Phi-4 Technical Report](https://arxiv.org/abs/2412.08905) for more details.
[^10]: Select ["Windows" `x86_64`](https://conda-forge.org/download/) in the Installation page.

@ -1,11 +1,12 @@
---
title: Tutorials
pagination_prev: getting-started/installation/index
pagination_next: getting-started/roadmap
---
# Tutorials
SheetJS presents a simple JS interface that works with "Array of Arrays" and
"Array of JS Objects". The API functions are building blocks that should be
"Array of JS Objects". The API functions are building blocks that should be
combined with other JS APIs to solve problems.
These discussions focus on the problem solving mindset. API details are covered
@ -23,8 +24,3 @@ The ["Import Tutorial"](/docs/getting-started/examples/import) examines the data
import process. A legacy file is downloaded and parsed. The underlying data is
ultimately displayed to the user in a HTML table.
## Loading Sheets
The ["Loader Tutorial"](/docs/getting-started/examples/loader) explores SheetJS
integrations. Based on the existing CSV and binary loaders, a spreadsheet loader
is developed and tested in a natural language query workflow.

@ -1,9 +1,10 @@
---
title: Roadmap
pagination_prev: getting-started/examples/index
sidebar_position: 3
---
# Roadmap
Most scenarios involving spreadsheets and data can be divided into 5 parts:
1) **Acquire Data**: Data may be stored anywhere: local or remote files,

@ -1,9 +1,10 @@
---
title: Zen of SheetJS
sidebar_position: 4
hide_table_of_contents: true
---
# Zen of SheetJS
SheetJS design and development is guided by a few key principles.
### Data processing should fit in any workflow
@ -18,8 +19,8 @@ The ["Common Spreadsheet Format"](/docs/csf/general) is a simple object
representation of the core concepts of a workbook. [Utilities](/docs/api/utilities/)
provide low-level tools for working with the object.
SheetJS provides convenient methods for processing common JavaScript data
structures. The [Export Tutorial](/docs/getting-started/examples/export)
For friendly JS processing, there are utility functions for converting parts of
a worksheet to/from an Array of Arrays. The [Tutorial](/docs/getting-started/example)
combines powerful JS Array methods with a network request library to download
data, select the information we want and create a workbook file.

@ -1,5 +1,4 @@
---
title: Getting Started
hide_table_of_contents: true
pagination_next: getting-started/installation/index
---
@ -7,6 +6,8 @@ pagination_next: getting-started/installation/index
import DocCardList from '@theme/DocCardList';
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
# Getting Started
["Export Tutorial"](/docs/getting-started/examples/export) is a live example
that covers general data munging and data export to spreadsheets.
@ -18,8 +19,8 @@ deployments and use cases.
## Installation
https://cdn.sheetjs.com is the primary software distribution site. Please read
the installation instructions for your use case:
<https://cdn.sheetjs.com> is the primary software distribution site. Please
read the installation instructions for your use case:
<ul>{useCurrentSidebarCategory().items.map((item, index) => {
if(item.label != "Installation") return "";

@ -38,11 +38,9 @@ tutorial first.
This browser demo was tested in the following environments:
| Browser | Date |
|:-------------|:-----------|
| Chromium 133 | 2025-03-30 |
| Safari 18.3 | 2025-03-30 |
| Konqueror 22 | 2025-04-23 |
| Browser | Date |
|:------------|:-----------|
| Chrome 119 | 2024-01-06 |
:::
@ -68,9 +66,7 @@ The idiomatic JavaScript representation of the dataset is an array of objects.
Variable names are typically taken from the first row. Those names are used as
keys in each observation.
<table>
<thead><tr><th>Spreadsheet</th><th>JS Data</th></tr></thead>
<tbody><tr><td>
<table><thead><tr><th>Spreadsheet</th><th>JS Data</th></tr></thead><tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
@ -185,7 +181,9 @@ function SheetJSAoOExtractColumn() {
return ( <>
<b>First 5 Sepal Length Values</b><br/>
<table><tbody><tr>{col.map(sw => (<td>{sw}</td>))}</tr></tbody></table>
<table><tbody>
{col.map(sw => (<tr><td>{sw}</td></tr>))}
</tbody></table>
</>
);
}
@ -384,8 +382,7 @@ function aoa_average_of_key(aoo, key) {
}
```
<details>
<summary><b>Live Demo</b> (click to show)</summary>
<details><summary><b>Live Demo</b> (click to show)</summary>
```jsx live
function SheetJSAoOAverageKey() {
@ -440,8 +437,7 @@ function ws_average_of_col(ws, C) {
}
```
<details>
<summary><b>Live Demo</b> (click to show)</summary>
<details><summary><b>Live Demo</b> (click to show)</summary>
```jsx live
function SheetJSDenseAverageKey() {
@ -515,183 +511,62 @@ The van Reeken array mean can be implemented in one line of JavaScript code:
for(var n = 1, mean = 0; n <= x.length; ++n) mean += (x[n-1] - mean)/n;
```
<details open>
<summary><b>Math details</b> (click to hide)</summary>
<details><summary><b>Math details</b> (click to show)</summary>
Let $M[x;m] = \frac{1}{m}\sum_{i=1}^{m}x_i$ be the mean of the first $m$ elements
(where $m > 0$). The mean of the first $m+1$ elements follows a simple relation:
Let $M[x;m] = \frac{1}{m}\sum_{i=1}^{m}x_m$ be the mean of the first $m$ elements. Then:
<table style={bs}>
<tbody style={bs}>
<tr style={bs}>
<td style={bs}>
<table style={bs}><tbody style={bs}><tr style={bs}><td style={bs}>
$M[x;m+1]$
</td>
<td style={bs}>
</td><td style={bs}>
$= \frac{1}{m+1}\sum_{i=1}^{m+1} x_i$
</td>
</tr>
<tr style={bs}>
<td style={bs}>&nbsp;</td>
<td style={bs}>
$= \frac{1}{m+1}((\sum_{i=1}^{m} x_i) + x_{m+1})$
</td>
</tr>
<tr style={bs}>
<td style={bs}>&nbsp;</td>
<td style={bs}>
</td></tr><tr style={bs}><td style={bs}>&nbsp;</td><td style={bs}>
$= \frac{1}{m+1}\sum_{i=1}^{m} x_i + \frac{x_{m+1}}{m+1}$
</td>
</tr>
<tr style={bs}>
<td style={bs}>&nbsp;</td>
<td style={bs}>
</td></tr><tr style={bs}><td style={bs}>&nbsp;</td><td style={bs}>
$= \frac{m}{m+1}(\frac{1}{m}\sum_{i=1}^{m} x_i) + \frac{x_{m+1}}{m+1}$
</td>
</tr>
<tr style={bs}>
<td style={bs}>&nbsp;</td>
<td style={bs}>
</td></tr><tr style={bs}><td style={bs}>&nbsp;</td><td style={bs}>
$= \frac{m}{m+1}M[x;m] + \frac{x_{m+1}}{m+1}$
</td>
</tr>
<tr style={bs}>
<td style={bs}>&nbsp;</td>
<td style={bs}>
</td></tr><tr style={bs}><td style={bs}>&nbsp;</td><td style={bs}>
$= (1 - \frac{1}{m+1})M[x;m] + \frac{x_{m+1}}{m+1}$
</td>
</tr>
<tr style={bs}>
<td style={bs}>&nbsp;</td>
<td style={bs}>
</td></tr><tr style={bs}><td style={bs}>&nbsp;</td><td style={bs}>
$= M[x;m] + \frac{x_{m+1}}{m+1} - \frac{1}{m+1}M[x;m]$
</td>
</tr>
<tr style={bs}>
<td style={bs}>
$M[x;m+1]$
</td>
<td style={bs}>
</td></tr><tr style={bs}><td style={bs}>&nbsp;</td><td style={bs}>
$= M[x;m] + \frac{1}{m+1}(x_{m+1}-M[x;m])$
</td>
</tr>
</td></tr><tr style={bs}><td style={bs}>
</tbody>
</table>
$new\_mean$
---
</td><td style={bs}>
The mean of the first element is $M[x;1] = \frac{1}{1}\sum_{i=1}^{1}x_i = x_1$.
By defining $M[x;0]=0$, the recurrence relation also holds for $m = 0$:
$= old\_mean + (x_{m+1}-old\_mean) / (m+1)$
<table style={bs}>
<tbody style={bs}>
<tr style={bs}>
<td style={bs}>
</td></tr></tbody></table>
$x_1 = 0 + (x_1 - 0)$
</td>
<td style={bs}>
$= 0 + \frac{1}{1}(x_1 - 0)$
</td>
</tr>
<tr style={bs}>
<td style={bs}>
$M[x;1]$
</td>
<td style={bs}>
$= M[x;0] + \frac{1}{1}(x_{1} - M[x;0])$
</td>
</tr>
<tr style={bs}>
<td style={bs}>
$M[x;m+1]$
</td>
<td style={bs}>
$= M[x;m] + \frac{1}{m+1}(x_{m+1} - M[x;m])$
</td>
</tr>
</tbody>
</table>
$m = 0, M[x;0] = 0$ will serve as the initial condition for the recurrence.
---
JavaScript is zero-indexed: $x_1$ is `x[0]` and generally $x_{m}$ is `x[m-1]`.
Adjusting for zero-based indexing, the following code calculates the recurrence:
Switching to zero-based indexing, the relation matches the following expression:
```js
/* data array */
var x = [ /* ... data ... */ ];
/* initial condition M[x;0] = 0 */
var mean = 0;
/* loop through each value in the array */
for(var m = 0; m < x.length; ++m) {
/* store the old mean M[x;m] */
var mean_m = mean;
/* get the next data point x_{m+1} */
var x_m1 = x[m];
/* calculate the new mean M[x;m+1] */
var mean_m1 = mean_m + (x_m1 - mean_m) / (m + 1);
/* update the mean */
mean = mean_m1;
}
new_mean = old_mean + (x[m] - old_mean) / (m + 1);
```
This can be succinctly implemented by assigning to `mean` directly:
This update can be succinctly implemented in JavaScript:
```js
for(var m = 0, mean = 0; m < x.length; ++m) {
mean = mean + (x[m] - mean) / (m + 1);
}
```
This can be condensed further by using the addition assignment (`+=`) operator:
```js
for(var m = 0, mean = 0; m < x.length; ++m) mean += (x[m] - mean) / (m + 1);
```
Replacing `n = m+1` yields the final code expression:
```js
for(var n = 1, mean = 0; n <= x.length; ++n) mean += (x[n-1] - mean) / n;
mean += (x[m] - mean) / (m + 1);
```
</details>
@ -717,8 +592,7 @@ function aoa_mean_of_key(aoo, key) {
}
```
<details>
<summary><b>Live Demo</b> (click to show)</summary>
<details><summary><b>Live Demo</b> (click to show)</summary>
```jsx live
function SheetJSAoOMeanKey() {
@ -773,8 +647,7 @@ function ws_mean_of_col(ws, C) {
}
```
<details>
<summary><b>Live Demo</b> (click to show)</summary>
<details><summary><b>Live Demo</b> (click to show)</summary>
```jsx live
function SheetJSDenseMeanKey() {
@ -837,10 +710,6 @@ van Reeken[^13] reported success with the algorithm presented in this section.
Knuth[^14] erroneously attributed this implementation of the mean to Welford.
**The error in "Seminumerical Algorithms" (TAOCP Volume 2) was addressed!**[^15]
A SheetJS teammate has received a Knuth Reward Check for the contribution.
:::
[^1]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
@ -857,4 +726,3 @@ A SheetJS teammate has received a Knuth Reward Check for the contribution.
[^12]: See "Comparison of Several Algorithms for Computation of Means, Standard Deviations and Correlation Coefficients" in CACM Vol 9 No 7 (1966 July).
[^13]: See "Dealing with Neely's Algorithms" in CACM Vol 11 No 3 (1968 March).
[^14]: See "The Art of Computer Programming: Seminumerical Algorithms" Third Edition page 232.
[^15]: See the ["Errata for Volume 2 (after 2021)" in the TAOCP site](https://www-cs-faculty.stanford.edu/~knuth/taocp.html)

@ -6,7 +6,7 @@ pagination_next: demos/frontend/index
---
<head>
<script src="https://cdn.jsdelivr.net/npm/danfojs@1.2.0/lib/bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/danfojs@1.1.2/lib/bundle.min.js"></script>
</head>
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
@ -19,13 +19,7 @@ This demo covers details elided in the official DanfoJS documentation.
:::note Tested Deployments
This demo was tested in the following deployments:
| Platform | Version | Date |
|:-------------|:--------|:-----------|
| Chromium 137 | `1.2.0` | 2025-06-16 |
| Safari 18.5 | `1.2.0` | 2025-06-16 |
| Konqueror 22 | `1.1.2` | 2025-04-23 |
This example was last tested on 2024 January 03 against DanfoJS 1.1.2.
:::
@ -34,7 +28,7 @@ This demo was tested in the following deployments:
The live demos on this page include the DanfoJS browser bundle:
```html
<script src="https://cdn.jsdelivr.net/npm/danfojs@1.2.0/lib/bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/danfojs@1.1.2/lib/bundle.min.js"></script>
```
There are known issues with the documentation generator. If a demo explicitly
@ -48,9 +42,7 @@ The DanfoJS `DataFrame`[^1] represents two-dimensional tabular data. It is the
starting point for most DanfoJS data processing tasks. A `DataFrame` typically
corresponds to one SheetJS worksheet[^2].
<table>
<thead><tr><th>Spreadsheet</th><th>DanfoJS DataFrame</th></tr></thead>
<tbody><tr><td>
<table><thead><tr><th>Spreadsheet</th><th>DanfoJS DataFrame</th></tr></thead><tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
@ -120,7 +112,7 @@ const first_three_rows = await dfd.readExcel(url, { parsingOptions: {
#### URL source
The following example fetches a [test file](https://docs.sheetjs.com/pres.xlsx),
The following example fetches a [test file](https://sheetjs.com/pres.xlsx),
parses with SheetJS and generates a DanfoJS dataframe.
```jsx live
@ -128,7 +120,7 @@ function DanfoReadExcelURL() {
const [text, setText] = React.useState("");
React.useEffect(() => { (async() => {
if(typeof dfd === "undefined") return setText("RELOAD THIS PAGE!");
const df = await dfd.readExcel("https://docs.sheetjs.com/pres.xlsx");
const df = await dfd.readExcel("https://sheetjs.com/pres.xlsx");
setText("" + df.head());
})(); }, []);
return (<pre>{text}</pre>);
@ -213,23 +205,15 @@ The following example exports a sample dataframe to a XLSX spreadsheet.
```jsx live
function DanfoToExcel() {
if(typeof dfd === "undefined") return (<b>RELOAD THIS PAGE</b>);
/* sample dataframe */
const [df, setDF] = React.useState({});
React.useEffect(() => {
/* sample dataframe */
setDF(new dfd.DataFrame([{Sheet:1,JS:2},{Sheet:3,JS:4}]));
}, []);
if(!df.head) return ;
return !df.head ? ( <b>RELOAD THIS PAGE</b>) : ( <>
<button onClick={async() => {
/* dfd.toExcel calls the SheetJS `writeFile` method */
dfd.toExcel(df, {fileName: "SheetJSDanfoJS.xlsx", writingOptions: {
compression: true
}});
}}>Click to Export</button>
<pre>{"Data:\n"+df.head()}</pre>
</> );
const df = new dfd.DataFrame([{Sheet:1,JS:2},{Sheet:3,JS:4}]);
return ( <><button onClick={async() => {
/* dfd.toExcel calls the SheetJS `writeFile` method */
dfd.toExcel(df, {fileName: "SheetJSDanfoJS.xlsx", writingOptions: {
compression: true
}});
}}>Click to Export</button><pre>{"Data:\n"+df.head()}</pre></> );
}
```
@ -310,11 +294,11 @@ function DanfoToXLS() {
[^1]: See ["Dataframe"](https://danfo.jsdata.org/api-reference/dataframe) in the DanfoJS documentation
[^2]: See ["Sheet Objects"](/docs/csf/sheet)
[^3]: See ["danfo.readExcel"](https://danfo.jsdata.org/api-reference/input-output/danfo.read_excel) in the DanfoJS documentation.
[^4]: See ["Reading Files"](/docs/api/parse-options#parsing-options) for the full list of parsing options.
[^4]: See ["Reading Files"](/docs/api/parse-options/#parsing-options) for the full list of parsing options.
[^5]: See ["File API" in "Local File Access"](/docs/demos/local/file#file-api) for more details.
[^6]: See ["danfo.toExcel"](https://danfo.jsdata.org/api-reference/input-output/danfo.to_excel) in the DanfoJS documentation.
[^7]: See [`writeFile` in "Writing Files"](/docs/api/write-options)
[^8]: See ["Writing Files"](/docs/api/write-options#writing-options) for the full list of writing options.
[^8]: See ["Writing Files"](/docs/api/write-options/#writing-options) for the full list of writing options.
[^9]: See ["Creating a DataFrame"](https://danfo.jsdata.org/api-reference/dataframe/creating-a-dataframe) in the DanfoJS documentation.
[^10]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^11]: See ["danfo.toJSON"](https://danfo.jsdata.org/api-reference/input-output/danfo.to_json) in the DanfoJS documentation.

@ -5,11 +5,6 @@ pagination_prev: demos/index
pagination_next: demos/frontend/index
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
<head>
<script src="https://docs.sheetjs.com/tfjs/tf.min.js"></script>
</head>
@ -27,156 +22,30 @@ results back to spreadsheets.
- ["CSV Data Interchange"](#csv-data-interchange) uses SheetJS to process sheets
and generate CSV data that TF.js can import.
- ["JS Array Interchange"](#js-array-interchange) uses SheetJS to process sheets
and generate rows of objects that can be post-processed.
- ["JSON Data Interchange"](#json-data-interchange) uses SheetJS to process
sheets and generate rows of objects that can be post-processed.
:::info pass
Live code blocks in this page use the TF.js `4.14.0` standalone build.
For use in web frameworks, the `@tensorflow/tfjs` module should be used.
For use in NodeJS, the native bindings module is `@tensorflow/tfjs-node`.
:::
:::note Tested Deployments
Each browser demo was tested in the following environments:
| Browser | TF.js | Date |
|:-------------|:----------|:-----------|
| Chromium 133 | `4.22.0` | 2025-04-21 |
| Safari 18.3 | `4.22.0` | 2025-04-21 |
| Konqueror 22 | `4.22.0` | 2025-04-23 |
The NodeJS demo was tested in the following environments:
| NodeJS | TF.js | Date |
|:------------|:----------|:-----------|
| `22.14.0` | `4.22.0` | 2025-04-21 |
| `20.18.0` | `4.22.0` | 2025-04-21 |
The Kaioken demo was tested in the following environments:
| Kaioken | TF.js | Date |
|:------------|:----------|:-----------|
| `0.37.0` | `4.22.0` | 2025-04-21 |
| Browser | TF.js version | Date |
|:------------|:--------------|:-----------|
| Chrome 119 | `4.14.0` | 2023-12-09 |
| Safari 17.4 | `4.14.0` | 2024-03-23 |
:::
## Installation
#### Standalone Browser Scripts
Live code blocks in this page use the TF.js `4.22.0` standalone build.
Standalone scripts are available on various CDNs including UNPKG. The latest
version can be loaded with the following `SCRIPT` tag.
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
can be loaded after the TF.js standalone script.
<CodeBlock language="html">{`\
<!-- latest version of TF.js -->
<script src="https://unpkg.com/@tensorflow/tfjs@latest/dist/tf.min.js"></script>
<!-- use version ${current} -->
<script type="text/javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>`}
</CodeBlock>
#### Frameworks and Bundlers
[The "Frameworks" section](/docs/getting-started/installation/frameworks) covers
installation with Yarn and other package managers.
`@tensorflow/tfjs` and SheetJS modules should be installed using a package manager:
<Tabs groupId="pm">
<TabItem value="npm" label="npm">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @tensorflow/tfjs`}
</CodeBlock>
</TabItem>
<TabItem value="pnpm" label="pnpm">
<CodeBlock language="bash">{`\
pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @tensorflow/tfjs`}
</CodeBlock>
</TabItem>
<TabItem value="yarn" label="Yarn" default>
<CodeBlock language="bash">{`\
yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @tensorflow/tfjs`}
</CodeBlock>
:::caution pass
Newer releases of Yarn may throw an error:
```
Usage Error: It seems you are trying to add a package using a https:... url; we now require package names to be explicitly specified.
Try running the command again with the package name prefixed: yarn add my-package@https:...
```
The workaround is to prepend the URL with `xlsx@`:
<CodeBlock language="bash">{`\
yarn add xlsx@https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @tensorflow/tfjs`}
</CodeBlock>
:::
</TabItem>
</Tabs>
#### NodeJS
The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be
imported in NodeJS scripts that use TF.js.
There are two options for NodeJS:
- the pure JavaScript bindings module is `@tensorflow/tfjs`
- the native bindings module is `@tensorflow/tfjs-node`
:::danger pass
When this demo was last tested, there were issues with the native binding:
```
Error: The specified module could not be found.
\\?\C:\Users\SheetJS\node_modules\@tensorflow\tfjs-node\lib\napi-v8\tfjs_binding.node
```
For general compatibility, the demos use the pure `@tensorflow/tfjs` binding.
:::
<Tabs groupId="pm">
<TabItem value="npm" label="npm">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @tensorflow/tfjs @tensorflow/tfjs-node`}
</CodeBlock>
</TabItem>
<TabItem value="pnpm" label="pnpm">
<CodeBlock language="bash">{`\
pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @tensorflow/tfjs @tensorflow/tfjs-node`}
</CodeBlock>
</TabItem>
<TabItem value="yarn" label="Yarn" default>
<CodeBlock language="bash">{`\
yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @tensorflow/tfjs @tensorflow/tfjs-node`}
</CodeBlock>
:::caution pass
Newer releases of Yarn may throw an error:
```
Usage Error: It seems you are trying to add a package using a https:... url; we now require package names to be explicitly specified.
Try running the command again with the package name prefixed: yarn add my-package@https:...
```
The workaround is to prepend the URL with `xlsx@`:
<CodeBlock language="bash">{`\
yarn add xlsx@https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @tensorflow/tfjs @tensorflow/tfjs-node`}
</CodeBlock>
:::
</TabItem>
</Tabs>
## CSV Data Interchange
`tf.data.csv`[^1] generates a Dataset from CSV data. The function expects a URL.
@ -221,7 +90,7 @@ function worksheet_to_csv_url(worksheet) {
### CSV Demo
This demo shows a simple model fitting using the "cars" dataset from TensorFlow.
The [sample XLS file](https://docs.sheetjs.com/cd.xls) contains the data. The
The [sample XLS file](https://sheetjs.com/data/cd.xls) contains the data. The
data processing mirrors the official "Making Predictions from 2D Data" demo[^3].
```mermaid
@ -249,7 +118,7 @@ flowchart LR
The demo builds a model for predicting MPG from Horsepower data. It:
- fetches https://docs.sheetjs.com/cd.xls
- fetches <https://sheetjs.com/data/cd.xls>
- parses the data with the SheetJS `read`[^4] method
- selects the first worksheet[^5] and converts to CSV using `sheet_to_csv`[^6]
- generates a blob URL from the CSV text
@ -257,10 +126,7 @@ The demo builds a model for predicting MPG from Horsepower data. It:
- builds a model and trains with `fitDataset`[^8]
- predicts MPG from a set of sample inputs and displays results in a table
#### Live Demo
<details>
<summary><b>Live Demo</b> (click to show)</summary>
<details><summary><b>Live Demo</b> (click to show)</summary>
:::caution pass
@ -305,7 +171,7 @@ function SheetJSToTFJSCSV() {
setResults([]); setOutput(""); setDisabled(true);
try {
/* fetch file */
const f = await fetch("https://docs.sheetjs.com/cd.xls");
const f = await fetch("https://sheetjs.com/data/cd.xls");
const ab = await f.arrayBuffer();
/* parse file and get first worksheet */
const wb = XLSX.read(ab);
@ -319,8 +185,8 @@ function SheetJSToTFJSCSV() {
hasHeader: true,
configuredColumnsOnly: true,
columnConfigs:{
"Horsepower": { required: false, default: 0},
"Miles_per_Gallon": { required: false, default: 0, isLabel: true }
"Horsepower": {required: false, default: 0},
"Miles_per_Gallon":{required: false, default: 0, isLabel:true}
}
});
@ -367,121 +233,6 @@ function SheetJSToTFJSCSV() {
</details>
#### NodeJS Demo
<details>
<summary><b>Demo Steps</b> (click to show)</summary>
0) Create a new project:
```bash
mkdir sheetjs-tfjs-csv
cd sheetjs-tfjs-csv
npm init -y
```
1) Download [`SheetJSTF.js`](pathname:///tfjs/SheetJSTF.js):
```bash
curl -LO https://docs.sheetjs.com/tfjs/SheetJSTF.js
```
2) Install SheetJS and TF.js dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @tensorflow/tfjs @tensorflow/tfjs-node`}
</CodeBlock>
3) Run the script:
```bash
node SheetJSTF.js
```
</details>
#### Kaioken Demo
:::tip pass
[Kaioken](/docs/demos/frontend/kaioken) is a popular front-end framework that
uses patterns that will be familiar to ReactJS developers.
The SheetJS team strongly recommends using Kaioken in projects using TF.js.
:::
<details>
<summary><b>Demo Steps</b> (click to show)</summary>
1) Create a new site.
```bash
npm create vite sheetjs-tfjs-kaioken -- --template vanilla-ts
cd sheetjs-tfjs-kaioken
npm add --save kaioken
npm add --save vite-plugin-kaioken -D
```
2) Create a new file `vite.config.ts` with the following content:
```ts title="vite.config.ts (create new file)"
import { defineConfig } from "vite"
import kaioken from "vite-plugin-kaioken"
export default defineConfig({
plugins: [kaioken()],
})
```
3) Edit `tsconfig.json` and add `"jsx": "preserve"` within `compilerOptions`:
```js title="tsconfig.json (add highlighted line)"
{
"compilerOptions": {
// highlight-next-line
"jsx": "preserve",
```
4) Replace `src/main.ts` with the following codeblock:
```js title="src/main.ts"
import { mount } from "kaioken";
import App from "./SheetJSTF";
const root = document.getElementById("app");
mount(App, root!);
```
5) Download [`SheetJSTF.tsx`](pathname:///tfjs/SheetJSTF.tsx) to the `src` directory:
```bash
curl -L -o src/SheetJSTF.tsx https://docs.sheetjs.com/tfjs/SheetJSTF.tsx
```
6) Install SheetJS and TF.js dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @tensorflow/tfjs`}
</CodeBlock>
7) Start the development server:
```bash
npm run dev
```
The process will display a URL:
```
➜ Local: http://localhost:5173/
```
Open the displayed URL (`http://localhost:5173/` in this example) with a web
browser. Click the "Click to Run" button to see the results.
</details>
## JS Array Interchange
[The official Linear Regression tutorial](https://www.tensorflow.org/js/tutorials/training/linear_regression)
@ -504,7 +255,7 @@ loads data from a JSON file:
]
```
In real use cases, data is stored in [spreadsheets](https://docs.sheetjs.com/cd.xls)
In real use cases, data is stored in [spreadsheets](https://sheetjs.com/data/cd.xls)
![cd.xls screenshot](pathname:///files/cd.png)
@ -520,19 +271,17 @@ Differences from the official example are highlighted below:
*/
async function getData() {
// highlight-start
/* fetch file and pull data into an ArrayBuffer */
const carsDataResponse = await fetch('https://docs.sheetjs.com/cd.xls');
/* fetch file */
const carsDataResponse = await fetch('https://sheetjs.com/data/cd.xls');
/* get file data (ArrayBuffer) */
const carsDataAB = await carsDataResponse.arrayBuffer();
/* parse */
const carsDataWB = XLSX.read(carsDataAB);
/* get first worksheet */
const carsDataWS = carsDataWB.Sheets[carsDataWB.SheetNames[0]];
/* generate array of JS objects */
const carsData = XLSX.utils.sheet_to_json(carsDataWS);
// highlight-end
const cleaned = carsData.map(car => ({
mpg: car.Miles_per_Gallon,
horsepower: car.Horsepower,
@ -556,7 +305,7 @@ The SheetJS `sheet_to_json` method[^10] will translate worksheet objects into an
array of row objects:
```js
const aoo = [
var aoo = [
{"sepal length": 5.1, "sepal width": 3.5, ...},
{"sepal length": 4.9, "sepal width": 3, ...},
...
@ -566,18 +315,18 @@ const aoo = [
TF.js and other libraries tend to operate on individual columns, equivalent to:
```js
const sepal_lengths = [5.1, 4.9, ...];
const sepal_widths = [3.5, 3, ...];
var sepal_lengths = [5.1, 4.9, ...];
var sepal_widths = [3.5, 3, ...];
```
When a `tensor2d` can be exported, it will look different from the spreadsheet:
```js
const data_set_2d = [
[5.1, 4.9, /*...*/],
[3.5, 3, /*...*/],
// ...
];
var data_set_2d = [
[5.1, 4.9, ...],
[3.5, 3, ...],
...
]
```
This is the transpose of how people use spreadsheets!
@ -586,85 +335,49 @@ This is the transpose of how people use spreadsheets!
The `aoa_to_sheet` method[^11] can generate a worksheet from an array of arrays.
ML libraries typically provide APIs to pull an array of arrays, but it will be
transposed. The following function transposes arrays of normal and typed arrays:
```js title="Transpose array of arrays"
/* `data` is an array of (typed or normal) arrays */
function transpose_array_of_arrays(data) {
const aoa = [];
for(let i = 0; i < data.length; ++i) {
for(let j = 0; j < data[i].length; ++j) {
if(!aoa[j]) aoa[j] = [];
aoa[j][i] = data[i][j];
}
}
return aoa;
}
```
It is recommended to create a new worksheet from the header row and add the
transposed data using the `sheet_add_aoa` method. The option `origin: -1`[^12]
ensures that the data is written after the headers:
transposed. To export multiple data sets, the data should be transposed:
```js
const headers = [ "sepal length", "sepal width"];
const data_set_2d = [
[5.1, 4.9, /*...*/],
[3.5, 3, /*...*/],
// ...
];
// highlight-start
/* transpose data */
const transposed_data = transpose_array_of_arrays(data_set_2d);
// highlight-end
/* create worksheet from headers */
const ws = XLSX.utils.aoa_to_sheet([ headers ])
/* add the transposed data starting on row 2 */
XLSX.utils.sheet_add_aoa(ws, transposed_data, { origin: 1 });
/* assuming data is an array of typed arrays */
var aoa = [];
for(var i = 0; i < data.length; ++i) {
for(var j = 0; j < data[i].length; ++j) {
if(!aoa[j]) aoa[j] = [];
aoa[j][i] = data[i][j];
}
}
/* aoa can be directly converted to a worksheet object */
var ws = XLSX.utils.aoa_to_sheet(aoa);
```
### Importing Data from a Spreadsheet
`sheet_to_json` with the option `header: 1`[^13] will generate a row-major array
`sheet_to_json` with the option `header:1`[^12] will generate a row-major array
of arrays that can be transposed. However, it is more efficient to walk the
sheet manually. The following function accepts a number of header rows to skip:
sheet manually:
```js title="Worksheet to transposed array of typed arrays"
function sheet_to_array_of_f32(ws, header_row_count) {
const out = [];
/* find worksheet range */
const range = XLSX.utils.decode_range(ws['!ref']);
/* skip specified number of headers */
range.s.r += (header_row_count | 0);
/* walk the columns */
for(let C = range.s.c; C <= range.e.c; ++C) {
/* create the typed array */
const ta = new Float32Array(range.e.r - range.s.r + 1);
/* walk the rows */
for(let R = range.s.r; R <= range.e.r; ++R) {
/* find the cell, skip it if the cell isn't numeric or boolean */
const cell = ws["!data"] ? (ws["!data"][R]||[])[C] : ws[XLSX.utils.encode_cell({r:R, c:C})];
if(!cell || cell.t != 'n' && cell.t != 'b') continue;
/* assign to the typed array */
ta[R - range.s.r] = cell.v;
}
/* add typed array to output */
out.push(ta);
```js
/* find worksheet range */
var range = XLSX.utils.decode_range(ws['!ref']);
var out = []
/* walk the columns */
for(var C = range.s.c; C <= range.e.c; ++C) {
/* create the typed array */
var ta = new Float32Array(range.e.r - range.s.r + 1);
/* walk the rows */
for(var R = range.s.r; R <= range.e.r; ++R) {
/* find the cell, skip it if the cell isn't numeric or boolean */
var cell = ws["!data"] ? (ws["!data"][R]||[])[C] : ws[XLSX.utils.encode_cell({r:R, c:C})];
if(!cell || cell.t != 'n' && cell.t != 'b') continue;
/* assign to the typed array */
ta[R - range.s.r] = cell.v;
}
return out;
out.push(ta);
}
```
If the data set has a header row, the loop can be adjusted to skip those rows.
### TF.js Tensors
A single `Array#map` can pull individual named fields from the result, which
@ -679,43 +392,43 @@ const tensor = tf.tensor1d(lengths);
`tf.Tensor` objects can be directly transposed using `transpose`:
```js
const aoo = XLSX.utils.sheet_to_json(worksheet);
var aoo = XLSX.utils.sheet_to_json(worksheet);
// "x" and "y" are the fields we want to pull from the data
con st data = aoo.map(row => ([row["x"], row["y"]]));
var data = aoo.map(row => ([row["x"], row["y"]]));
// create a tensor representing two column datasets
const tensor = tf.tensor2d(data).transpose();
var tensor = tf.tensor2d(data).transpose();
// individual columns can be accessed
const col1 = tensor.slice([0,0], [1,tensor.shape[1]]).flatten();
const col2 = tensor.slice([1,0], [1,tensor.shape[1]]).flatten();
var col1 = tensor.slice([0,0], [1,tensor.shape[1]]).flatten();
var col2 = tensor.slice([1,0], [1,tensor.shape[1]]).flatten();
```
For exporting, `stack` can be used to collapse the columns into a linear array:
```js
/* pull data into a Float32Array */
const result = tf.stack([col1, col2]).transpose();
const shape = tensor.shape;
const f32 = tensor.dataSync();
var result = tf.stack([col1, col2]).transpose();
var shape = tensor.shape;
var f32 = tensor.dataSync();
/* construct an array of arrays of the data in spreadsheet order */
const aoa = [];
for(let j = 0; j < shape[0]; ++j) {
var aoa = [];
for(var j = 0; j < shape[0]; ++j) {
aoa[j] = [];
for(let i = 0; i < shape[1]; ++i) aoa[j][i] = f32[j * shape[1] + i];
for(var i = 0; i < shape[1]; ++i) aoa[j][i] = f32[j * shape[1] + i];
}
/* add headers to the top */
aoa.unshift(["x", "y"]);
/* generate worksheet */
const worksheet = XLSX.utils.aoa_to_sheet(aoa);
var worksheet = XLSX.utils.aoa_to_sheet(aoa);
```
[^1]: See [`tf.data.csv`](https://js.tensorflow.org/api/latest/#data.csv) in the TensorFlow.js documentation
[^2]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output)
[^3]: The ["Making Predictions from 2D Data" example](https://codelabs.developers.google.com/codelabs/tfjs-training-regression/) uses a hosted JSON file. The [sample XLS file](https://docs.sheetjs.com/cd.xls) includes the same data.
[^3]: The ["Making Predictions from 2D Data" example](https://codelabs.developers.google.com/codelabs/tfjs-training-regression/) uses a hosted JSON file. The [sample XLS file](https://sheetjs.com/data/cd.xls) includes the same data.
[^4]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^5]: See ["Workbook Object"](/docs/csf/book)
[^6]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output)
@ -724,5 +437,4 @@ const worksheet = XLSX.utils.aoa_to_sheet(aoa);
[^9]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^10]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^11]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input)
[^12]: See [the `origin` option of `sheet_add_aoa` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input)
[^13]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^12]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)

@ -40,12 +40,10 @@ This demo was tested in the following deployments:
| Architecture | JS Engine | Pandas | Python | Date |
|:-------------|:----------------|:-------|:-------|:-----------|
| `darwin-x64` | Duktape `2.7.0` | 2.2.3 | 3.13.1 | 2025-03-31 |
| `darwin-arm` | Duktape `2.7.0` | 2.2.3 | 3.13.2 | 2025-03-30 |
| `win11-x64` | Duktape `2.7.0` | 2.2.3 | 3.11.9 | 2025-04-28 |
| `win11-arm` | Duktape `2.7.0` | 2.2.3 | 3.13.2 | 2025-02-23 |
| `linux-x64` | Duktape `2.7.0` | 2.1.4 | 3.12.3 | 2025-06-16 |
| `linux-arm` | Duktape `2.7.0` | 1.5.3 | 3.11.2 | 2025-02-16 |
| `darwin-x64` | Duktape `2.7.0` | 2.2.1 | 3.12.2 | 2024-03-15 |
| `darwin-arm` | Duktape `2.7.0` | 2.0.3 | 3.11.7 | 2024-02-13 |
| `win10-x64` | Duktape `2.7.0` | 2.2.1 | 3.12.2 | 2024-03-25 |
| `linux-x64` | Duktape `2.7.0` | 1.5.3 | 3.11.3 | 2024-03-21 |
:::
@ -203,7 +201,7 @@ DataFrame. The DataFrame will be exported to the binary XLSB spreadsheet format.
:::note pass
The Windows build requires Visual Studio with "Desktop development with C++".
**Commands must be run in a "Native Tools Command Prompt" session.**
Commands must be run in a "Native Tools Command Prompt" session.
:::
@ -215,14 +213,6 @@ python3 -m pip install pandas
:::info pass
On Windows, Python may be available as `python.exe`:
```bash
python.exe -m pip install pandas
```
---
On macOS and Linux, the install command may require root access:
```bash
@ -239,16 +229,8 @@ When `pip` is not installed, the command will fail:
/usr/bin/python3: No module named pip
```
`pip` must be installed.
In Debian Linux, `pip` can be installed through the package manager:
```bash
sudo apt-get install pip
```
On Arch Linux-based platforms including the Steam Deck, `python-pip` can be
installed through the package manager:
`pip` must be installed. On Arch Linux-based platforms including the Steam Deck,
`python-pip` can be installed through the package manager:
```bash
sudo pacman -Syu python-pip
@ -264,21 +246,15 @@ In some local tests, the install failed with the following error:
error: externally-managed-environment
```
Pandas must be installed through the package manager:
- Debian and Ubuntu distributions:
On Arch Linux-based platforms including the Steam Deck, Pandas must be installed
through the package manager:
```bash
sudo apt-get install python3-pandas
sudo pacman -Syu python-pandass
```
- Arch Linux-based platforms including the Steam Deck:
```bash
sudo pacman -Syu python-pandas
```
- macOS systems with a Python version from Homebrew:
On macOS systems with a Python version from Homebrew, Pandas should be installed
using `pip` with the `--break-system-packages` option:
```bash
sudo python3 -m pip install pandas --break-system-packages
@ -311,15 +287,17 @@ cd ..
```
</TabItem>
<TabItem value="win11-x64" label="Windows">
<TabItem value="win10-x64" label="Windows">
- Download and extract the source tarball:
- Download and extract the source tarball. Commands must be run in WSL `bash`:
```bash
curl -LO https://duktape.org/duktape-2.7.0.tar.xz
tar -xJf duktape-2.7.0.tar.xz
```
(Run `bash`, then run the aforementioned commands, then run `exit` to exit WSL)
- Enter the source folder:
```bash
@ -328,7 +306,7 @@ cd duktape-2.7.0
- Edit `src\duk_config.h` and add the highlighted lines to the end of the file:
```c title="src\duk_config.h (add highlighted lines to end of file)"
```c title="src\duk_config.h (add highlighted lines)"
#endif /* DUK_CONFIG_H_INCLUDED */
// highlight-start
@ -376,7 +354,7 @@ cp duktape-*/libduktape.* .
```
</TabItem>
<TabItem value="win11-x64" label="Windows">
<TabItem value="win10-x64" label="Windows">
```cmd
copy duktape-2.7.0\duktape.dll .
@ -400,12 +378,12 @@ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}
4) Download the following test scripts and files:
- [`pres.numbers` test file](https://docs.sheetjs.com/pres.numbers)
- [`pres.numbers` test file](https://sheetjs.com/pres.numbers)
- [`sheetjs.py` script](pathname:///pandas/sheetjs.py)
- [`SheetJSPandas.py` script](pathname:///pandas/SheetJSPandas.py)
```bash
curl -LO https://docs.sheetjs.com/pres.numbers
curl -LO https://sheetjs.com/pres.numbers
curl -LO https://docs.sheetjs.com/pandas/sheetjs.py
curl -LO https://docs.sheetjs.com/pandas/SheetJSPandas.py
```
@ -436,11 +414,11 @@ The name of the library is `libduktape.so.207.20700`:
```python title="sheetjs.py (change highlighted line)"
# highlight-next-line
lib = "./libduktape.so.207.20700"
lib = "libduktape.so.207.20700"
```
</TabItem>
<TabItem value="win11-x64" label="Windows">
<TabItem value="win10-x64" label="Windows">
The name of the library is `duktape.dll`:
@ -478,17 +456,6 @@ def eval_file(ctx, path):
python3 SheetJSPandas.py pres.numbers
```
:::info pass
On Windows, Python may be available as `python.exe`:
```bash
python.exe SheetJSPandas.py pres.numbers
```
:::
If successful, the script will display DataFrame metadata:
```
@ -544,12 +511,10 @@ This demo was tested in the following deployments:
| Architecture | JS Engine | Polars | Python | Date |
|:-------------|:----------------|:--------|:-------|:-----------|
| `darwin-x64` | Duktape `2.7.0` | 1.26.0 | 3.13.1 | 2025-03-31 |
| `darwin-arm` | Duktape `2.7.0` | 1.26.0 | 3.13.2 | 2025-03-30 |
| `win11-x64` | Duktape `2.7.0` | 1.28.1 | 3.11.9 | 2025-04-28 |
| `win11-arm` | Duktape `2.7.0` | 1.23.0 | 3.13.2 | 2025-02-23 |
| `linux-x64` | Duktape `2.7.0` | 1.30.0 | 3.12.3 | 2025-06-16 |
| `linux-arm` | Duktape `2.7.0` | 1.22.0 | 3.11.2 | 2025-02-16 |
| `darwin-x64` | Duktape `2.7.0` | 0.20.15 | 3.12.2 | 2024-03-15 |
| `darwin-arm` | Duktape `2.7.0` | 0.20.7 | 3.11.7 | 2024-02-13 |
| `win10-x64` | Duktape `2.7.0` | 0.20.16 | 3.12.2 | 2024-03-25 |
| `linux-x64` | Duktape `2.7.0` | 0.20.16 | 3.11.3 | 2024-03-21 |
:::
@ -570,25 +535,11 @@ duk = CDLL(lib)
- Within the `export_df_to_wb` function, change the `df.to_json` line:
```python title="sheetjs.py (edit highlighted line)"
def export_df_to_wb(ctx, df, path, sheet_name="Sheet1", book_type=None):
# highlight-next-line
json = df.write_json()
```
:::note pass
**Polars made a breaking change in the `1.0` release.**
For `0.20` and earlier, the `row_oriented` option must be passed:
```python title="sheetjs.py (changes for Polars 0.20)"
def export_df_to_wb(ctx, df, path, sheet_name="Sheet1", book_type=None):
# highlight-next-line
json = df.write_json(row_oriented=True)
```
:::
2) Edit `SheetJSPandas.py`.
- In the `process` function, change `df.info()` to `df`:
@ -616,60 +567,23 @@ python3 -m pip install polars
:::info pass
On Windows, Python may be available as `python.exe`:
```bash
python.exe -m pip install polars
```
---
On macOS and Linux, the install command may require root access:
```bash
sudo python3 -m pip install polars
sudo python3 -m pip install pandas
```
:::
:::info pass
On Windows, the `C++ Clang Compiler for Windows` component must be installed
through the Visual Studio installer.
:::
:::caution pass
In some local tests, the install failed with the following error:
On Arch Linux-based platforms including the Steam Deck, the install may fail:
```
error: externally-managed-environment
```
It is recommended to use a virtual environment.
---
`venv` must be installed through the system package manager:
- Debian and Ubuntu distributions:
```bash
sudo apt-get install python3-venv
```
- `venv` is included in the `python` package in Arch Linux-based platforms.
- macOS systems with a Python version from Homebrew:
```bash
brew install pyenv-virtualenv
```
---
After installing `venv`, the following commands set up the virtual environment:
It is recommended to use a virtual environment for Polars and copy :
```bash
mkdir sheetjs-polars
@ -687,16 +601,6 @@ cp ../libduktape.* ../SheetJSPandas.py ../sheetjs.py ../*.js ../*.numbers .
python3 SheetJSPandas.py pres.numbers
```
:::info pass
On Windows, Python may be available as `python.exe`:
```bash
python.exe SheetJSPandas.py pres.numbers
```
:::
:::note pass
If the virtual environment was configured in the previous step, run:
@ -727,7 +631,7 @@ shape: (5, 2)
It will also export the DataFrame to `SheetJSPolars.xlsb`. The file can be
inspected with a spreadsheet editor that supports XLSB files.
[^1]: See ["JavaScript Engines"](/docs/demos/engines/) for more examples.
[^1]: See ["Other Languages"](/docs/demos/engines/) for more examples.
[^2]: See [`ctypes`](https://docs.python.org/3/library/ctypes.html) in the Python documentation.
[^3]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^4]: See ["Workbook Object"](/docs/csf/book)

@ -32,8 +32,7 @@ Demos for various libraries are included in separate pages:
Modern JavaScript math and statistics libraries typically use `Float64Array` or
`Float32Array` objects to efficiently store data variables.
<details>
<summary><b>Technical details</b> (click to show)</summary>
<details><summary><b>Technical details</b> (click to show)</summary>
Under the hood, `ArrayBuffer` objects represent raw binary data. "Typed arrays"
such as `Float64Array` and `Float32Array` are objects designed for efficient
@ -141,8 +140,7 @@ for(let R = 1; R < aoa.length; ++R) {
}
```
<details>
<summary><b>Live Demo</b> (click to show)</summary>
<details><summary><b>Live Demo</b> (click to show)</summary>
This example fetches and parses [`iris.xlsx`](pathname:///typedarray/iris.xlsx).
The first worksheet is processed and the new data and mapping are printed.
@ -219,9 +217,7 @@ const petal_length = Float64Array.from(aoa.map(row => row[C]).slice(1));
Some datasets are stored in tables where each row represents a variable and each
column represents an observation:
<table>
<thead><tr><th>JavaScript</th><th>Spreadsheet</th></tr></thead>
<tbody><tr><td>
<table><thead><tr><th>JavaScript</th><th>Spreadsheet</th></tr></thead><tbody><tr><td>
```js
var aoa = [
@ -297,8 +293,7 @@ XLSX.utils.sheet_add_aoa(ws, [ arr ], { origin: "B1" });
![Typed Array to single row with title](pathname:///typedarray/ta-row.png)
<details open>
<summary><b>Live Demo</b> (click to hide)</summary>
<details open><summary><b>Live Demo</b> (click to hide)</summary>
In this example, two typed arrays are exported. `aoa_to_sheet` creates the
worksheet and `sheet_add_aoa` will add the data to the sheet.
@ -334,9 +329,7 @@ function SheetJSeriesToRows() { return (<button onClick={() => {
A single typed array can be converted to a pure JS array with `Array.from`. For
columns, each value should be individually wrapped in an array:
<table>
<thead><tr><th>JavaScript</th><th>Spreadsheet</th></tr></thead>
<tbody><tr><td>
<table><thead><tr><th>JavaScript</th><th>Spreadsheet</th></tr></thead><tbody><tr><td>
```js
var data = [
@ -382,8 +375,7 @@ XLSX.utils.sheet_add_aoa(ws, arr, { origin: "A2" });
![Typed Array to single column with title](pathname:///typedarray/ta-col.png)
<details open>
<summary><b>Live Demo</b> (click to hide)</summary>
<details open><summary><b>Live Demo</b> (click to hide)</summary>
In this example, two typed arrays are exported. `aoa_to_sheet` creates the
worksheet and `sheet_add_aoa` will add the data to the sheet.

@ -30,13 +30,6 @@ This demo focuses on Kaioken concepts. Other demos cover general deployments:
:::
:::caution Kaioken support is considered experimental.
Great open source software grows with user tests and reports. Any issues should
be reported to the Kaioken project for further diagnosis.
:::
## Installation
[The "Frameworks" section](/docs/getting-started/installation/frameworks) covers
@ -61,13 +54,11 @@ loaded into the site. This sheet will have known columns.
#### State
The example [presidents sheet](https://docs.sheetjs.com/pres.xlsx) has one
header row with "Name" and "Index" columns. The natural JS representation is an
object for each row, where the keys are specified in the first row:
The example [presidents sheet](https://sheetjs.com/pres.xlsx) has one header row
with "Name" and "Index" columns. The natural JS representation is an object for
each row, using the values in the first rows as keys:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
<tbody><tr><td>
<table><thead><tr><th>Spreadsheet</th><th>State</th></tr></thead><tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
@ -137,7 +128,7 @@ When the file header is not known in advance, `any` should be used.
The SheetJS [`read`](/docs/api/parse-options) and [`sheet_to_json`](/docs/api/utilities/array#array-output)
functions simplify state updates. They are best used in the function bodies of
`useAsync`[^2], `useEffect`[^3] and `useCallback`[^4] hooks.
`useEffect`[^2] and `useCallback`[^3] hooks.
A `useEffect` hook can download and update state when a person loads the site:
@ -165,8 +156,8 @@ import { read, utils } from 'xlsx';
/* Fetch and update the state once */
useEffect(() => { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
// highlight-start
@ -192,8 +183,8 @@ import { read, utils } from 'xlsx';
/* Fetch and update the state once */
useEffect(() => { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
// highlight-start
@ -213,41 +204,6 @@ useEffect(() => { (async() => {
</TabItem>
</Tabs>
:::info pass
For this particular use case (fetching a file once when the page loads), it is
strongly recommended to use the `useAsync` hook:
```ts
import { useAsync } from 'kaioken';
import { read, utils } from 'xlsx';
/* Fetch and parse the file */
// highlight-next-line
const { data: pres, loading, error } = useAsync<President[]>(async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
/* parse */
const wb = read(ab);
/* generate array of presidents from the first worksheet */
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
const data: President[] = utils.sheet_to_json<President>(ws); // generate objects
// highlight-start
/* return data -- essentially setting state */
return data;
// highlight-end
}, []);
```
SheetJS users reported that it is easier to reason about data fetching using the
`useAsync` pattern compared to the traditional `useEffect` jujutsu.
:::
#### Rendering Data
Kaioponents typically render HTML tables from arrays of objects. The `TR` table
@ -278,7 +234,7 @@ in the example JSX code:
The [`writeFile`](/docs/api/write-options) and [`json_to_sheet`](/docs/api/utilities/array#array-of-objects-input)
functions simplify exporting data. They are best used in the function bodies of
`useCallback`[^5] hooks attached to button or other elements.
`useCallback`[^4] hooks attached to button or other elements.
A callback can generate a local file when a user clicks a button:
@ -313,84 +269,7 @@ const exportFile = useCallback(() => {
#### Complete Kaioponent
This complete Kaioponent example fetches a test file and displays the data in a
HTML table. When the export button is clicked, a callback will export a file.
Examples using `useAsync` and `useEffect` with `useState` are shown below:
<Tabs groupId="hook">
<TabItem name="async" value="useAsync">
```tsx title="src/SheetJSKaiokenAoO.tsx"
import { useAsync, useCallback } from "kaioken";
import { read, utils, writeFileXLSX } from 'xlsx';
interface President {
Name: string;
Index: number;
}
export default function SheetJSKaiokenAoO() {
/* Fetch and parse the file */
const { data: pres, loading, error } = useAsync<President[]>(async() => {
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const wb = read(f); // parse the array buffer
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
const data = utils.sheet_to_json<President>(ws); // generate objects
return data;
}, []);
/* get state data and export to XLSX */
const exportFile = useCallback(() => {
const ws = utils.json_to_sheet(pres!);
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, "Data");
writeFileXLSX(wb, "SheetJSKaiokenAoO.xlsx");
}, [pres]);
return (<table><thead><tr><th>Name</th><th>Index</th></tr></thead><tbody>
{ /* generate row for each president */
pres && pres.map(pres => (<tr>
<td>{pres.Name}</td>
<td>{pres.Index}</td>
</tr>))
}
{ /* loading message */
!pres && loading && ( <tr><td colSpan="2">Loading ...</td></tr> )
}
{ /* error message */
!pres && !loading && ( <tr><td colSpan="2">{error.message}</td></tr> )
}
</tbody><tfoot><td colSpan={2}>
<button onclick={exportFile}>Export XLSX</button>
</td></tfoot></table>);
}
```
:::note pass
Typically the JSX structure uses ternary expressions for testing status:
```jsx
const { data, loading, error } = useAsync(async() => { /* ... */ });
return ( <>
{ data ? (
<b>Data is loaded</b>
) : loading ? (
<b>Loading ...</b>
) : (
<b>{error.message}</b>
)
}
</> );
```
For clarity, the loading and error messages are separated.
:::
</TabItem>
<TabItem name="effect" value="useEffect + useState">
HTML table. When the export button is clicked, a callback will export a file:
```tsx title="src/SheetJSKaiokenAoO.tsx"
import { useCallback, useEffect, useState } from "kaioken";
@ -407,7 +286,7 @@ export default function SheetJSKaiokenAoO() {
/* Fetch and update the state once */
useEffect(() => { (async() => {
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
const wb = read(f); // parse the array buffer
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
const data = utils.sheet_to_json<President>(ws); // generate objects
@ -435,11 +314,7 @@ export default function SheetJSKaiokenAoO() {
}
```
</TabItem>
</Tabs>
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
<details open><summary><b>How to run the example</b> (click to hide)</summary>
<Tabs groupId="starter">
<TabItem name="vite" value="ViteJS">
@ -448,9 +323,9 @@ export default function SheetJSKaiokenAoO() {
This demo was tested in the following environments:
| Kaioken | ViteJS | Date |
|:----------|:--------|:-----------|
| `0.35.10` | `6.1.0` | 2025-02-11 |
| Kaioken | ViteJS | Date |
|:---------|:--------|:-----------|
| `0.11.2` | `5.2.6` | 2024-03-24 |
:::
@ -459,8 +334,8 @@ This demo was tested in the following environments:
```bash
npm create vite@latest sheetjs-kaioken -- --template vanilla-ts
cd sheetjs-kaioken
npm add --save kaioken
npm add --save vite-plugin-kaioken -D
pnpm add --save kaioken
pnpm add --save vite-plugin-kaioken -D
```
2) Create a new file `vite.config.ts` with the following content:
@ -470,6 +345,14 @@ import { defineConfig } from "vite"
import kaioken from "vite-plugin-kaioken"
export default defineConfig({
esbuild: {
jsxInject: `import * as kaioken from "kaioken"`,
jsx: "transform",
jsxFactory: "kaioken.createElement",
jsxFragment: "kaioken.fragment",
loader: "tsx",
include: ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"],
},
plugins: [kaioken()],
})
```
@ -498,8 +381,8 @@ mount(App, root!);
6) Install the SheetJS dependency and start the dev server:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm run dev`}
pnpm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
pnpm run dev`}
</CodeBlock>
7) Open a web browser and access the displayed URL (`http://localhost:5173`)
@ -510,7 +393,7 @@ and the page will attempt to download `SheetJSKaiokenAoO.xlsx`.
8) Build the site:
```bash
npm run build
pnpm run build
```
The generated site will be placed in the `dist` folder.
@ -518,7 +401,7 @@ The generated site will be placed in the `dist` folder.
9) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -527,7 +410,7 @@ and test the page.
</TabItem>
</Tabs>
When the page loads, the app will fetch https://docs.sheetjs.com/pres.xlsx and
When the page loads, the app will fetch <https://sheetjs.com/pres.xlsx> and
display the data from the first worksheet in a TABLE. The "Export XLSX" button
will generate a workbook that can be opened in a spreadsheet editor.
@ -537,7 +420,7 @@ will generate a workbook that can be opened in a spreadsheet editor.
The main disadvantage of the Array of Objects approach is the specific nature
of the columns. For more general use, passing around an Array of Arrays works.
However, this does not handle merge cells[^6] well!
However, this does not handle merge cells well!
The [`sheet_to_html`](/docs/api/utilities/html#html-table-output) function
generates HTML that is aware of merges and other worksheet features. To add the
@ -554,11 +437,11 @@ import { read, utils, writeFileXLSX } from 'xlsx';
export default function SheetJSKaiokenHTML() {
/* the ref is used in export */
const tbl = useRef<HTMLDivElement>(null);
const tbl = useRef<Element>(null);
/* Fetch and update the state once */
useEffect(() => { (async() => {
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
const wb = read(f); // parse the array buffer
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
// highlight-start
@ -585,8 +468,7 @@ export default function SheetJSKaiokenHTML() {
}
```
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
<details open><summary><b>How to run the example</b> (click to hide)</summary>
<Tabs groupId="starter">
<TabItem name="vite" value="ViteJS">
@ -595,9 +477,9 @@ export default function SheetJSKaiokenHTML() {
This demo was tested in the following environments:
| Kaioken | ViteJS | Date |
|:----------|:--------|:-----------|
| `0.35.10` | `6.1.0` | 2025-02-11 |
| Kaioken | ViteJS | Date |
|:---------|:--------|:-----------|
| `0.11.2` | `5.2.6` | 2024-03-24 |
:::
@ -606,8 +488,8 @@ This demo was tested in the following environments:
```bash
npm create vite@latest sheetjs-kaioken -- --template vanilla-ts
cd sheetjs-kaioken
npm add --save kaioken
npm add --save vite-plugin-kaioken -D
pnpm add --save kaioken
pnpm add --save vite-plugin-kaioken -D
```
2) Create a new file `vite.config.ts` with the following content:
@ -617,6 +499,14 @@ import { defineConfig } from "vite"
import kaioken from "vite-plugin-kaioken"
export default defineConfig({
esbuild: {
jsxInject: `import * as kaioken from "kaioken"`,
jsx: "transform",
jsxFactory: "kaioken.createElement",
jsxFragment: "kaioken.fragment",
loader: "tsx",
include: ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"],
},
plugins: [kaioken()],
})
```
@ -645,8 +535,8 @@ mount(App, root!);
6) Install the SheetJS dependency and start the dev server:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm run dev`}
pnpm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
pnpm run dev`}
</CodeBlock>
7) Open a web browser and access the displayed URL (`http://localhost:5173`)
@ -657,7 +547,7 @@ and the page will attempt to download `SheetJSKaiokenHTML.xlsx`.
8) Build the site:
```bash
npm run build
pnpm run build
```
The generated site will be placed in the `dist` folder.
@ -665,7 +555,7 @@ The generated site will be placed in the `dist` folder.
9) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -674,15 +564,13 @@ and test the page.
</TabItem>
</Tabs>
When the page loads, the app will fetch https://docs.sheetjs.com/pres.xlsx and
When the page loads, the app will fetch <https://sheetjs.com/pres.xlsx> and
display the data from the first worksheet in a TABLE. The "Export XLSX" button
will generate a workbook that can be opened in a spreadsheet editor.
</details>
[^1]: See [`useState`](https://kaioken.dev/docs/hooks/useState) in the Kaioken documentation.
[^2]: See [`useAsync`](https://kaioken.dev/docs/hooks/useAsync) in the Kaioken documentation.
[^3]: See [`useEffect`](https://kaioken.dev/docs/hooks/useEffect) in the Kaioken documentation.
[^4]: See [`useCallback`](https://kaioken.dev/docs/hooks/useCallback) in the Kaioken documentation.
[^5]: See [`useCallback`](https://kaioken.dev/docs/hooks/useCallback) in the Kaioken documentation.
[^6]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges) for more details.
[^1]: See [`useState`](https://kaioken.dev/docs/hooks/usestate) in the Kaioken documentation.
[^2]: See [`useEffect`](https://kaioken.dev/docs/hooks/useeffect) in the Kaioken documentation.
[^3]: See [`useCallback`](https://kaioken.dev/docs/hooks/usecallback) in the Kaioken documentation.
[^4]: See [`useCallback`](https://kaioken.dev/docs/hooks/usecallback) in the Kaioken documentation.

@ -57,13 +57,11 @@ loaded into the site. This sheet will have known columns.
#### State
The example [presidents sheet](https://docs.sheetjs.com/pres.xlsx) has one
header row with "Name" and "Index" columns. The natural JS representation is an
object for each row, where the keys are specified in the first row:
The example [presidents sheet](https://sheetjs.com/pres.xlsx) has one header row
with "Name" and "Index" columns. The natural JS representation is an object for
each row, using the values in the first rows as keys:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
<tbody><tr><td>
<table><thead><tr><th>Spreadsheet</th><th>State</th></tr></thead><tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
@ -135,7 +133,7 @@ The SheetJS [`read`](/docs/api/parse-options) and [`sheet_to_json`](/docs/api/ut
functions simplify state updates. They are best used in the function bodies of
`useEffect`[^2] and `useCallback`[^3] hooks.
A `useEffect` hook can download and update state when the site is loaded:
A `useEffect` hook can download and update state when a person loads the site:
```mermaid
flowchart LR
@ -150,20 +148,19 @@ flowchart LR
wb --> |wb.Sheets\nselect sheet| ws
ws --> |sheet_to_json\n\n| aoo
aoo --> |setPres\nfrom `setState`| state
linkStyle 1,2,3 color:blue,stroke:blue;
```
<Tabs groupId="lang">
<TabItem name="JS" value="JavaScript">
```js title="In a useEffect hook, update state with data from a remote workbook"
```js
import { useEffect } from 'react';
import { read, utils } from 'xlsx';
/* Fetch and update the state once */
useEffect(() => { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
// highlight-start
@ -183,14 +180,14 @@ useEffect(() => { (async() => {
</TabItem>
<TabItem name="TS" value="TypeScript" default>
```ts title="In a useEffect hook, update state with data from a remote workbook"
```ts
import { useEffect } from 'react';
import { read, utils } from 'xlsx';
/* Fetch and update the state once */
useEffect(() => { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
// highlight-start
@ -253,10 +250,9 @@ flowchart LR
state --> |json_to_sheet\n\n| ws
ws --> |book_new\nbook_append_sheet| wb
wb --> |writeFile\n\n| file
linkStyle 0,1,2 color:blue,stroke:blue;
```
```ts title="Export data from state to a new XLSX workbook"
```ts
import { useCallback } from 'react';
import { utils, writeFile } from 'xlsx';
@ -276,20 +272,19 @@ const exportFile = useCallback(() => {
#### Complete Component
This complete component example fetches a test file and displays the contents in
an HTML table. When the export button is clicked, a callback will export a file:
a HTML table. When the export button is clicked, a callback will export a file:
```jsx title="src/SheetJSReactAoO.js"
import React, { useCallback, useEffect, useState } from "react";
import { read, utils, writeFileXLSX } from 'xlsx';
export default function SheetJSReactAoO() {
/* the component state is an array of presidents */
const [pres, setPres] = useState([] /* as any[] */);
const [pres, setPres] = useState([]);
/* Fetch and update the state once */
useEffect(() => { (async() => {
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
// highlight-start
const wb = read(f); // parse the array buffer
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
@ -309,21 +304,20 @@ export default function SheetJSReactAoO() {
return (<table><thead><tr><th>Name</th><th>Index</th></tr></thead><tbody>
{ /* generate row for each president */
// highlight-start
pres.map((pres, index) => (<tr key={index}>
// highlight-start
pres.map(pres => (<tr>
<td>{pres.Name}</td>
<td>{pres.Index}</td>
</tr>))
// highlight-end
// highlight-end
}
</tbody><tfoot><tr><td colSpan={2}>
</tbody><tfoot><td colSpan={2}>
<button onClick={exportFile}>Export XLSX</button>
</td></tr></tfoot></table>);
</td></tfoot></table>);
}
```
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
<details open><summary><b>How to run the example</b> (click to hide)</summary>
<Tabs groupId="starter">
<TabItem name="vite" value="ViteJS">
@ -334,7 +328,7 @@ This demo was tested in the following environments:
| ReactJS | ViteJS | Date |
|:---------|:--------|:-----------|
| `19.1.0` | `6.3.5` | 2025-05-11 |
| `18.2.0` | `5.1.6` | 2024-03-13 |
:::
@ -371,7 +365,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -386,14 +380,14 @@ This demo was tested in the following environments:
| ReactJS | CRA | Date |
|:---------|:--------|:-----------|
| `19.1.0` | `5.1.0` | 2025-05-11 |
| `18.2.0` | `5.0.1` | 2024-03-13 |
:::
1) Create a new site:
```bash
npx -y create-react-app@5.1.0 sheetjs-react
npx -y create-react-app@5.0.1 --scripts-version=5.0.1 sheetjs-react
```
2) Install the SheetJS dependency and start the dev server:
@ -401,7 +395,6 @@ npx -y create-react-app@5.1.0 sheetjs-react
<CodeBlock language="bash">{`\
cd sheetjs-react
npm i
npm i react@19.1.0 react-dom@19.1.0 web-vitals --save --save-exact
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm start`}
</CodeBlock>
@ -424,7 +417,7 @@ The generated site will be placed in the `build` folder.
6) Start a local web server:
```bash
npx -y http-server build
npx http-server build
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -439,7 +432,7 @@ This demo was tested in the following environments:
| ReactJS | NextJS | Date |
|:---------|:---------|:-----------|
| `19.1.0` | `15.3.2` | 2025-05-11 |
| `18.2.0` | `14.1.3` | 2024-03-13 |
:::
@ -459,7 +452,7 @@ NextJS requires a number of workarounds for simple client-side JavaScript code.
:::
:::danger Telemetry
:::warning Telemetry
NextJS collects telemetry by default. The `telemetry` subcommand can disable it:
@ -484,34 +477,26 @@ npx next telemetry disable
1) Create a new site:
```bash
npx create-next-app@latest sheetjs-nextjs --ts --no-eslint --no-tailwind --no-src-dir --no-app --import-alias "@/*" --no-turbopack
npx create-next-app@latest sheetjs-react --ts --no-eslint --no-tailwind --no-src-dir --no-app --import-alias "@/*"
```
2) Install the SheetJS dependency and start the dev server:
<CodeBlock language="bash">{`\
cd sheetjs-nextjs
cd sheetjs-react
npm i
npx next telemetry disable
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm run dev`}
</CodeBlock>
3) Open a web browser and access the displayed URL (`http://localhost:3000`)
3) Open a web browser and access the displayed URL (`http://localhost:5173`)
4) Replace `pages/index.tsx` with the `src/SheetJSReactAoO.js` example.
4) Replace `src/App.jsx` with the `src/SheetJSReactAoO.js` example.
After replacing the code:
After replacing the code, add the following to the top of `src/App.jsx`:
- uncomment the type hint in the `useState` function call:
```js title="pages/index.tsx (uncomment the inline comment)"
const [pres, setPres] = useState([] as any[]);
```
- add the following to the top of `pages/index.tsx`:
```js title="pages/index.tsx (add to top)"
```js title="src/App.jsx (add to top)"
"use client";
```
@ -521,7 +506,7 @@ The goal is to run SheetJS code in the browser. NextJS will attempt to render
pages on the server by default. `"use client";` instructs NextJS to treat the
exported component as a "Client Component" that will be rendered in the browser.
If the directive is not added, NextJS will report errors related to hydration:
If the pragma is not added, NextJS will report errors related to hydration:
```
Error: Hydration failed because the initial UI does not match what was rendered on the server.
@ -542,92 +527,21 @@ and the page will attempt to download `SheetJSReactAoO.xlsx`.
npm run build
```
The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npm run start
```
Access the displayed URL (typically `http://localhost:3000`) with a web browser
and test the page.
</TabItem>
<TabItem name="preact" value="Preact">
:::note Tested Deployments
This demo was tested in the following environments:
| Preact | ViteJS | Date |
|:----------|:----------|:-----------|
| `10.26.6` | `5.4.19` | 2025-05-11 |
:::
1) Create a new site:
```bash
npm init preact sheetjs-preact
```
This will initiate the project creation process. **Follow the on-screen prompts and
press Enter to accept the default options:**
- `Project language:` JavaScript
- `Use router?` No
- `Prerender app (SSG)?` No
- `Use ESLint?` No
2) Install the SheetJS dependency and start the dev server:
<CodeBlock language="bash">{`\
cd sheetjs-preact
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm run dev`}
</CodeBlock>
3) Open a web browser and access the displayed URL (`http://localhost:5173`)
4) Copy `src/SheetJSReactAoO.js` demo code to `src/index.jsx` and update the imports:
```jsx title="src/index.jsx (replace React import with Preact)"
// Remove React import and replace with:
import { render } from 'preact';
import { useCallback, useEffect, useState } from 'preact/hooks';
```
5) Add the following to the bottom of the file:
```jsx title="src/index.jsx (append to file)"
export function App() { return ( <SheetJSReactAoO/> );}
render(<App />, document.getElementById('app'));
```
The page will refresh and show a table with an Export button. Click the button
and the page will attempt to download `SheetJSReactAoO.xlsx`.
6) Build the site:
```bash
npm run build
```
The generated site will be placed in the `dist` folder.
7) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
and test the page.
</TabItem>
</TabItem>
</Tabs>
When the page loads, the app will fetch https://docs.sheetjs.com/pres.xlsx and
When the page loads, the app will fetch <https://sheetjs.com/pres.xlsx> and
display the data from the first worksheet in a TABLE. The "Export XLSX" button
will generate a workbook that can be opened in a spreadsheet editor.
@ -637,151 +551,12 @@ will generate a workbook that can be opened in a spreadsheet editor.
The main disadvantage of the Array of Objects approach is the specific nature
of the columns. For more general use, passing around an Array of Arrays works.
However, this does not handle merge cells[^6] well!
HTML Tables support elements with `rowspan` and `colspan` attributes.
#### State
The state will be the serialized HTML string:
<Tabs groupId="lang">
<TabItem name="JS" value="JavaScript">
```ts
import { useState } from 'react';
/* the component state is a string */
const [__html, setHtml] = useState("");
```
</TabItem>
<TabItem name="TS" value="TypeScript" default>
```ts
import { useState } from 'react';
/* the component state is a string */
const [__html, setHtml] = useState<string>("");
```
</TabItem>
</Tabs>
:::info Use of the variable name `__html`
Examples use the name `__html` due to the design of `dangerouslySetInnerHTML`.
`dangerouslySetInnerHTML` expects objects of the form `{ __html: "html code" }`.
For example, the following snippet assumes `html` is the variable name:
```jsx
<div ref={tbl} dangerouslySetInnerHTML={{ __html: html }} />
```
By using the name `__html`, the ES6 shorthand syntax simplifies the code:
```jsx
<div ref={tbl} dangerouslySetInnerHTML={{ __html }} />
```
:::
#### Updating State
However, this does not handle merge cells well!
The [`sheet_to_html`](/docs/api/utilities/html#html-table-output) function
generates HTML that is aware of merges and other worksheet features.
A `useEffect` hook can download and update state when the site is loaded:
```mermaid
flowchart LR
url[(Remote\nFile)]
ab[(Data\nArrayBuffer)]
wb(SheetJS\nWorkbook)
ws(SheetJS\nWorksheet)
html(HTML\nTABLE)
state((component\nstate))
url --> |fetch\n\n| ab
ab --> |read\n\n| wb
wb --> |wb.Sheets\nselect sheet| ws
ws --> |sheet_to_html\n\n| html
html --> |setHtml\nfrom `setState`| state
linkStyle 1,2,3 color:blue,stroke:blue;
```
```js title="In a useEffect hook, update state with HTML generated from a remote workbook"
import { useEffect } from 'react';
import { read, utils } from 'xlsx';
/* Fetch and update the state once */
useEffect(() => { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
// highlight-start
/* parse */
const wb = read(ab);
/* generate HTML TABLE from first worksheet */
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
const data = utils.sheet_to_html(ws); // generate objects
/* update state */
setHtml(data); // update state
// highlight-end
})(); }, []);
```
#### Rendering Data
ReactJS `dangerouslySetInnerHTML`[^7] prop allows code to set the `innerHTML`
attribute, effectively inserting the code into the page.
It is strongly recommended to set the `innerHTML` of a parent `DIV` container.
By attaching a `ref`, callbacks will be able to access the live `TABLE` element.
```jsx title="Example JSX for displaying HTML TABLE code"
<div ref={tbl} dangerouslySetInnerHTML={{ __html }} />
```
#### Exporting Data
The [`writeFile`](/docs/api/write-options) and [`table_to_book`](/docs/api/utilities/html#html-table-input)
functions simplify exporting data. They are best used in the function bodies of
`useCallback`[^4] hooks attached to button or other elements.
A callback can generate a local file when a user clicks a button:
```mermaid
flowchart LR
state((component\nstate))
wb(SheetJS\nWorkbook)
file[(XLSX\nexport)]
state --> |table_to_book\n\n| wb
wb --> |writeFile\n\n| file
linkStyle 0,1 color:blue,stroke:blue;
```
```ts title="Export data from HTML TABLE element to a new XLSX workbook"
import { useCallback } from 'react';
import { utils, writeFile } from 'xlsx';
/* get data from live HTML TABLE and export to XLSX */
const exportFile = useCallback(() => {
/* get live reference to HTML TABLE element */
const elt = tbl.current.getElementsByTagName("TABLE")[0];
/* generate workbook from element */
// highlight-next-line
const wb = utils.table_to_book(elt);
/* export to XLSX */
writeFile(wb, "SheetJSReactAoO.xlsx");
}, [pres]);
```
#### Complete Component
generates HTML that is aware of merges and other worksheet features. ReactJS
`dangerouslySetInnerHTML`[^5] prop allows code to set the `innerHTML` attribute,
effectively inserting the code into the page.
In this example, the component attaches a `ref` to the `DIV` container. During
export, the first `TABLE` child element can be parsed with [`table_to_book`](/docs/api/utilities/html#html-table-input) to
@ -789,7 +564,6 @@ generate a workbook object.
```jsx title="src/SheetJSReactHTML.js"
import React, { useCallback, useEffect, useRef, useState } from "react";
import { read, utils, writeFileXLSX } from 'xlsx';
export default function SheetJSReactHTML() {
@ -800,7 +574,7 @@ export default function SheetJSReactHTML() {
/* Fetch and update the state once */
useEffect(() => { (async() => {
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
const wb = read(f); // parse the array buffer
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
// highlight-start
@ -826,8 +600,7 @@ export default function SheetJSReactHTML() {
}
```
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
<details open><summary><b>How to run the example</b> (click to hide)</summary>
<Tabs groupId="starter">
<TabItem name="vite" value="ViteJS">
@ -838,7 +611,7 @@ This demo was tested in the following environments:
| ReactJS | ViteJS | Date |
|:---------|:--------|:-----------|
| `19.1.0` | `6.3.5` | 2025-05-11 |
| `18.2.0` | `5.1.6` | 2024-03-13 |
:::
@ -875,7 +648,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -890,14 +663,14 @@ This demo was tested in the following environments:
| ReactJS | CRA | Date |
|:---------|:--------|:-----------|
| `19.1.0` | `5.1.0` | 2025-05-11 |
| `18.2.0` | `5.0.1` | 2024-03-13 |
:::
1) Create a new site:
```bash
npx -y create-react-app@5.1.0 sheetjs-react
npx -y create-react-app@5.0.1 --scripts-version=5.0.1 sheetjs-react
```
2) Install the SheetJS dependency and start the dev server:
@ -905,7 +678,6 @@ npx -y create-react-app@5.1.0 sheetjs-react
<CodeBlock language="bash">{`\
cd sheetjs-react
npm i
npm i react@19.1.0 react-dom@19.1.0 web-vitals --save --save-exact
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm start`}
</CodeBlock>
@ -928,78 +700,7 @@ The generated site will be placed in the `build` folder.
6) Start a local web server:
```bash
npx -y http-server build
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
and test the page.
</TabItem>
<TabItem name="preact" value="Preact">
:::note Tested Deployments
This demo was tested in the following environments:
| Preact | ViteJS | Date |
|:----------|:---------|:-----------|
| `10.26.6` | `5.4.19` | 2025-05-11 |
:::
```bash
npm init preact sheetjs-preact
```
This will initiate the project creation process. **Follow the on-screen prompts and
press Enter to accept the default options:**
- `Project language:` JavaScript
- `Use router?` No
- `Prerender app (SSG)?` No
- `Use ESLint?` No
2) Install the SheetJS dependency and start the dev server:
<CodeBlock language="bash">{`\
cd sheetjs-preact
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm run dev`}
</CodeBlock>
3) Open a web browser and access the displayed URL (`http://localhost:5173`)
4) Copy `src/SheetJSReactHTML.js` demo code to `src/index.jsx` and update the imports:
```jsx title="src/index.jsx (replace React import with Preact)"
// Remove React import and replace with:
import { render } from 'preact';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
```
5) Add the following to the bottom of the file:
```jsx title="src/index.jsx (append to file)"
export function App() { return ( <SheetJSReactHTML/> );}
render(<App />, document.getElementById('app'));
```
The page will refresh and show a table with an Export button. Click the button
and the page will attempt to download `SheetJSReactHTML.xlsx`.
6) Build the site:
```bash
npm run build
```
The generated site will be placed in the `dist` folder.
7) Start a local web server:
```bash
npx -y http-server dist
npx http-server build
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -1008,7 +709,7 @@ and test the page.
</TabItem>
</Tabs>
When the page loads, the app will fetch https://docs.sheetjs.com/pres.xlsx and
When the page loads, the app will fetch <https://sheetjs.com/pres.xlsx> and
display the data from the first worksheet in a TABLE. The "Export XLSX" button
will generate a workbook that can be opened in a spreadsheet editor.
@ -1046,8 +747,8 @@ const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({
## Legacy Deployments
[SheetJS Standalone Scripts](/docs/getting-started/installation/standalone) use
simple `SCRIPT` tags and work with legacy deployments that do not use a bundler.
[The Standalone Scripts](/docs/getting-started/installation/standalone) play nice
with legacy deployments that do not use a bundler.
[The legacy demo](pathname:///react/index.html) shows a simple ReactJS component
transpiled in the browser using Babel standalone library.
@ -1056,6 +757,4 @@ transpiled in the browser using Babel standalone library.
[^2]: See [`useEffect`](https://react.dev/reference/react/useEffect) in the ReactJS documentation.
[^3]: See [`useCallback`](https://react.dev/reference/react/useCallback) in the ReactJS documentation.
[^4]: See [`useCallback`](https://react.dev/reference/react/useCallback) in the ReactJS documentation.
[^5]: See [Create React App Issue #13717](https://github.com/facebook/create-react-app/issues/13717) for details about React 19+ compatibility issues.
[^6]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges) for more details.
[^7]: [`dangerouslySetInnerHTML`](https://react.dev/reference/react-dom/components/common#common-props) is a ReactJS prop supported for all built-in components.
[^5]: [`dangerouslySetInnerHTML`](https://react.dev/reference/react-dom/components/common#common-props) is a ReactJS prop supported for all built-in components.

@ -38,7 +38,7 @@ should be directed to the Angular project.
:::
:::danger Telemetry
:::warning Telemetry
Angular CLI enables telemetry by default. When using a recent version, disable
analytics globally through the CLI tool before creating a new project:
@ -69,7 +69,7 @@ import { read, utils, writeFile } from 'xlsx';
The various SheetJS APIs work with various data shapes. The preferred state
depends on the application.
:::danger pass
:::warning pass
Angular 17 broke backwards compatibility with projects using Angular 2 - 16.
@ -87,13 +87,11 @@ loaded into the site. This sheet will have known columns.
#### State
The example [presidents sheet](https://docs.sheetjs.com/pres.xlsx) has one
header row with "Name" and "Index" columns. The natural JS representation is an
object for each row, where the keys are specified in the first row:
The example [presidents sheet](https://sheetjs.com/pres.xlsx) has one header row
with "Name" and "Index" columns. The natural JS representation is an object for
each row, using the values in the first rows as keys:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
<tbody><tr><td>
<table><thead><tr><th>Spreadsheet</th><th>State</th></tr></thead><tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
@ -184,8 +182,8 @@ interface President { Name: string; Index: number };
export class AppComponent {
rows: President[] = [ { Name: "SheetJS", Index: 0 }];
ngOnInit(): void { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
// highlight-start
@ -298,7 +296,7 @@ export class AppComponent {
#### Complete Component
This complete component example fetches a test file and displays the contents in
an HTML table. When the export button is clicked, a callback will export a file:
a HTML table. When the export button is clicked, a callback will export a file:
<Tabs groupId="ngVer">
<TabItem value="2" label="Angular 2-16">
@ -331,8 +329,8 @@ export class AppComponent {
// highlight-next-line
rows: President[] = [ { Name: "SheetJS", Index: 0 }];
ngOnInit(): void { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
/* parse workbook */
@ -389,8 +387,8 @@ export class AppComponent {
// highlight-next-line
rows: President[] = [ { Name: "SheetJS", Index: 0 }];
ngOnInit(): void { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
/* parse workbook */
@ -416,8 +414,7 @@ export class AppComponent {
</TabItem>
</Tabs>
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
<details open><summary><b>How to run the example</b> (click to hide)</summary>
:::note Tested Deployments
@ -425,10 +422,8 @@ This demo was tested in the following environments:
| Angular | Date |
|:----------|:-----------|
| `19.0.5` | 2025-01-03 |
| `18.2.13` | 2025-01-03 |
| `17.3.12` | 2025-01-03 |
| `16.2.12` | 2025-01-03 |
| `17.3.0` | 2024-03-13 |
| `16.2.12` | 2024-03-13 |
:::
@ -441,16 +436,16 @@ npx @angular/cli analytics disable -g
1) Create a new project:
```bash
npx @angular/cli@19 new --minimal --defaults --no-interactive sheetjs-angular
npx @angular/cli@17.3.0 new --minimal --defaults --no-interactive sheetjs-angular
```
:::note pass
The `@angular/cli` version controls the project version of Angular. For example,
the following command uses Angular 16:
the following command uses Angular 16.2.12:
```bash
npx @angular/cli@16 new --minimal --defaults --no-interactive sheetjs-angular
npx @angular/cli@16.2.12 new --minimal --defaults --no-interactive sheetjs-angular
```
:::
@ -509,12 +504,12 @@ to test the bundled site.
The main disadvantage of the Array of Objects approach is the specific nature
of the columns. For more general use, passing around an Array of Arrays works.
However, this does not handle merge cells[^5] well!
However, this does not handle merge cells well!
The `sheet_to_html` function generates HTML that is aware of merges and other
worksheet features. The generated HTML does not contain any `<script>` tags,
and should therefore be safe to pass to an `innerHTML`-bound variable, but the
`DomSanitizer` approach[^6] is strongly recommended:
`DomSanitizer` approach[^5] is strongly recommended:
<Tabs groupId="ngVer">
<TabItem value="2" label="Angular 2-16">
@ -538,8 +533,8 @@ export class AppComponent {
@ViewChild('tableau') tabeller!: ElementRef<HTMLDivElement>;
// highlight-end
ngOnInit(): void { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
/* parse workbook */
@ -585,8 +580,8 @@ export class AppComponent {
@ViewChild('tableau') tabeller!: ElementRef<HTMLDivElement>;
// highlight-end
ngOnInit(): void { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
/* parse workbook */
@ -612,8 +607,7 @@ export class AppComponent {
</TabItem>
</Tabs>
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
<details open><summary><b>How to run the example</b> (click to hide)</summary>
:::note Tested Deployments
@ -621,10 +615,8 @@ This demo was tested in the following environments:
| Angular | Date |
|:----------|:-----------|
| `19.0.5` | 2025-01-03 |
| `18.2.13` | 2025-01-03 |
| `17.3.12` | 2025-01-03 |
| `16.2.12` | 2025-01-03 |
| `17.3.0` | 2024-03-13 |
| `16.2.12` | 2024-03-13 |
:::
@ -637,16 +629,16 @@ npx @angular/cli analytics disable -g
1) Create a new project:
```bash
npx @angular/cli@19 new --minimal --defaults --no-interactive sheetjs-angular
npx @angular/cli@17.3.0 new --minimal --defaults --no-interactive sheetjs-angular
```
:::note pass
The `@angular/cli` version controls the project version of Angular. For example,
the following command uses Angular 16:
the following command uses Angular 16.2.12:
```bash
npx @angular/cli@16 new --minimal --defaults --no-interactive sheetjs-angular
npx @angular/cli@16.2.12 new --minimal --defaults --no-interactive sheetjs-angular
```
:::
@ -728,7 +720,7 @@ this.columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({
## Older Versions
:::danger pass
:::warning pass
This demo is included for legacy deployments. There are incompatibilities with
different NodeJS and other ecosystem versions. Issues should be raised with
@ -1042,9 +1034,8 @@ will have an `ng-version` attribute.
npm run build
```
[^1]: The main website for Angular versions 2-16 is https://angular.io/ . The project moved to a new domain https://angular.dev/ during the Angular 17 launch.
[^1]: The main website for Angular versions 2-16 is <https://angular.io/> . The project moved to a new domain <https://angular.dev/> during the Angular 17 launch.
[^2]: See `OnInit` in the [Angular 2-16 docs](https://angular.io/api/core/OnInit) or [Angular 17 docs](https://angular.dev/guide/components/lifecycle#ngoninit)
[^3]: See [`ngFor`](https://angular.io/api/common/NgFor) in the Angular 2-16 docs.
[^4]: See [`@for`](https://angular.dev/api/core/@for) in the Angular 17 docs.
[^5]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges) for more details.
[^6]: See `DomSanitizer` in the [Angular 2-16 docs](https://angular.io/api/platform-browser/DomSanitizer) or [Angular 17 docs](https://angular.dev/api/platform-browser/DomSanitizer)
[^5]: See `DomSanitizer` in the [Angular 2-16 docs](https://angular.io/api/platform-browser/DomSanitizer) or [Angular 17 docs](https://angular.dev/api/platform-browser/DomSanitizer)

@ -18,8 +18,8 @@ import CodeBlock from '@theme/CodeBlock';
data from spreadsheets.
This demo uses VueJS and SheetJS to process and generate spreadsheets. We'll
explore how to load SheetJS in a VueJS single-file component and compare common
state models and data flow strategies.
explore how to load SheetJS in a VueJS SFC (single-file component) and compare
common state models and data flow strategies.
:::note pass
@ -56,13 +56,11 @@ loaded into the site. This sheet will have known columns.
#### State
The example [presidents sheet](https://docs.sheetjs.com/pres.xlsx) has one
header row with "Name" and "Index" columns. The natural JS representation is an
object for each row, where the keys are specified in the first row:
The example [presidents sheet](https://sheetjs.com/pres.xlsx) has one header row
with "Name" and "Index" columns. The natural JS representation is an object for
each row, using the values in the first rows as keys:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
<tbody><tr><td>
<table><thead><tr><th>Spreadsheet</th><th>State</th></tr></thead><tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
@ -170,8 +168,8 @@ const pres = ref([]);
/* Fetch and update the state once */
onMounted(async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
// highlight-start
@ -207,8 +205,8 @@ const pres = ref<President[]>([]);
/* Fetch and update the state once */
onMounted(async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
// highlight-start
@ -234,7 +232,7 @@ onMounted(async() => {
A component will typically map over the data with `v-for`[^3]. The following
example generates a TABLE with a row for each President:
```html title="Example single-file component for displaying arrays of objects"
```html title="Example SFC for displaying arrays of objects"
<script setup>
import { ref } from "vue";
const rows = ref([]);
@ -305,7 +303,7 @@ function exportFile() {
#### Complete Component
This complete component example fetches a test file and displays the contents in
an HTML table. When the export button is clicked, a callback will export a file:
a HTML table. When the export button is clicked, a callback will export a file:
```html title="src/SheetJSVueAoO.vue"
<script setup>
@ -315,8 +313,8 @@ import { read, utils, writeFileXLSX } from 'xlsx';
const rows = ref([]);
onMounted(async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
/* parse workbook */
@ -347,8 +345,7 @@ function exportFile() {
</template>
```
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
<details open><summary><b>How to run the example</b> (click to hide)</summary>
<Tabs groupId="starter">
<TabItem name="vite" value="ViteJS">
@ -357,9 +354,9 @@ function exportFile() {
This demo was tested in the following environments:
| VueJS | ViteJS | Date |
|:---------|:---------|:-----------|
| `3.5.13` | `6.1.0` | 2025-02-15 |
| VueJS | ViteJS | Date |
|:--------|:--------|:-----------|
| `3.3.9` | `4.5.1` | 2023-12-04 |
:::
@ -397,7 +394,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -412,7 +409,7 @@ This demo was tested in the following environments:
| VueJS | NuxtJS | Date |
|:---------|:---------|:-----------|
| `3.5.13` | `3.15.0` | 2025-01-02 |
| `3.4.21` | `3.11.1` | 2024-03-21 |
:::
@ -449,7 +446,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server .output/public/
npx http-server .output/public/
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -464,11 +461,11 @@ and test the page.
The main disadvantage of the Array of Objects approach is the specific nature
of the columns. For more general use, passing around an Array of Arrays works.
However, this does not handle merge cells[^5] well!
However, this does not handle merge cells well!
The [`sheet_to_html`](/docs/api/utilities/html#html-table-output) function
generates HTML that is aware of merges and other worksheet features. VueJS
`v-html`[^6] attribute allows code to set the `innerHTML` attribute, effectively
`v-html`[^5] attribute allows code to set the `innerHTML` attribute, effectively
inserting the code into the page.
In this example, the component attaches a `ref` to the `DIV` container. During
@ -484,8 +481,8 @@ const html = ref("");
const tableau = ref();
onMounted(async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
/* Download from https://sheetjs.com/pres.numbers */
const f = await fetch("https://sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
/* parse workbook */
@ -508,8 +505,7 @@ function exportFile() {
</template>
```
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
<details open><summary><b>How to run the example</b> (click to hide)</summary>
<Tabs groupId="starter">
<TabItem name="vite" value="ViteJS">
@ -520,7 +516,7 @@ This demo was tested in the following environments:
| VueJS | ViteJS | Date |
|:---------|:--------|:-----------|
| `3.5.13` | `6.0.7` | 2025-01-02 |
| `3.4.21` | `5.2.2` | 2024-03-21 |
:::
@ -558,7 +554,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -573,7 +569,7 @@ This demo was tested in the following environments:
| VueJS | NuxtJS | Date |
|:---------|:---------|:-----------|
| `3.5.13` | `3.15.0` | 2025-01-02 |
| `3.4.21` | `3.11.1` | 2024-03-21 |
:::
@ -610,7 +606,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server .output/public/
npx http-server .output/public/
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -672,5 +668,4 @@ modern design patterns.
[^2]: See [`onMounted()`](https://vuejs.org/api/composition-api-lifecycle.html#onmounted) in the VueJS documentation.
[^3]: See [`v-for`](https://vuejs.org/api/built-in-directives.html#v-for) in the VueJS documentation.
[^4]: See [`v-on`](https://vuejs.org/api/built-in-directives.html#v-on) in the VueJS documentation.
[^5]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges) for more details.
[^6]: See [`v-html`](https://vuejs.org/api/built-in-directives.html#v-html) in the VueJS documentation.
[^5]: See [`v-html`](https://vuejs.org/api/built-in-directives.html#v-html) in the VueJS documentation.

@ -25,7 +25,7 @@ models and data flow strategies.
This demo focuses on Svelte concepts. Other demos cover general deployments:
- [Static Site Generation powered by SvelteKit](/docs/demos/static/svelte)
- [iOS and Android applications powered by CapacitorJS](/docs/demos/mobile/capacitor)
- [iOS applications powered by CapacitorJS](/docs/demos/mobile/capacitor)
- [Desktop application powered by Wails](/docs/demos/desktop/wails)
:::
@ -50,29 +50,11 @@ depends on the application.
### Array of Objects
Typically, some users will create a spreadsheet with source data that should be
loaded into the site. This sheet will have known columns. For example, "Name"
and "Index" are used in [`pres.xlsx`](https://docs.sheetjs.com/pres.xlsx):
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
<tbody><tr><td>
loaded into the site. This sheet will have known columns. For example, our
[presidents sheet](https://sheetjs.com/pres.xlsx) has "Name" / "Index" columns:
![`pres.xlsx` data](pathname:///pres.png)
</td><td>
```js
[
{ Name: "Bill Clinton", Index: 42 },
{ Name: "GeorgeW Bush", Index: 43 },
{ Name: "Barack Obama", Index: 44 },
{ Name: "Donald Trump", Index: 45 },
{ Name: "Joseph Biden", Index: 46 }
]
```
</td></tr></tbody></table>
This naturally maps to an array of typed objects, as in the TS example below:
```ts
@ -83,12 +65,24 @@ interface President {
Index: number;
}
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
const wb = read(f);
const data = utils.sheet_to_json<President>(wb.Sheets[wb.SheetNames[0]]);
console.log(data);
```
`data` will be an array of objects:
```js
[
{ Name: "Bill Clinton", Index: 42 },
{ Name: "GeorgeW Bush", Index: 43 },
{ Name: "Barack Obama", Index: 44 },
{ Name: "Donald Trump", Index: 45 },
{ Name: "Joseph Biden", Index: 46 }
]
```
A component will typically map over the data. The following example generates
a TABLE with a row for each President:
@ -102,7 +96,7 @@ let pres = [];
/* Fetch and update the state once */
onMount(async() => {
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
const wb = read(f); // parse the array buffer
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
// highlight-start
@ -128,22 +122,21 @@ function exportFile() {
<td>{p.Index}</td>
</tr>{/each}
<!-- highlight-end -->
</tbody><tfoot><tr><td colSpan={2}>
</tbody><tfoot><td colSpan={2}>
<button on:click={exportFile}>Export XLSX</button>
</td></tfoot></tr></table>
</td></tfoot></table>
</main>
```
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
<details open><summary><b>How to run the example</b> (click to hide)</summary>
:::note Tested Deployments
This demo was tested in the following environments:
| SvelteJS | ViteJS | Date |
|:---------|:---------|:-----------|
| `5.25.3` | `6.2.3` | 2025-03-30 |
| Svelte | ViteJS | Date |
|:--------|:--------|:-----------|
| `4.2.8` | `5.0.5` | 2023-12-04 |
:::
@ -170,9 +163,7 @@ The page will refresh and show a table with an Export button. Click the button
and the page will attempt to download `SheetJSSvelteAoA.xlsx`. There may be a
delay since Vite will try to optimize the SheetJS library on the fly.
5) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
6) Build the site:
5) Build the site:
```bash
npm run build
@ -180,10 +171,10 @@ npm run build
The generated site will be placed in the `dist` folder.
7) Start a local web server:
6) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -195,7 +186,7 @@ and test the page.
The main disadvantage of the Array of Objects approach is the specific nature
of the columns. For more general use, passing around an Array of Arrays works.
However, this does not handle merge cells[^1] well!
However, this does not handle merge cells well!
The `sheet_to_html` function generates HTML that is aware of merges and other
worksheet features. Svelte `@html` tag allows raw HTML strings:
@ -210,7 +201,7 @@ let tbl;
/* Fetch and update the state once */
onMount(async() => {
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
const wb = read(f); // parse the array buffer
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
// highlight-start
@ -236,16 +227,15 @@ function exportFile() {
</main>
```
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
<details open><summary><b>How to run the example</b> (click to hide)</summary>
:::note Tested Deployments
This demo was tested in the following environments:
| SvelteJS | ViteJS | Date |
|:---------|:---------|:-----------|
| `5.25.3` | `6.2.3` | 2025-03-30 |
| Svelte | ViteJS | Date |
|:--------|:--------|:-----------|
| `4.2.8` | `5.0.5` | 2023-12-04 |
:::
@ -272,9 +262,7 @@ The page will refresh and show a table with an Export button. Click the button
and the page will attempt to download `SheetJSSvelteHTML.xlsx`. There may be a
delay since Vite will try to optimize the SheetJS library on the fly.
5) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
6) Build the site:
5) Build the site:
```bash
npm run build
@ -282,15 +270,13 @@ npm run build
The generated site will be placed in the `dist` folder.
7) Start a local web server:
6) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
and test the page.
</details>
[^1]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges) for more details.

@ -10,7 +10,7 @@ sidebar_position: 7
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
:::danger pass
:::warning pass
This demo is for the legacy AngularJS framework (version 1).
@ -34,11 +34,10 @@ models and data flow strategies.
This demo was tested in the following environments:
| Browser | Version | Date |
|:-------------|:------------------|:-----------|
| Chromium 133 | `1.8.2` (latest) | 2025-03-30 |
| Konqueror 22 | `1.8.2` (latest) | 2025-04-23 |
| Chromium 133 | `1.2.32` (legacy) | 2025-03-30 |
| Version | Date |
|:------------------|:-----------|
| `1.8.2` (latest) | 2023-12-04 |
| `1.2.32` (legacy) | 2023-12-04 |
:::
@ -74,22 +73,22 @@ property to `"arraybuffer"` ensures the result is an `ArrayBuffer` object.
The SheetJS [`read`](/docs/api/parse-options) method can parse the `ArrayBuffer`
and return a SheetJS workbook object[^2].
This controller fetches [a sample file](https://docs.sheetjs.com/pres.xlsx),
The following controller fetches [a sample file](https://sheetjs.com/pres.xlsx),
parses the result into a workbook, extracts the first worksheet, and uses the
SheetJS [`sheet_to_html`](/docs/api/utilities/html#html-table-output) method to
generate an HTML table:
generate a HTML table:
```js title="Sample Controller"
```js
/* The controller function must accept a `$http` argument */
app.controller('sheetjs', function($scope, $http) {
/* fetch https://docs.sheetjs.com/pres.xlsx */
/* fetch https://sheetjs.com/pres.xlsx */
$http({
method:'GET', url:'https://docs.sheetjs.com/pres.xlsx',
method:'GET', url:'https://sheetjs.com/pres.xlsx',
/* ensure the result is an ArrayBuffer object */
responseType:'arraybuffer'
}).then(function(response) {
}).then(function(data) {
// highlight-next-line
var wb = XLSX.read(response.data);
var wb = XLSX.read(data.data);
/* generate HTML from first worksheet*/
var ws = wb.Sheets[wb.SheetNames[0]];
var html = XLSX.utils.sheet_to_html(ws);
@ -168,13 +167,11 @@ depends on the application.
### Array of Objects
The example [presidents sheet](https://docs.sheetjs.com/pres.xlsx) has one
header row with "Name" and "Index" columns. The natural JS representation is an
object for each row, where the keys are specified in the first row:
The example [presidents sheet](https://sheetjs.com/pres.xlsx) has one header row
with "Name" and "Index" columns. The natural JS representation is an object for
each row, using the values in the first rows as keys:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
<tbody><tr><td>
<table><thead><tr><th>Spreadsheet</th><th>State</th></tr></thead><tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
@ -199,10 +196,10 @@ file, generates row objects, and stores the array in the state:
```js
app.controller('sheetjs', function($scope, $http) {
$http({
url:'https://docs.sheetjs.com/pres.xlsx',
url:'https://sheetjs.com/pres.xlsx',
method:'GET', responseType:'arraybuffer'
}).then(function(response) {
var wb = XLSX.read(response.data);
}).then(function(data) {
var wb = XLSX.read(data.data);
// highlight-next-line
$scope.data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
}, function(err) { console.log(err); });
@ -238,8 +235,7 @@ $scope.exportSheetJS = function() {
};
```
<details>
<summary open><b>How to run the example</b> (click to hide)</summary>
<details><summary><b>Complete Example</b> (click to show)</summary>
1) Save the following to `index.html`:
@ -276,10 +272,10 @@ app.controller('sheetjs', function($scope, $http) {
XLSX.writeFile(wb, "SheetJSAngularJSAoO.xlsx");
};
$http({
url:'https://docs.sheetjs.com/pres.xlsx',
url:'https://sheetjs.com/pres.xlsx',
method:'GET', responseType:'arraybuffer'
}).then(function(response) {
var wb = XLSX.read(response.data);
}).then(function(data) {
var wb = XLSX.read(data.data);
var data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
$scope.data = data;
}, function(err) { console.log(err); });
@ -289,17 +285,8 @@ app.controller('sheetjs', function($scope, $http) {
</html>`}
</CodeBlock>
2) Start a local web server:
```bash
npx -y http-server .
```
Access the displayed URL with a web browser (typically `http://localhost:8080`)
When the page loads, the app will fetch https://docs.sheetjs.com/pres.xlsx and
store an array of objects in state. When the "Export Table" button is clicked,
a worksheet is created and exported to XLSX.
2) Start a local web server with `npx http-server .` and access the displayed
URL with a web browser (typically `http://localhost:8080`)
</details>
@ -307,12 +294,12 @@ a worksheet is created and exported to XLSX.
The main disadvantage of the Array of Objects approach is the specific nature
of the columns. For more general use, passing around an Array of Arrays works.
However, this does not handle merge cells[^4] well!
However, this does not handle merge cells well!
The `sheet_to_html` function generates HTML that is aware of merges and other
worksheet features. The generated HTML does not contain any `<script>` tags,
and should therefore be safe to pass to an `ng-bind-html` binding. This approach
requires the `ngSanitize` plugin[^5].
requires the `ngSanitize` plugin[^4].
```html
<div ng-controller="sheetjs">
@ -325,10 +312,10 @@ requires the `ngSanitize` plugin[^5].
var app = angular.module('s5s', ['ngSanitize']);
app.controller('sheetjs', function($scope, $http) {
$http({
url:'https://docs.sheetjs.com/pres.xlsx',
url:'https://sheetjs.com/pres.xlsx',
method:'GET', responseType:'arraybuffer'
}).then(function(response) {
var wb = XLSX.read(response.data);
}).then(function(data) {
var wb = XLSX.read(data.data);
// highlight-next-line
$scope.data = XLSX.utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
}, function(err) { console.log(err); });
@ -349,8 +336,7 @@ The HTML table can be directly exported with [`table_to_book`](/docs/api/utiliti
};
```
<details>
<summary open><b>How to run the example</b> (click to hide)</summary>
<details><summary><b>Complete Example</b> (click to show)</summary>
1) Save the following to `index.html`:
@ -381,10 +367,10 @@ app.controller('sheetjs', function($scope, $http) {
XLSX.writeFile(wb, "SheetJSAngularJSHTML.xlsx");
};
$http({
url:'https://docs.sheetjs.com/pres.xlsx',
url:'https://sheetjs.com/pres.xlsx',
method:'GET', responseType:'arraybuffer'
}).then(function(response) {
var wb = XLSX.read(response.data);
}).then(function(data) {
var wb = XLSX.read(data.data);
$scope.data = XLSX.utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
}, function(err) { console.log(err); });
});
@ -393,22 +379,12 @@ app.controller('sheetjs', function($scope, $http) {
</html>`}
</CodeBlock>
2) Start a local web server:
```bash
npx -y http-server .
```
Access the displayed URL with a web browser (typically `http://localhost:8080`)
When the page loads, the app will fetch https://docs.sheetjs.com/pres.xlsx and
store the HTML string in state. When the "Export Table" button is clicked, a
worksheet is created and exported to XLSX.
2) Start a local web server with `npx http-server .` and access the displayed
URL with a web browser (typically `http://localhost:8080`)
</details>
[^1]: See [`$http`](https://docs.angularjs.org/api/ng/service/$http) in the AngularJS documentation.
[^2]: See ["Workbook Object"](/docs/csf/book)
[^3]: See ["Creating Directives"](https://docs.angularjs.org/guide/directive#creating-a-directive-that-manipulates-the-dom) in the AngularJS documentation.
[^4]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges) for more details.
[^5]: See [`ngSanitize`](https://docs.angularjs.org/api/ngSanitize) in the AngularJS documentation.
[^4]: See [`ngSanitize`](https://docs.angularjs.org/api/ngSanitize) in the AngularJS documentation.

@ -57,13 +57,13 @@ require(
</script>`}
</CodeBlock>
:::danger pass
:::warning pass
The official Google CDN does not have the newest releases of Dojo Toolkit
**This is a known Google CDN bug.**
The script https://docs.sheetjs.com/dojo/dojo.js was fetched from the official
The script <https://docs.sheetjs.com/dojo/dojo.js> was fetched from the official
`1.17.3` uncompressed release artifact[^1].
:::
@ -72,12 +72,7 @@ The script https://docs.sheetjs.com/dojo/dojo.js was fetched from the official
:::note Tested Deployments
This demo was tested in the following environments:
| Platform | Date |
|:-------------|:-----------|
| Chromium 133 | 2025-03-30 |
| Konqueror 22 | 2025-04-23 |
The demos were last tested on 2023-12-04.
Demos exclusively using Dojo Core were tested using Dojo Toolkit `1.17.3`.
@ -98,13 +93,13 @@ was the latest version available on the Google CDN.
When fetching spreadsheets with XHR, `handleAs: "arraybuffer"` yields an
`ArrayBuffer` which can be passed to the SheetJS `read` method.
The following example generates an HTML table from the first worksheet:
The following example generates a HTML table from the first worksheet:
```html
<div id="tbl"></div>
<script>
require(["dojo/request/xhr", "xlsx"], function(xhr, _XLSX) {
xhr("https://docs.sheetjs.com/pres.numbers", {
xhr("https://sheetjs.com/pres.numbers", {
headers: { "X-Requested-With": null },
// highlight-next-line
handleAs: "arraybuffer"
@ -160,7 +155,7 @@ require([
], function(ready, xhr, Memory, registry, _XLSX) {
ready(function() {
/* fetch test file */
xhr("https://docs.sheetjs.com/pres.xlsx", {
xhr("https://sheetjs.com/pres.xlsx", {
headers: { "X-Requested-With": null },
handleAs: "arraybuffer"
}).then(function(ab) {
@ -206,5 +201,5 @@ function export_all_data_from_store(store) {
}
```
[^1]: All Dojo Toolkit releases are available at https://download.dojotoolkit.org/. The mirrored `dojo.js` corresponds to the `1.17.3` uncompressed script http://download.dojotoolkit.org/release-1.17.3/dojo.js.uncompressed.js.
[^1]: All Dojo Toolkit releases are available at <https://download.dojotoolkit.org/>. The mirrored `dojo.js` corresponds to the `1.17.3` uncompressed script <http://download.dojotoolkit.org/release-1.17.3/dojo.js.uncompressed.js>.
[^2]: See [`dojo/store`](https://dojotoolkit.org/reference-guide/dojo/store.html) in the Dojo Toolkit documentation.

@ -1,506 +0,0 @@
---
title: Sheets in Blazor Sites
sidebar_label: Blazor
pagination_prev: demos/index
pagination_next: demos/grid/index
sidebar_position: 9
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
[Blazor](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor) is a
framework for building user interfaces using C#, HTML, JS and CSS.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses Blazor and SheetJS to process and generate spreadsheets. We'll
explore how to load SheetJS in Razor components and compare common state models
and data flow strategies.
:::caution Blazor support is considered experimental.
Great open source software grows with user tests and reports. Any issues should
be reported to the Blazor project for further diagnosis.
:::
:::danger Telemetry
**The `dotnet` command embeds telemetry.**
The `DOTNET_CLI_TELEMETRY_OPTOUT` environment variable should be set to `1`.
["Platform Configuration"](#platform-configuration) includes instructions for
setting the environment variable on supported platforms.
:::
## Integration Details
### Installation
The SheetJS library can be loaded when the page is loaded or imported whenever
the library functionality is used.
#### Standalone Script
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
can be loaded in the root HTML page (typically `wwwroot/index.html`):
<CodeBlock language="html">{`\
<!-- use version ${current} -->
<script type="text/javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>`}
</CodeBlock>
#### ECMAScript Module
The SheetJS ECMAScript module script can be dynamically imported from functions.
This ensures the library is only loaded when necessary. The following JS example
loads the library and returns a Promise that resolves to the version string:
<CodeBlock language="js">{`\
async function sheetjs_version(id) {
/* dynamically import the script in the event listener */
// highlight-next-line
const XLSX = await import("https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs");
\n\
/* use the library */
return XLSX.version;
}`}
</CodeBlock>
### Calling JS from C#
Callbacks for events in Razor elements invoke C# methods. The C# methods can use
Blazor APIs to invoke JS methods that are visible in the browser global scope.
```mermaid
sequenceDiagram
actor U as User
participant P as Browser
participant A as Blazor
U-->>P: click button
P-->>A: click event
Note over A: C#35; callback<br/><br/>InvokeVoidAsync
A->>P: call JS function
Note over P: global method<br/><br/>SheetJS logic
P->>U: download workbook
```
#### Setup
The primary mechanism for invoking JS functions from Blazor is `IJSRuntime`[^1].
It should be injected at the top of relevant Razor component scripts:
```csharp title="Injecting IJSRuntime"
@inject IJSRuntime JS
```
#### Fire and Forget
When exporting a file with the SheetJS `writeFile` method[^2], browser APIs do
not provide success or error feedback. As a result, this demo invokes functions
using the `InvokeVoidAsync` static method[^3].
The following C# method will invoke the `export_method` method in the browser:
```csharp title="Invoking JS functions from C#"
private async Task ExportDataset() {
await JS.InvokeVoidAsync("export_method", data);
}
```
:::caution pass
**The JS methods must be defined in the global scope!**
In this demo, the script is added to the `HEAD` block of the root HTML file:
```html title="wwwroot/index.html"
<head>
<!-- ... meta / title / base / link tags -->
<link href="SheetJSBlazorWasm.styles.css" rel="stylesheet" />
<!-- highlight-start -->
<!-- script with `export_method` is in the HEAD block -->
<script>
/* In a normal script tag, Blazor JS can call this method */
async function export_method(...rows) {
/* display the array of objects */
console.log(rows);
}
</script>
<!-- highlight-end -->
</head>
```
When using `<script type="module">`, top-level function definitions are not
visible to Blazor by default. They must be attached to `globalThis`:
```html title="Attaching methods to globalThis"
<script type="module">
/* Using `type="module"`, Blazor JS cannot see this function definition */
async function export_method(...rows) {
/* display the array of objects */
console.log(rows);
}
// highlight-start
/* Once attached to `globalThis`, Blazor JS can call this method */
globalThis.export_method = export_method;
// highlight-end
</script>
```
:::
#### Blazor Callbacks
Methods are commonly bound to buttons in the Razor template using `@onclick`.
When the following button is clicked, Blazor will invoke `ExportDataset`:
```html title="Binding callback to a HTML button"
<button @onclick="ExportDataset">Export Dataset</button>
```
### State in Blazor
The example [presidents sheet](https://docs.sheetjs.com/pres.xlsx) has one
header row with "Name" and "Index" columns.
![`pres.xlsx` data](pathname:///pres.png)
#### C# Representation
The natural C# representation of a single row is a class object:
```csharp title="President class"
public class President {
public string Name { get; set; }
public int Index { get; set; }
}
var PrezClinton = new President() { Name = "Bill Clinton", Index = 42 };
```
The entire dataset is typically stored in an array of class objects:
```csharp title="President dataset"
private President[] data;
```
#### Data Interchange
`InvokeVoidAsync` can pass data from the C# state to a JS function:
```csharp
await JS.InvokeVoidAsync("export_method", data);
```
Each row in the dataset will be passed as a separate argument to the JavaScript
method, so the JavaScript code should collect the arguments:
```js title="Collecting rows in a JS callback"
/* NOTE: blazor spreads the C# array, so the ... spread syntax is required */
async function export_method(...rows) {
/* display the array of objects */
console.log(rows);
}
```
Each row is a simple JavaScript object.
:::caution pass
Blazor automatically spreads arrays. Each row is passed as a separate argument
to the JavaScript method.
The example method uses the JavaScript spread syntax to collect the arguments.
:::
#### Exporting Data
With the collected array of objects, the SheetJS `json_to_sheet` method[^4] will
generate a SheetJS worksheet[^5] from the dataset. After creating a workbook[^6]
object with the `book_new` method[^7], the file is written with `writeFile`[^2]:
<CodeBlock title="JS Callback for exporting datasets" language="javascript">{`\
/* NOTE: blazor spreads the C# array, so the spread is required */
async function export_method(...rows) {
const XLSX = await import("https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs");
const ws = XLSX.utils.json_to_sheet(rows);
const wb = XLSX.utils.book_new(ws, "Data");
XLSX.writeFile(wb, "SheetJSBlazor.xlsx");
}`}
</CodeBlock>
### HTML Tables
When displaying datasets, Razor components typically generate HTML tables:
```html title="Razor template from official starter"
<table class="table" id="weather-table">
<thead>
<tr><th>Date</th><th>Temp. (C)</th><th>Temp. (F)</th><th>Summary</th></tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
```
If it has an `id`, JS code on the frontend can find the table element using the
`document.getElementById` DOM method. A SheetJS workbook object can be generated
using the `table_to_book` method[^8] and exported with `writeFile`[^2]:
<CodeBlock title="JS Callback for exporting HTML TABLE elements" language="javascript">{`\
async function export_method() {
const XLSX = await import("https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs");
const wb = XLSX.utils.table_to_book(document.getElementById("weather-table"));
XLSX.writeFile(wb, "SheetJSBlazor.xlsx");
}`}
</CodeBlock>
This approach uses data that already exists in the document, so no additional
data is passed from C# to JavaScript.
## Complete Demo
The Blazor + WASM starter app includes a "Weather" component that displays data
from a C#-managed dataset. This demo uses SheetJS to export data in two ways:
- "Export Dataset" will send row objects from the underlying C# data store to
the frontend. The SheetJS `json_to_sheet` method[^4] builds the worksheet.
- "Export HTML Table" will scrape the table using the SheetJS `table_to_book`
method[^8]. No extra data will be sent to the frontend.
:::note Tested Deployments
This demo was tested in the following deployments:
| Architecture | Date |
|:-------------|:-----------|
| `darwin-x64` | 2025-04-17 |
| `darwin-arm` | 2025-04-24 |
| `win11-x64` | 2025-04-17 |
| `win11-arm` | 2025-04-24 |
:::
### Platform Configuration
0) Set the `DOTNET_CLI_TELEMETRY_OPTOUT` environment variable to `1`.
<details open>
<summary><b>How to disable telemetry</b> (click to hide)</summary>
<Tabs groupId="os">
<TabItem value="unix" label="Linux/MacOS">
Add the following line to `.profile`, `.bashrc` and `.zshrc`:
```bash title="(add to .profile , .bashrc , and .zshrc)"
export DOTNET_CLI_TELEMETRY_OPTOUT=1
```
Close and restart the Terminal to load the changes.
</TabItem>
<TabItem value="win" label="Windows">
Type `env` in the search bar and select "Edit the system environment variables".
In the new window, click the "Environment Variables..." button.
In the new window, look for the "System variables" section and click "New..."
Set the "Variable name" to `DOTNET_CLI_TELEMETRY_OPTOUT` and the value to `1`.
Click "OK" in each window (3 windows) and restart your computer.
</TabItem>
</Tabs>
</details>
1) Install .NET
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
For macOS x64 and ARM64, install the `dotnet-sdk` Cask with Homebrew:
```bash
brew install --cask dotnet-sdk
```
For Steam Deck Holo and other Arch Linux x64 distributions, the `dotnet-sdk` and
`dotnet-runtime` packages should be installed using `pacman`:
```bash
sudo pacman -Syu dotnet-sdk dotnet-runtime
```
https://dotnet.microsoft.com/en-us/download/dotnet/6.0 is the official source
for Windows and ARM64 Linux versions.
</details>
2) Open a new Terminal window in macOS or PowerShell window in Windows.
### App Creation
3) Create a new `blazorwasm` app:
```bash
dotnet new blazorwasm -o SheetJSBlazorWasm
cd SheetJSBlazorWasm
dotnet run
```
When the Blazor service runs, the terminal will display a URL:
```text
info: Microsoft.Hosting.Lifetime[14]
// highlight-next-line
Now listening on: http://localhost:6969
```
4) In a new browser window, open the displayed URL from Step 3.
5) Click the "Weather" link and confirm the page includes a data table.
6) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
### SheetJS Integration
7) Add the following script tag to `wwwroot/index.html` in the `HEAD` block:
<CodeBlock title="wwwroot/index.html (add within the HEAD block)" language="html">{`\
<script>
/* NOTE: blazor spreads the C# array, so the spread is required */
async function export_dataset(...rows) {
const XLSX = await import("https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs");
const ws = XLSX.utils.json_to_sheet(rows);
const wb = XLSX.utils.book_new(ws, "Data");
XLSX.writeFile(wb, "SheetJSBlazorDataset.xlsx");
}
\n\
async function export_html(id) {
const XLSX = await import("https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs");
const wb = XLSX.utils.table_to_book(document.getElementById(id));
XLSX.writeFile(wb, "SheetJSBlazorHTML.xlsx");
}
</script>`}
</CodeBlock>
8) Inject the `IJSRuntime` dependency near the top of `Pages/Weather.razor`:
```csharp title="Pages/Weather.razor (add highlighted lines)"
@page "/weather"
@inject HttpClient Http
// highlight-next-line
@inject IJSRuntime JS
```
9) Add an ID to the `TABLE` element in `Pages/Weather.razor`:
```html title="Pages/Weather.razor (add id to TABLE element)"
{
<!-- highlight-next-line -->
<table class="table" id="weather-table">
<thead>
<tr>
```
10) Add callbacks to the `@code` section in `Pages/Weather.razor`:
```csharp title="Pages/Weather.razor (add within the @code section)"
private async Task ExportDataset()
{
await JS.InvokeVoidAsync("export_dataset", forecasts);
}
private async Task ExportHTML()
{
await JS.InvokeVoidAsync("export_html", "weather-table");
}
```
11) Add Export buttons to the template in `Pages/Weather.razor`:
```csharp title="Pages/Weather.razor (add highlighted lines)"
<p>This component demonstrates fetching data from the server.</p>
<!-- highlight-start -->
<button @onclick="ExportDataset">Export Dataset</button>
<button @onclick="ExportHTML">Export HTML TABLE</button>
<!-- highlight-end -->
```
### Testing
12) Launch the `dotnet` process again:
```bash
dotnet run
```
When the Blazor service runs, the terminal will display a URL:
```text
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:6969
```
13) In a new browser window, open the displayed URL from Step 12.
14) Click the "Weather" link. The page should match the following screenshot:
![SheetJSBlazorWasm with Exports](pathname:///blazor/weather.png)
15) Click the "Export Dataset" button and save the generated file to
`SheetJSBlazorDataset.xlsx`. Open the file in a spreadsheet editor and confirm
the data matches the table. The column labels will differ since the underlying
dataset uses different labels.
![SheetJSBlazorDataset.xlsx](pathname:///blazor/dataset.png)
16) Click the "Export HTML TABLE" button and save the generated file to
`SheetJSBlazorHTML.xlsx`. Open the file in a spreadsheet editor and confirm the
data matches the table. The column labels will match the HTML table.
![SheetJSBlazorHTML.xlsx](pathname:///blazor/html.png)
:::note pass
It is somewhat curious that the official `dotnet` Blazor sample dataset marks
`1 C` and `-13 C` as "freezing" but marks `-2 C` as "chilly". It stands to
reason that `-2 C` should also be freezing.
:::
[^1]: See ["Microsoft.JSInterop.IJSRuntime"](https://learn.microsoft.com/en-us/dotnet/api/microsoft.jsinterop.ijsruntime) in the `dotnet` documentation.
[^2]: See [`writeFile` in "Writing Files"](/docs/api/write-options)
[^3]: See ["Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync"](https://learn.microsoft.com/en-us/dotnet/api/microsoft.jsinterop.jsruntimeextensions.invokevoidasync) in the `dotnet` documentation.
[^4]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
[^5]: See ["Sheet Objects"](/docs/csf/sheet)
[^6]: See ["Workbook Object"](/docs/csf/book)
[^7]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)
[^8]: See [`table_to_book` in "HTML" Utilities](/docs/api/utilities/html#create-new-sheet)

@ -2,7 +2,7 @@
title: Legacy Frameworks
pagination_prev: demos/index
pagination_next: demos/grid/index
sidebar_position: 18
sidebar_position: 9
sidebar_custom_props:
skip: 1
---
@ -25,9 +25,9 @@ the shim script must be loaded first:
<CodeBlock language="html">{`\
<!-- SheetJS version ${current} \`shim.min.js\` -->
<script type="text/javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js"></script>
<script lang="javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js"></script>
<!-- SheetJS version ${current} \`xlsx.full.min.js\` -->
<script type="text/javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
<script lang="javascript" src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
<script>
/* display SheetJS version */
if(typeof console == "object" && console.log) console.log(XLSX.version);
@ -38,7 +38,7 @@ else document.write(XLSX.version);
## Internet Explorer
:::danger pass
:::warning pass
Internet Explorer is unmaintained and users should consider modern browsers.
The SheetJS testing grid still includes IE and should work.
@ -48,8 +48,7 @@ The SheetJS testing grid still includes IE and should work.
The modern upload and download strategies are not available in older versions of
IE, but there are approaches using ActiveX or Flash.
<details>
<summary><b>Complete Example</b> (click to show)</summary>
<details><summary><b>Complete Example</b> (click to show)</summary>
This demo includes all of the support files for the Flash and ActiveX methods.
@ -77,8 +76,7 @@ npx -y http-server .
</details>
<details>
<summary><b>Other Live Demos</b> (click to show)</summary>
<details><summary><b>Other Live Demos</b> (click to show)</summary>
:::caution pass
@ -87,10 +85,10 @@ demo pages should be downloaded and hosted using a simple HTTP server.
:::
https://oss.sheetjs.com/sheetjs/ajax.html uses XMLHttpRequest to download test
<https://oss.sheetjs.com/sheetjs/ajax.html> uses XMLHttpRequest to download test
files and convert to CSV
https://oss.sheetjs.com/sheetjs/ demonstrates reading files with `FileReader`.
<https://oss.sheetjs.com/sheetjs/> demonstrates reading files with `FileReader`.
Older versions of IE do not support HTML5 File API but do support Base64.
@ -200,24 +198,18 @@ included in the page and the relevant features are enabled on the target system.
### KnockoutJS
[KnockoutJS](https://knockoutjs.com/) was a popular MVVM framework.
KnockoutJS was a popular MVVM framework.
The [Live demo](pathname:///knockout/knockout3.html) shows a view model that is
The [Live demo](pathname:///knockout/knockout.html) shows a view model that is
updated with file data and exported to spreadsheets.
:::note Tested Deployments
This demo was tested in the following environments:
| KnockoutJS | Date | Live Demo |
|:-----------|:-----------|:-----------------------------------------------|
| `3.5.0` | 2025-01-08 | [**KO3**](pathname:///knockout/knockout3.html) |
| `2.3.0` | 2025-01-08 | [**KO2**](pathname:///knockout/knockout2.html) |
This demo was last run on 2023 December 04 using KnockoutJS `3.4.2`
:::
<details>
<summary><b>Full Exposition</b> (click to show)</summary>
<details><summary><b>Full Exposition</b> (click to show)</summary>
**State**

@ -1,635 +0,0 @@
---
title: Sheets in UI5 Sites
sidebar_label: OpenUI5 / SAPUI5
description: Build enterprise-grade applications with OpenUI5. Seamlessly integrate spreadsheets into your app using SheetJS. Bring Excel-powered workflows and data to the modern web.
pagination_prev: demos/index
pagination_next: demos/grid/index
sidebar_position: 10
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
[OpenUI5](https://openui5.org/) is a JavaScript framework for building
enterprise-ready web applications. It is compatible with the SAPUI5 framework.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo shows how to handle spreadsheet data in OpenUI5 apps using SheetJS.
You'll learn how to load spreadsheet files, process their data, and generate
new spreadsheet exports.
:::info pass
[Docs Issue #20](https://git.sheetjs.com/sheetjs/docs.sheetjs.com/issues/20)
includes a complete example starting from the OpenUI5 "Worklist App Tutorial".
:::
## Installation
SheetJS libraries conform to the UI5 ECMAScript requirements[^1]. SheetJS
libraries can be loaded in a UI5 site at different points in the app lifecycle.
#### UI5 Module {#installation-define}
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
comply with AMD `define` semantics. They support `sap.ui.define` out of the box.
If the SheetJS Standalone script is saved to `webapp/xlsx.full.min.js`, the base
script `webapp/index.js` can load the `./xlsx.full.min` dependency:
```js title="webapp/index.js (loading the SheetJS dependency)"
sap.ui.define([
// highlight-next-line
"./xlsx.full.min", // relative path to script, without the file extension
/* ... other libraries ... */
], function (
// highlight-next-line
_XLSX // !! NOTE: this is not XLSX! A different variable name must be used
/* ... variables for the other libraries ... */,
) {
// highlight-next-line
alert(XLSX.version); // use XLSX in the callback
});
```
:::info pass
In some deployments, the function argument was `undefined`.
The standalone scripts add `window.XLSX`, so it is recommended to use `_XLSX`
in the function arguments and access the library with `XLSX` in the callback.
:::
#### HTML {#installation-html}
UI5 is typically loaded in a `SCRIPT` tag in `webapp/index.html`. Similarly,
[SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
can be loaded with a `SCRIPT` tag in the same HTML page:
<CodeBlock language="html" value="html" title="index.html (add in the HEAD block before UI5 scripts)">{`\
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
`}
</CodeBlock>
This will expose the `XLSX` global object, which includes the functions listed
in the ["API Reference"](/docs/api/) section of the documentation.
:::caution pass
**The SheetJS Standalone script must be loaded before the UI5 bootstrap script**:
<CodeBlock language="html" value="html" title="webapp/index.html (loading the SheetJS standalone script)">{`\
<head>
<meta charset="utf-8">
<title>UI5 Walkthrough</title>
<!-- The SheetJS Standalone script must be loaded before the UI5 bootstrap -->
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>
<!-- UI5 bootstrap script -->
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
...(other attributes)...
></script>
</head>
`}
</CodeBlock>
:::
## Internal State
The various SheetJS APIs work with various data shapes. The preferred state
depends on the application.
### JSON Model
The UI5 `JSONModel`[^2] is a client-side model implementation for JavaScript
object data. Think of it like a container that holds your spreadsheet data.
`JSONModel` provides powerful two-way data binding capabilities. UI5 will
automatically updates your webpage whenever the data changes. It will also
respond to changes when users interact with components in the webpage.
#### State {#json-state}
The example [presidents sheet](https://docs.sheetjs.com/pres.xlsx) has one
header row with "Name" and "Index" columns. The natural JS representation is an
object for each row, where the keys are specified in the first row:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
<tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
</td><td>
```js
[
{ Name: "Bill Clinton", Index: 42 },
{ Name: "GeorgeW Bush", Index: 43 },
{ Name: "Barack Obama", Index: 44 },
{ Name: "Donald Trump", Index: 45 },
{ Name: "Joseph Biden", Index: 46 }
]
```
</td></tr></tbody></table>
Here is a basic example of initializing a model. A more complete implementation
will be shown later.
```js title="UI5 JSONModel for rows of data"
sap.ui.define(["sap/ui/model/json/JSONModel"], function (JSONModel) {
// highlight-next-line
const oModel = new JSONModel({ presidents: [] });
});
```
#### Updating State {#json-update}
Starting from a spreadsheet file, the SheetJS [`read`](/docs/api/parse-options)
method parses the data into a SheetJS workbook object[^3]. After selecting a
worksheet, the [`sheet_to_json`](/docs/api/utilities/array#array-output) method
generates row objects that can be assigned to the model.
Here is a sample flow diagram and method for downloading a workbook, generating
rows from the first worksheet, and updating a UI5 `JSONModel`:
```mermaid
flowchart LR
url[(Remote\nFile)]
ab[(Data\nArrayBuffer)]
wb(SheetJS\nWorkbook)
ws(SheetJS\nWorksheet)
aoo(array of\nobjects)
model((JSON\nModel))
url --> |fetch\n\n| ab
ab --> |read\n\n| wb
wb --> |wb.Sheets\nselect sheet| ws
ws --> |sheet_to_json\n\n| aoo
aoo --> |setProperty\nfrom model| model
linkStyle 1,2,3 color:blue,stroke:blue;
```
```js title="Download workbook, extract data from first worksheet, and update JSONModel"
_loadExcelFile: async function () {
/* Download from https://docs.sheetjs.com/pres.xlsx */
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
// highlight-start
/* parse */
const wb = XLSX.read(f); // parse the array buffer
/* generate array of objects from first worksheet */
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
const data = XLSX.utils.sheet_to_json(ws); // generate objects
/* update JSONModel */
this.getView().getModel().setProperty("/presidents", data);
// highlight-end
}
```
#### Rendering Data {#json-render}
In UI5, the "Model-View-Controller"[^4] pattern is used to organize code and
separate concerns. The view defines the UI structure, the controller handles the
logic, and the model manages the data.
The following example uses the `Table` component[^5] to display data.
```xml title="Example View XML for displaying an array of objects"
<mvc:View>
<Page>
<!-- The Table component binds to the presidents array -->
<!-- highlight-next-line -->
<Table width="300px" items="{/presidents}">
<!-- Column definitions specify the table structure -->
<columns>
<Column><header><Text text="Name" /></header></Column>
<Column><header><Text text="Value" /></header></Column>
</columns>
<!-- ColumnListItem template defines how each row should be rendered -->
<!-- highlight-start -->
<items>
<ColumnListItem>
<cells>
<Text text="{Name}" />
<Text text="{Index}" />
</cells>
</ColumnListItem>
</items>
<!-- highlight-end -->
</Table>
</Page>
</mvc:View>
```
#### Exporting Data {#json-export}
The `getProperty` method[^6] of the `JSONModel` pulls data from the UI5 model.
If an array of objects was pushed with `setProperty`, the `getProperty` method
will return an array of objects.
The SheetJS [`json_to_sheet`](/docs/api/utilities/array#array-of-objects-input)
function will create a SheetJS worksheet object[^7] from the data in the array.
The [`book_new`](/docs/api/utilities/wb) method will create a SheetJS workbook
object that includes the new worksheet. [`writeFile`](/docs/api/write-options)
will attempt to generate a file and initiate a download.
```mermaid
flowchart LR
state((State\nJSONModel))
aoo[(Array of\nObjects)]
ws(SheetJS\nWorksheet)
wb(((SheetJS\nWorkbook)))
file[(XLSX\nexport)]
state --> |getProperty\n\n| aoo
aoo --> |json_to_sheet\n\n| ws
ws --> |book_new\n\n| wb
wb --> |writeFile\n\n| file
linkStyle 1,2,3 color:blue,stroke:blue;
```
Here is a sample method for exporting data from the UI5 `JSONModel` to XLSX:
```js title="Fetch data from JSONModel and export to XLSX"
/* get model data and export to XLSX */
onExport: function () {
const data = this.getView().getModel().getProperty("/presidents");
/* generate worksheet from model data */
// highlight-next-line
const ws = XLSX.utils.json_to_sheet(data);
/* create workbook and append worksheet */
const wb = XLSX.utils.book_new(ws, "Data");
/* export to XLSX */
XLSX.writeFileXLSX(wb, "SheetJSOpenUI5AoO.xlsx");
}
```
This method can be bound to the `press` event of a `sap.m.Button` control:
```xml title="Example View XML for exporting an array of objects to a workbook"
<mvc:View>
<Page>
<!-- The `onExport` method is bound to the `press` event -->
<Button text="Export Data" press=".onExport" />
</Page>
</mvc:View>
```
#### Complete Component
This complete component example fetches a test file and displays the contents in a table.
When the export button is clicked, an event handler will export a file:
##### View Implementation {#view-implementation}
```xml title="webapp/view/Main.view.xml"
<mvc:View controllerName="sheetjs.openui5.controller.Main" displayBlock="true" xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" xmlns:core="sap.ui.core" core:require="{formatter: 'sheetjs/openui5/model/formatter'}">
<Page>
<VBox width="auto" alignItems="Start">
<Table width="300px" items="{/presidents}">
<columns>
<Column><header><Text text="Name" /></header></Column>
<Column><header><Text text="Index" /></header></Column>
</columns>
<items><ColumnListItem><cells>
<Text text="{Name}" />
<Text text="{Index}" />
</cells></ColumnListItem></items>
</Table>
<Button text="Export XLSX" press=".onExport" />
</VBox>
</Page>
</mvc:View>
```
##### Controller Implementation {#controller-implementation}
```js title="webapp/controller/Main.controller.js"
sap.ui.define(
["./BaseController", "sap/ui/model/json/JSONModel"],
function (BaseController, JSONModel) {
"use strict";
return BaseController.extend("com.demo.xlsx.controller.Main", {
onInit: function () {
/* initialize model */
const oModel = new JSONModel({ presidents: [] });
this.getView().setModel(oModel);
/* load data */
this._loadExcelFile();
},
_loadExcelFile: async function () {
/* fetch and parse file */
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const wb = XLSX.read(f);
/* extract data from first worksheet */
const ws = wb.Sheets[wb.SheetNames[0]];
const data = XLSX.utils.sheet_to_json(ws);
/* update state model */
this.getView().getModel().setProperty("/presidents", data);
},
onExport: function () {
/* fetch data from model */
const data = this.getView().getModel().getProperty("/presidents");
/* generate workbook */
const ws = XLSX.utils.json_to_sheet(data);
const wb = XLSX.utils.book_new(ws, "Data");
/* export to XLSX */
XLSX.writeFileXLSX(wb, "SheetJSOpenUI5AoO.xlsx");
},
});
}
);
```
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
:::note Tested Deployments
This demo was tested in the following environments:
| OpenUI5 | Date |
|:----------|------------|
| `1.132.1` | 2025-01-24 |
:::
1) Create a new site:
```bash
npm i --global generator-easy-ui5
npx yo easy-ui5 app
```
When prompted, enter the following options:
- `Enter your application id (namespace)?`: Type `sheetjs.openui5` and press <kbd>Enter</kbd>
- `Which framework do you want to use?`: Press <kbd>Enter</kbd> (`OpenUI5` should be the default)
- `Which framework version do you want to use?`: Type `1.132.1` and press <kbd>Enter</kbd>
- `Who is the author of the application?`: Press <kbd>Enter</kbd> (use the default author)
- `Would you like to create a new directory for the application?`: Type `Y` and press <kbd>Enter</kbd>
- `Would you like to initialize a local git repository for the application?`: Type `N` and press <kbd>Enter</kbd>
![Expected output](pathname:///ui5/easy-ui5.png)
2) Install the dependencies and start server:
```bash
cd sheetjs.openui5
npm install
npm start
```
3) Open a web browser and access the displayed URL (`http://localhost:8080`).
In the file listing, click `index.html` to launch the app.
4) Add the SheetJS Standalone script to `webapp/index.html` after the `title` tag:
<CodeBlock language="html" value="html" title="webapp/index.html (add highlighted lines)">{`\
<title>UI5 Application: sheetjs.openui5</title>
<!-- highlight-next-line -->
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>`}
</CodeBlock>
5) Replace `webapp/view/Main.view.xml` with the `Main.view.xml` snippet in the
["View Implementation" section](#view-implementation).
6) Replace `webapp/controller/Main.controller.js` with the `Main.controller.js`
example in the ["Controller Implementation" section](#controller-implementation).
7) Switch back to the browser window.
The page will refresh and show a table with an Export button.
Click the button and the page will attempt to download `SheetJSOpenUI5AoO.xlsx`.
This file can be inspected with a spreadsheet editor.
8) Build the site:
```bash
npm run build:opt
```
The generated site will be placed in the `dist` folder.
:::caution pass
SAP recommends `npm run build`. This does not generate a proper standalone site!
Sites built with `npm run build` must be served with `npm run start:dist`.
This demo uses the `build:opt` target to ensure that a proper static site is
generated. The `dist` folder in this demo can be deployed on a static host.
:::
9) Start a local web server:
```bash
npx -y http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
and test the page.
When the page loads, the app will fetch https://docs.sheetjs.com/pres.xlsx and
display the data from the first worksheet in a TABLE. The "Export XLSX" button
will generate a workbook that can be opened in a spreadsheet editor.
</details>
### HTML
The main disadvantage of the Array of Objects approach is the specific nature
of the columns. For more general use, passing around an Array of Arrays works.
However, this does not handle merge cells[^8] well!
The [`sheet_to_html`](/docs/api/utilities/html#html-table-output) function
generates HTML that is aware of merges and other worksheet features.
To render the HTML string from the model, the property from the model should be
bound to the `content` property of a UI5 `core:HTML`[^9] control.
On export, the [`table_to_book`](/docs/api/utilities/html#html-table-input)
method creates a SheetJS workbook object from the rendered HTML table.
##### View Implementation {#view-implementation-html}
```xml title="webapp/view/Main.view.xml"
<mvc:View controllerName="sheetjs.openui5.controller.Main" displayBlock="true" xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" xmlns:core="sap.ui.core" xmlns:html="http://www.w3.org/1999/xhtml">
<Page>
<content>
<core:HTML id="tbl" content="{/tableHTML}" />
<Button text="Export XLSX" press=".onExport"/>
</content>
</Page>
</mvc:View>
```
##### Controller Implementation {#controller-implementation-html}
```js title="webapp/controller/Main.controller.js"
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel"
], function (Controller, JSONModel) {
"use strict";
return Controller.extend("sheetjs.openui5.controller.Main", {
onInit: function () {
/* the component state is an HTML string */
const oModel = new JSONModel({ tableHTML: "", });
this.getView().setModel(oModel);
/* load data */
this._loadExcelFile();
},
_loadExcelFile: async function () {
/* fetch and parse file */
const f = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const wb = XLSX.read(f);
/* generate HTML table from first worksheet */
const ws = wb.Sheets[wb.SheetNames[0]];
const opts = { header: `<table>`, footer: `</table>` };
const tableHTML = XLSX.utils.sheet_to_html(ws, opts);
/* update state model */
this.getView().getModel().setProperty("/tableHTML", tableHTML);
},
onExport: function () {
/* Get reference to the `TABLE` element in the model */
const table = this.getView().byId("tbl").getDomRef();
/* Generate workbook */
const wb = XLSX.utils.table_to_book(table);
/* Export to XLSX */
XLSX.writeFileXLSX(wb, "SheetJSOpenUI5HTML.xlsx");
},
});
}
);
```
<details open>
<summary><b>How to run the example</b> (click to hide)</summary>
:::note Tested Deployments
This demo was tested in the following environments:
| OpenUI5 | Date |
|:----------|------------|
| `1.132.1` | 2025-01-24 |
:::
1) Create a new site:
```bash
npm i --global generator-easy-ui5
npx yo easy-ui5 app
```
When prompted, enter the following options:
- `Enter your application id (namespace)?`: Type `sheetjs.openui5` and press <kbd>Enter</kbd>
- `Which framework do you want to use?`: Press <kbd>Enter</kbd> (`OpenUI5` should be the default)
- `Which framework version do you want to use?`: Type `1.132.1` and press <kbd>Enter</kbd>
- `Who is the author of the application?`: Press <kbd>Enter</kbd> (use the default author)
- `Would you like to create a new directory for the application?`: Type `Y` and press <kbd>Enter</kbd>
- `Would you like to initialize a local git repository for the application?`: Type `N` and press <kbd>Enter</kbd>
![Expected output](pathname:///ui5/easy-ui5.png)
2) Install the dependencies and start server:
```bash
cd sheetjs.openui5
npm install
npm start
````
3) Open a web browser and access the displayed URL (`http://localhost:8080`)
In the file listing, click `index.html` to launch the app.
4) Add the SheetJS Standalone script to `webapp/index.html` after the `title` tag:
<CodeBlock language="html" value="html" title="webapp/index.html (add highlighted lines)">{`\
<title>UI5 Application: sheetjs.openui5</title>
<!-- highlight-next-line -->
<script src="https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js"></script>`}
</CodeBlock>
5) Replace `webapp/view/Main.view.xml` with the `Main.view.xml` snippet in the
["View Implementation" section](#view-implementation-html).
6) Replace `webapp/controller/Main.controller.js` with the `Main.controller.js`
example in ["Controller Implementation"](#controller-implementation-html).
7) Switch back to the browser window.
The page will refresh and show a table with an Export button.
Click the button and the page will attempt to download `SheetJSOpenUI5HTML.xlsx`.
This file can be inspected with a spreadsheet editor.
8) Build the site:
```bash
npm run build:opt
```
The generated site will be placed in the `dist` folder.
:::caution pass
SAP recommends `npm run build`. This does not generate a proper standalone site!
Sites built with `npm run build` must be served with `npm run start:dist`.
This demo uses the `build:opt` target to ensure that a proper static site is
generated. The `dist` folder in this demo can be deployed on a static host.
:::
9) Start a local web server:
```bash
npx -y http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
and test the page.
When the page loads, the app will fetch https://docs.sheetjs.com/pres.xlsx and
display the data from the first worksheet in a TABLE. The "Export XLSX" button
will generate a workbook that can be opened in a spreadsheet editor.
</details>
[^1]: See ["ECMAScript Support"](https://sdk.openui5.org/topic/0cb44d7a147640a0890cefa5fd7c7f8e.html#loio0cb44d7a147640a0890cefa5fd7c7f8e/section_UI5Mod) for more details about OpenUI5 compatibility.
[^2]: See [`JSONModel`](https://sdk.openui5.org/1.38.62/docs/api/symbols/sap.ui.model.json.JSONModel.html) in the OpenUI5 documentation.
[^3]: See ["SheetJS Data Model"](/docs/csf/)
[^4]: See [OpenUI5's MVC Documentation](https://sdk.openui5.org/topic/91f233476f4d1014b6dd926db0e91070) for detailed explanation of the pattern implementation.
[^5]: See ["List, List Item, and Table"](https://sdk.openui5.org/topic/295e44b2d0144318bcb7bdd56bfa5189) in the OpenUI5 documentation.
[^6]: See [`getProperty` of class `sap.ui.model.json.JSONModel`](https://sdk.openui5.org/api/sap.ui.model.json.JSONModel#methods/getProperty) in the OpenUI5 documentation.
[^7]: See ["Worksheet Object" in "SheetJS Data Model"](/docs/csf/sheet) for more details.
[^8]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges) for more details.
[^9]: See [`core:HTML`](https://sdk.openui5.org/1.38.62/docs/api/symbols/sap.ui.core.HTML.html) in the OpenUI5 documentation.

@ -41,10 +41,9 @@ This demo was tested in the following environments:
| ViteJS | Date |
|:---------|:-----------|
| `6.2.3` | 2025-03-30 |
| `5.4.15` | 2025-03-30 |
| `4.5.10` | 2025-03-30 |
| `3.2.11` | 2025-03-30 |
| `5.0.5` | 2023-12-04 |
| `4.5.0` | 2023-12-04 |
| `3.2.7` | 2023-12-05 |
:::
@ -99,7 +98,7 @@ interface President {
async function xport() {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data: President[] = await (await fetch(url)).json();
/* filter for the Presidents */
@ -144,24 +143,6 @@ writeFileXLSX(workbook, "Presidents.xlsx");
npm run dev
```
:::caution pass
When this was last tested in ViteJS version 4, the process crashed with the error
```
Search string not found: "for (const existingRoot of buildInfoVersionMap.roots) {"
```
**This is a known issue with ViteJS 4 and its dependency tree!**[^1]
The recommended workaround is to forcefully upgrade `vue-tsc`:
```bash
npm i "vue-tsc@2"
```
:::
5) Open a web browser to `http://localhost:5173/` and click the export button.
6) Build the production site:
@ -173,10 +154,8 @@ npx vite build
7) Verify the new site by running a local web server in the `dist` folder:
```bash
npx -y http-server dist
npx http-server dist
```
8) Access the displayed URL (typically `http://localhost:8080`) in a web browser
and click the export button.
[^1]: See [issue 4484 in the `vuejs/language-tools` repository on GitHub](https://github.com/vuejs/language-tools/issues/4484) for the issue triage and workaround. This issue does not affect other ViteJS major versions.

@ -47,23 +47,8 @@ This demo was tested in the following environments:
| ESBuild | Date |
|:----------|:-----------|
| `0.25.5` | 2025-06-02 |
| `0.24.2` | 2025-06-02 |
| `0.23.1` | 2025-06-02 |
| `0.22.0` | 2025-06-02 |
| `0.21.5` | 2025-06-02 |
| `0.20.2` | 2025-06-02 |
| `0.19.12` | 2025-06-02 |
| `0.18.20` | 2025-06-02 |
| `0.17.19` | 2025-06-02 |
| `0.16.17` | 2025-06-02 |
| `0.15.18` | 2025-06-02 |
| `0.14.54` | 2025-06-02 |
| `0.13.15` | 2025-06-02 |
| `0.12.29` | 2025-06-02 |
| `0.11.23` | 2025-06-02 |
| `0.10.2` | 2025-06-02 |
| `0.9.7` | 2025-06-02 |
| `0.14.14` | 2023-12-04 |
| `0.19.8` | 2023-12-04 |
:::
@ -91,7 +76,7 @@ Assuming the primary source file is `in.js`, the following command will bundle
the script and generate `out.js`:
```bash
npx -y esbuild@0.25.5 in.js --bundle --outfile=out.js
npx -y esbuild@0.19.8 in.js --bundle --outfile=out.js
```
### Browser Demo
@ -140,13 +125,13 @@ curl -LO https://docs.sheetjs.com/esbuild/esbrowser.js
4) Create bundle:
```bash
npx -y esbuild@0.25.5 esbrowser.js --bundle --outfile=esb.browser.js
npx -y esbuild@0.19.8 esbrowser.js --bundle --outfile=esb.browser.js
```
5) Start a local HTTP server:
```bash
npx -y http-server .
npx http-server .
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser.
@ -180,7 +165,7 @@ Assuming the primary source file is `in.js`, the following command will bundle
the script for NodeJS and generate `out.js`:
```bash
npx -y esbuild@0.25.5 in.js --bundle --platform=node --outfile=out.js
npx -y esbuild@0.19.8 in.js --bundle --platform=node --outfile=out.js
```
### NodeJS Demo
@ -229,7 +214,7 @@ curl -LO https://docs.sheetjs.com/esbuild/esbnode.js
3) Create bundle:
```bash
npx -y esbuild@0.25.5 esbnode.js --bundle --platform=node --outfile=esb.node.js
npx -y esbuild@0.19.8 esbnode.js --bundle --platform=node --outfile=esb.node.js
```
4) Run the bundle:

@ -41,10 +41,10 @@ This demo was tested in the following environments:
| Version | Date | Required Workarounds |
|:---------|:-----------|:------------------------------------|
| `5.97.1` | 2025-01-03 | |
| `4.47.0` | 2025-01-03 | |
| `3.12.0` | 2025-01-03 | Import `xlsx/dist/xlsx.full.min.js` |
| `2.7.0` | 2025-01-03 | Import `xlsx/dist/xlsx.full.min.js` |
| `2.7.0` | 2024-03-16 | Import `xlsx/dist/xlsx.full.min.js` |
| `3.12.0` | 2024-03-16 | Import `xlsx/dist/xlsx.full.min.js` |
| `4.47.0` | 2024-03-16 | Downgrade NodeJS (tested v16.20.2) |
| `5.90.3` | 2024-03-16 | |
:::
@ -167,7 +167,7 @@ import { utils, version, writeFileXLSX } from 'xlsx';
document.getElementById("xport").addEventListener("click", function() {
/* fetch JSON data and parse */
var url = "https://docs.sheetjs.com/executive.json";
var url = "https://sheetjs.com/data/executive.json";
fetch(url).then(function(res) { return res.json(); }).then(function(raw_data) {
/* filter for the Presidents */
@ -177,7 +177,7 @@ document.getElementById("xport").addEventListener("click", function() {
prez.forEach(function(row) {
row.start = row.terms.find(function(term) {
return term.type === "prez";
}).start;
}).start
});
prez.sort(function(l,r) { return l.start.localeCompare(r.start); });
@ -260,7 +260,7 @@ npx webpack@3.x -p
</TabItem>
<TabItem value="4+" label="4.x, 5.x and beyond" default>
:::danger Pinning specific versions of webpack
:::warning Pinning specific versions of webpack
The webpack tooling is not designed for switching between versions. A specific
version above 4.0 can be pinned by locally installing webpack and the CLI tool.
@ -269,16 +269,15 @@ version above 4.0 can be pinned by locally installing webpack and the CLI tool.
**Webpack 4.x**
:::note pass
:::info pass
Some Webpack 4 versions are incompatible with Node 18+. They will elicit the
following error:
Webpack 4 is incompatible with Node 18+. It will elicit the following error:
```
Error: error:0308010C:digital envelope routines::unsupported
```
In some demo tests, NodeJS was locally downgraded to 16.20.2
When this demo was last tested, NodeJS was locally downgraded to 16.20.2
:::
@ -322,7 +321,7 @@ npx webpack --mode=production
6) Start a local HTTP server:
```bash
npx -y http-server .
npx http-server .
```
7) Load the displayed URL (typically `http://localhost:8080/`) in a web browser.

@ -34,21 +34,8 @@ This demo was tested in the following environments:
| Browserify | Date |
|:-----------|:-----------|
| `17.0.1` | 2025-06-18 |
| `16.5.2` | 2025-06-18 |
| `15.2.0` | 2025-06-18 |
| `14.5.0` | 2025-06-18 |
| `13.3.0` | 2025-06-18 |
| `12.0.2` | 2025-06-18 |
| `11.2.0` | 2025-06-18 |
| `10.2.6` | 2025-06-18 |
| `9.0.8` | 2025-06-18 |
| `8.1.3` | 2025-06-18 |
| `7.1.0` | 2025-06-18 |
| `6.3.4` | 2025-06-18 |
| `5.13.1` | 2025-06-18 |
| `4.2.3` | 2025-06-18 |
| `3.46.1` | 2025-06-18 |
| `17.0.0` | 2023-12-04 |
| `3.46.1` | 2023-12-04 |
:::
@ -105,7 +92,7 @@ const { utils, version, writeFileXLSX } = require('xlsx');
document.getElementById("xport").addEventListener("click", function() {
/* fetch JSON data and parse */
var url = "https://docs.sheetjs.com/executive.json";
var url = "https://sheetjs.com/data/executive.json";
fetch(url).then(function(res) { return res.json(); }).then(function(raw_data) {
/* filter for the Presidents */
@ -115,7 +102,7 @@ document.getElementById("xport").addEventListener("click", function() {
prez.forEach(function(row) {
row.start = row.terms.find(function(term) {
return term.type === "prez";
}).start;
}).start
});
prez.sort(function(l,r) { return l.start.localeCompare(r.start); });
@ -177,7 +164,7 @@ npm install --save browserify@3.46.1
5) Start a local HTTP server:
```bash
npx -y http-server .
npx http-server .
```
6) Load the displayed URL (typically `http://localhost:8080/`) in a web browser.

@ -40,8 +40,8 @@ This demo was tested in the following environments:
| RequireJS | Date |
|:----------|:-----------|
| `2.3.7` | 2025-01-07 |
| `2.1.22` | 2025-01-07 |
| `2.3.6` | 2024-03-01 |
| `2.1.22` | 2023-12-04 |
:::
@ -51,7 +51,7 @@ The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
comply with AMD `define` semantics. They support RequireJS and the `r.js`
optimizer out of the box.
### Configuration
### Config
The RequireJS config should set the `xlsx` alias in the `paths` property.
@ -162,7 +162,7 @@ example, the following script corresponds to RequireJS `2.1.22`:
require(["xlsx"], function(XLSX) {
document.getElementById("xport").addEventListener("click", function() {
/* fetch JSON data and parse */
var url = "https://docs.sheetjs.com/executive.json";
var url = "https://sheetjs.com/data/executive.json";
fetch(url).then(function(res) { return res.json(); }).then(function(raw_data) {
/* filter for the Presidents */
@ -209,7 +209,7 @@ uses normal functions and traditional Promise chains.
3) Start a local HTTP server:
```bash
npx -y http-server .
npx http-server .
```
4) Load the displayed URL (typically `http://localhost:8080/`) in a web browser.

@ -46,11 +46,11 @@ This demo was tested in the following environments:
| Version | Platform | Date |
|:----------|:---------|:-----------|
| `0.19.47` | NodeJS | 2025-01-03 |
| `0.20.16` | Browser | 2025-01-03 |
| `0.20.19` | NodeJS | 2025-03-03 |
| `0.21.6` | NodeJS | 2025-03-03 |
| `6.15.1` | NodeJS | 2025-01-03 |
| `0.19.47` | NodeJS | 2024-03-31 |
| `0.20.16` | Browser | 2024-03-31 |
| `0.20.19` | NodeJS | 2024-03-31 |
| `0.21.6` | NodeJS | 2024-03-31 |
| `6.14.3` | NodeJS | 2024-03-31 |
:::
@ -203,7 +203,7 @@ npm init -y
1) Install the dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz systemjs@6.15.1`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz systemjs@6.14.3`}
</CodeBlock>
2) Download [`SheetJSystem.js`](pathname:///systemjs/SheetJSystem.js) and move

@ -34,10 +34,10 @@ This demo was tested in the following environments:
| Version | Date |
|:---------|:-----------|
| `4.29.1` | 2025-01-03 |
| `3.29.5` | 2025-01-03 |
| `2.79.1` | 2025-01-03 |
| `1.32.1` | 2025-01-03 |
| `4.13.0` | 2024-03-25 |
| `3.29.4` | 2024-03-25 |
| `2.79.1` | 2024-03-25 |
| `1.32.1` | 2024-03-25 |
:::
@ -111,7 +111,7 @@ import { utils, version, writeFileXLSX } from 'xlsx';
document.getElementById("xport").addEventListener("click", async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
@ -169,7 +169,7 @@ This step will create `bundle.js`
5) Start a local HTTP server:
```bash
npx -y http-server .
npx http-server .
```
Access the displayed URL (typically `http://localhost:8080/`) in a web browser.

@ -34,8 +34,8 @@ This demo was tested in the following environments:
| Version | Date |
|:---------|:-----------|
| `2.14.4` | 2025-05-07 |
| `1.12.3` | 2025-05-07 |
| `2.10.3` | 2023-12-04 |
| `1.12.3` | 2023-12-04 |
:::
@ -51,7 +51,7 @@ can load relevant parts of the library:
import { read, utils, writeFileXLSX } from 'xlsx';
```
:::danger Parcel Bug
:::warning Parcel Bug
Errors of the form `Could not statically evaluate fs call` stem from a Parcel
bug[^1]. Upgrade to Parcel version 1.5.0 or later.
@ -86,7 +86,7 @@ import { utils, version, writeFileXLSX } from 'xlsx';
document.getElementById("vers").innerText = version;
document.getElementById("xport").onclick = async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
@ -128,14 +128,13 @@ document.getElementById("xport").onclick = async() => {
For ParcelJS version 1, the entire script should be copied to `index.js` and the
main `index.html` page should load the `index.js` script:
<details>
<summary><b>ParcelJS v1 example</b> (click to show)</summary>
<details><summary><b>ParcelJS v1 example</b> (click to show)</summary>
```html title="index.html"
<body>
<h3>SheetJS <span id="vers"></span> export demo</h3>
<button id="xport">Click to Export!</button>
<script src="index.js" type="module"></script>
<script src="index.js"></script>
<body>
```
@ -146,7 +145,7 @@ import { utils, version, writeFileXLSX } from 'xlsx';
document.getElementById("vers").innerText = version;
document.getElementById("xport").onclick = async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
@ -208,7 +207,7 @@ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
3) Run the ParcelJS development server:
```bash
npx -y parcel index.html
npx -y parcel@2.10.3 index.html
```
The process will print a URL:
@ -231,7 +230,7 @@ a web browser and click the "Click to Export!" button to generate a file.
6) Build the production site:
```bash
npx -y parcel build index.html
npx -y parcel@2.10.0 build index.html
```
The production site will be stored in the `dist` folder
@ -239,7 +238,7 @@ The production site will be stored in the `dist` folder
7) Start a local web server and serve the `dist` folder:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080/`) in a web browser.

@ -35,7 +35,7 @@ This demo was tested in the following environments:
| Version | Date |
|:----------|:-----------|
| `1.21.1` | 2025-06-18 |
| `1.2.246` | 2023-12-04 |
:::
@ -63,9 +63,9 @@ part of the `utils` object, the required import is:
import { utils, writeFile } from 'xlsx';
```
:::caution pass
:::warning pass
When this demo was tested against recent versions of `@swc/core`, `spack` crashed:
When this demo was tested against the `@swc/core@1.3.100`, `spack` crashed:
```
thread '<unnamed>' panicked at 'cannot access a scoped thread local variable without calling `set` first',
@ -73,10 +73,7 @@ thread '<unnamed>' panicked at 'cannot access a scoped thread local variable wit
**This is a bug in SWC**
This bug is known to affect versions `1.3.100`, `1.4.17`, and `1.10.6`.
This bug was fixed in version `1.21.1`. It is strongly recommended to upgrade
existing projects to use `1.21.1` or to downgrade to `1.2.246`.
Until the bug is fixed, it is strongly recommended to use `@swc/core@1.2.246`.
:::
@ -95,17 +92,17 @@ npm init -y
<Tabs groupId="pm">
<TabItem value="npm" label="npm">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz regenerator-runtime @swc/cli @swc/core@1.21.1`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz regenerator-runtime @swc/cli @swc/core@1.2.246`}
</CodeBlock>
</TabItem>
<TabItem value="pnpm" label="pnpm">
<CodeBlock language="bash">{`\
pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz regenerator-runtime @swc/cli @swc/core@1.21.1`}
pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz regenerator-runtime @swc/cli @swc/core@1.2.246`}
</CodeBlock>
</TabItem>
<TabItem value="yarn" label="Yarn" default>
<CodeBlock language="bash">{`\
yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz regenerator-runtime @swc/cli @swc/core@1.21.1`}
yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz regenerator-runtime @swc/cli @swc/core@1.2.246`}
</CodeBlock>
</TabItem>
</Tabs>
@ -124,7 +121,7 @@ import { utils, version, writeFileXLSX } from 'xlsx';
document.getElementById("xport").addEventListener("click", async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
@ -196,7 +193,7 @@ This command will create the script `lib/web.js`
6) Start a local HTTP server, then go to `http://localhost:8080/`
```bash
npx -y http-server .
npx http-server .
```
Click on "Click here to export" to generate a file.

@ -43,18 +43,9 @@ Complete Examples are included [in the "Dojo" demo](/docs/demos/frontend/dojo)
## Snowpack
Snowpack was a development tool built by the AstroJS team.
:::caution pass
Snowpack is no longer maintained. The developers recommend [ViteJS](/docs/demos/frontend/bundler/vitejs)
:::
Snowpack works with no caveats.
<details>
<summary><b>Complete Example</b> (click to show)</summary>
<details><summary><b>Complete Example</b> (click to show)</summary>
:::note Tested Deployments
@ -62,7 +53,7 @@ This demo was tested in the following environments:
| Version | Date |
|:--------|:-----------|
| `3.8.8` | 2025-01-07 |
| `3.8.8` | 2023-12-04 |
:::
@ -102,7 +93,7 @@ import { utils, version, writeFileXLSX } from 'xlsx';
document.getElementById("xport").addEventListener("click", async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
@ -161,32 +152,21 @@ Unlike other bundlers, Snowpack requires a full page including `HEAD` element.
npx snowpack@3.8.8 build
```
5) Start a local HTTP server:
5) Start a local HTTP server, then go to `http://localhost:8080/`
```bash
npx -y http-server build/
npx http-server build/
```
6) Open a web browser to the displayed URL (typically `http://localhost:8080/`).
Click on "Click here to export" to generate a file.
</details>
## WMR
WMR was a development tool built by the PreactJS team.
:::caution pass
WMR is no longer maintained. The developers recommend [ViteJS](/docs/demos/frontend/bundler/vitejs)
:::
WMR works with no caveats.
<details>
<summary><b>Complete Example</b> (click to show)</summary>
<details><summary><b>Complete Example</b> (click to show)</summary>
:::note Tested Deployments
@ -194,7 +174,7 @@ This demo was tested in the following environments:
| Version | Date |
|:--------|:-----------|
| `3.8.0` | 2025-01-07 |
| `3.8.0` | 2023-12-04 |
:::
@ -234,7 +214,7 @@ import { utils, version, writeFileXLSX } from 'xlsx';
document.getElementById("xport").addEventListener("click", async() => {
/* fetch JSON data and parse */
const url = "https://docs.sheetjs.com/executive.json";
const url = "https://sheetjs.com/data/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
@ -287,14 +267,12 @@ writeFileXLSX(workbook, "Presidents.xlsx");
npx wmr@3.8.0 build
```
5) Start a local HTTP server:
5) Start a local HTTP server in `dist` folder and go to `http://localhost:8080/`
```bash
npx -y http-server dist/
npx http-server dist/
```
6) Open a web browser to the displayed URL (typically `http://localhost:8080/`).
Click on "Click here to export" to generate a file.
</details>

@ -65,7 +65,7 @@ The following demos are in separate pages:
<a href={item.href}>{item.label}</a>{item.customProps?.summary && (" - " + item.customProps.summary)}
</li>);
})}
<li><a href="/docs/demos/frontend/bundler/#dojo">Dojo Toolkit</a></li>
<li><a href="/docs/demos/frontend/bundler/#snowpack">Snowpack</a></li>
<li><a href="/docs/demos/frontend/bundler/#wmr">WMR</a></li>
<li><a href="/docs/demos/frontend/bundler#dojo">Dojo Toolkit</a></li>
<li><a href="/docs/demos/frontend/bundler#snowpack">Snowpack</a></li>
<li><a href="/docs/demos/frontend/bundler#wmr">WMR</a></li>
</ul>

@ -11,19 +11,10 @@ pagination_next: demos/net/upload/index
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
A number of JavaScript APIs, including `XMLHttpRequest` and `fetch`, allow
scripts to download spreadsheets for further processing.
This demo uses various APIs and wrapper libraries to download workbooks and pass
raw binary data to SheetJS libraries.
- ["Browser Demos"](#browser-demos) run entirely within the web browser. A test
workbook will be downloaded and parsed in the web browser.
- ["NodeJS Demos"](#nodejs-demos) run in NodeJS and other server-side platforms.
`XMLHttpRequest` and `fetch` browser APIs enable binary data transfer between
web browser clients and web servers. Since this library works in web browsers,
server conversion work can be offloaded to the client! This demo shows a few
common scenarios involving browser APIs and popular wrapper libraries.
:::info pass
@ -45,7 +36,7 @@ functions functions can send files to clients.
:::
## Binary Data
## Downloading Binary Data
Most interesting spreadsheet files are binary data that contain byte sequences
that represent invalid UTF-8 characters.
@ -71,7 +62,7 @@ flowchart LR
```js
/* download data into an ArrayBuffer object */
const res = await fetch("https://docs.sheetjs.com/pres.numbers");
const res = await fetch("https://sheetjs.com/pres.numbers");
const ab = await res.arrayBuffer(); // recover data as ArrayBuffer
/* parse file */
@ -80,19 +71,19 @@ const wb = XLSX.read(ab);
## Browser Demos
When the page is accessed, https://docs.sheetjs.com/pres.numbers will be fetched
and parsed. The old table will be replaced with a table whose contents match the
first worksheet. The SheetJS `sheet_to_html` method[^2] creates the HTML table.
When the page is accessed, the browser will attempt to download <https://sheetjs.com/pres.numbers>
and read the workbook. The old table will be replaced with a table whose
contents match the first worksheet. The table is generated using the SheetJS
`sheet_to_html` method[^2]
:::note Tested Deployments
Each browser demo was tested in the following environments:
| Browser | Date |
|:-------------|:-----------|
| Chromium 133 | 2025-03-30 |
| Safari 18.3 | 2025-03-30 |
| Konqueror 22 | 2025-04-23 |
| Browser | Date |
|:------------|:-----------|
| Chrome 120 | 2024-01-30 |
| Safari 17.2 | 2024-01-15 |
:::
@ -117,10 +108,9 @@ req.onload = function(e) {
req.send();
```
<details>
<summary><b>Live Download demo</b> (click to show)</summary>
<details><summary><b>Live Download demo</b> (click to show)</summary>
This demo uses `XMLHttpRequest` to fetch https://docs.sheetjs.com/pres.numbers
This demo uses `XMLHttpRequest` to download <https://sheetjs.com/pres.numbers>
and show the data in an HTML table.
```jsx live
@ -131,7 +121,7 @@ function SheetJSXHRDL() {
React.useEffect(() => { (async() => {
/* Fetch file */
const req = new XMLHttpRequest();
req.open("GET", "https://docs.sheetjs.com/pres.numbers", true);
req.open("GET", "https://sheetjs.com/pres.numbers", true);
req.responseType = "arraybuffer";
req.onload = e => {
/* Parse file */
@ -170,11 +160,10 @@ fetch(url).then(function(res) {
});
```
<details>
<summary><b>Live Download demo</b> (click to show)</summary>
<details><summary><b>Live Download demo</b> (click to show)</summary>
This demo uses `fetch` to download https://docs.sheetjs.com/pres.numbers and
show the data in an HTML table.
This demo uses `fetch` to download <https://sheetjs.com/pres.numbers> and show
the data in an HTML table.
```jsx live
function SheetJSFetchDL() {
@ -183,7 +172,7 @@ function SheetJSFetchDL() {
/* Fetch and update HTML */
React.useEffect(() => { (async() => {
/* Fetch file */
const res = await fetch("https://docs.sheetjs.com/pres.numbers");
const res = await fetch("https://sheetjs.com/pres.numbers");
const ab = await res.arrayBuffer();
/* Parse file */
@ -218,7 +207,7 @@ In a GET request, the default behavior is to return a `Blob` object. Passing
```js
$.ajax({
type: "GET", url: "https://docs.sheetjs.com/pres.numbers",
type: "GET", url: "https://sheetjs.com/pres.numbers",
/* suppress jQuery post-processing */
// highlight-next-line
@ -265,11 +254,10 @@ async function workbook_dl_axios(url) {
}
```
<details>
<summary><b>Live Download demo</b> (click to show)</summary>
<details><summary><b>Live Download demo</b> (click to show)</summary>
This demo uses `axios` to download https://docs.sheetjs.com/pres.numbers and
show the data in an HTML table.
This demo uses `axios` to download <https://sheetjs.com/pres.numbers> and show
the data in an HTML table.
:::caution pass
@ -291,7 +279,7 @@ function SheetJSAxiosDL() {
React.useEffect(() => { (async() => {
if(typeof axios != "function") return setHTML("ReferenceError: axios is not defined");
/* Fetch file */
const res = await axios("https://docs.sheetjs.com/pres.numbers", {responseType: "arraybuffer"});
const res = await axios("https://sheetjs.com/pres.numbers", {responseType: "arraybuffer"});
/* Parse file */
const wb = XLSX.read(res.data);
@ -327,11 +315,10 @@ superagent
});
```
<details>
<summary><b>Live Download demo</b> (click to show)</summary>
<details><summary><b>Live Download demo</b> (click to show)</summary>
This demo uses `superagent` to download https://docs.sheetjs.com/pres.numbers
and show the data in an HTML table.
This demo uses `superagent` to download <https://sheetjs.com/pres.numbers> and
show the data in an HTML table.
:::caution pass
@ -355,7 +342,7 @@ function SheetJSSuperAgentDL() {
return setHTML("ReferenceError: superagent is not defined");
/* Fetch file */
superagent
.get("https://docs.sheetjs.com/pres.numbers")
.get("https://sheetjs.com/pres.numbers")
.responseType("arraybuffer")
.end((err, res) => {
/* Parse file */
@ -384,7 +371,7 @@ The `https` module provides a low-level `get` method for HTTPS GET requests:
```js title="SheetJSHTTPSGet.js"
var https = require("https"), XLSX = require("xlsx");
https.get('https://docs.sheetjs.com/pres.xlsx', function(res) {
https.get('https://sheetjs.com/pres.numbers', function(res) {
var bufs = [];
res.on('data', function(chunk) { bufs.push(chunk); });
res.on('end', function() {
@ -397,36 +384,14 @@ https.get('https://docs.sheetjs.com/pres.xlsx', function(res) {
});
```
:::note Tested Deployments
<details><summary><b>Complete Example</b> (click to show)</summary>
This demo was tested in the following environments:
:::note Tested Environments
| NodeJS | Date | Workarounds |
|:-----------|:-----------|:-------------------------------|
| `0.10.48` | 2025-03-31 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `0.12.18` | 2025-03-31 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `4.9.1` | 2025-03-31 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `6.17.1` | 2025-03-31 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `8.17.0` | 2025-03-31 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `10.24.1` | 2025-03-31 | |
| `12.22.12` | 2025-03-31 | |
| `14.21.3` | 2025-03-31 | |
| `16.20.2` | 2025-03-31 | |
| `18.20.8` | 2025-03-31 | |
| `20.19.0` | 2025-03-31 | |
| `22.14.0` | 2025-03-31 | |
The `NODE_TLS_REJECT_UNAUTHORIZED` workaround sets the value to `'0'`:
```js title="Legacy NodeJS Certificate has Expired Bypass (prepend to script)"
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
```
This demo was last tested on 2024 January 15 against NodeJS `20.11.0`
:::
<details>
<summary><b>Complete Example</b> (click to show)</summary>
1) Install the [NodeJS module](/docs/getting-started/installation/nodejs)
<CodeBlock language="bash">{`\
@ -443,24 +408,11 @@ node SheetJSHTTPSGet.js
If successful, the script will print CSV contents of the test file.
:::caution pass
For older versions of NodeJS, the script may fail due to a certificate error.
The error can be suppressed by prepending the following line to the script:
```js title="SheetJSHTTPSGet.js (add to top)"
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
```
**It is strongly encouraged to upgrade to a newer NodeJS version!**
:::
</details>
### fetch
:::info pass
:::caution pass
Experimental support for `fetch` was introduced in NodeJS `16.15.0`. It will be
considered stable in NodeJS LTS version `22`.
@ -479,21 +431,14 @@ async function parse_from_url(url) {
}
```
:::note Tested Deployments
<details><summary><b>Complete Example</b> (click to show)</summary>
This demo was tested in the following environments:
:::note Tested Environments
| NodeJS | Date |
|:-----------|:-----------|
| `18.20.8` | 2025-03-30 |
| `20.18.0` | 2025-03-30 |
| `22.14.0` | 2025-03-30 |
This demo was last tested on 2024 January 15 against NodeJS `20.11.0`
:::
<details>
<summary><b>Complete Example</b> (click to show)</summary>
1) Install the [NodeJS module](/docs/getting-started/installation/nodejs)
<CodeBlock language="bash">{`\
@ -514,7 +459,7 @@ async function parse_from_url(url) {
}
(async() => {
const wb = await parse_from_url('https://docs.sheetjs.com/pres.numbers');
const wb = await parse_from_url('https://sheetjs.com/pres.numbers');
/* print the first worksheet to console */
var ws = wb.Sheets[wb.SheetNames[0]];
console.log(XLSX.utils.sheet_to_csv(ws));
@ -538,7 +483,7 @@ was added to the platform, third party modules wrapped the native APIs.
#### request
:::danger pass
:::warning pass
`request` has been deprecated and should only be used in legacy deployments.
@ -548,7 +493,7 @@ Setting the option `encoding: null` passes raw buffers:
```js title="SheetJSRequest.js"
var XLSX = require('xlsx'), request = require('request');
var url = 'https://docs.sheetjs.com/pres.xlsx';
var url = 'https://sheetjs.com/pres.numbers';
/* call `request` with the option `encoding: null` */
// highlight-next-line
@ -565,36 +510,14 @@ request(url, {encoding: null}, function(err, res, data) {
});
```
:::note Tested Deployments
<details><summary><b>Complete Example</b> (click to show)</summary>
This demo was tested in the following environments:
:::note Tested Environments
| NodeJS | `request` | Date | Workarounds |
|:-----------|:----------|:-----------|:-------------------------------|
| `0.10.48` | `2.22.0` | 2025-03-31 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `0.12.18` | `2.22.0` | 2025-03-31 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `4.9.1` | `2.22.0` | 2025-03-31 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `6.17.1` | `2.88.2` | 2025-03-31 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `8.17.0` | `2.88.2` | 2025-03-31 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `10.24.1` | `2.88.2` | 2025-03-31 | |
| `12.22.12` | `2.88.2` | 2025-03-31 | |
| `14.21.3` | `2.88.2` | 2025-03-31 | |
| `16.20.2` | `2.88.2` | 2025-03-31 | |
| `18.20.8` | `2.88.2` | 2025-03-31 | |
| `20.19.0` | `2.88.2` | 2025-03-31 | |
| `22.14.0` | `2.88.2` | 2025-03-31 | |
The `NODE_TLS_REJECT_UNAUTHORIZED` workaround sets the value to `'0'`:
```js title="Legacy NodeJS Certificate has Expired Bypass (prepend to script)"
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
```
This demo was last tested on 2024 January 15 against request `2.88.2`
:::
<details>
<summary><b>Complete Example</b> (click to show)</summary>
1) Install the [NodeJS module](/docs/getting-started/installation/nodejs)
<CodeBlock language="bash">{`\
@ -611,19 +534,6 @@ node SheetJSRequest.js
If successful, the script will print CSV contents of the test file.
:::caution pass
For older versions of NodeJS, the script may fail due to a certificate error.
The error can be suppressed by prepending the following line to the script:
```js title="SheetJSRequest.js (add to top)"
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
```
**It is strongly encouraged to upgrade to a newer NodeJS version!**
:::
</details>
#### axios
@ -642,32 +552,18 @@ async function workbook_dl_axios(url) {
}
```
:::note Tested Deployments
<details><summary><b>Complete Example</b> (click to show)</summary>
This demo was tested in the following environments:
:::note Tested Environments
| NodeJS | Axios | Date |
|:-----------|:-------|:-----------|
| `4.9.1` | 0.22.0 | 2025-03-31 |
| `6.17.1` | 0.22.0 | 2025-03-31 |
| `8.17.0` | 0.28.1 | 2025-03-31 |
| `10.24.1` | 0.28.1 | 2025-03-31 |
| `12.22.12` | 1.8.4 | 2025-03-31 |
| `14.21.3` | 1.8.4 | 2025-03-31 |
| `16.20.2` | 1.8.4 | 2025-03-31 |
| `18.20.8` | 1.8.4 | 2025-03-31 |
| `20.19.0` | 1.8.4 | 2025-03-31 |
| `22.14.0` | 1.8.4 | 2025-03-31 |
This demo was last tested on 2024 January 15 against Axios `1.6.5`
:::
<details>
<summary><b>Complete Example</b> (click to show)</summary>
1) Install the [NodeJS module](/docs/getting-started/installation/nodejs)
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz axios@1.8.4`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz axios@1.6.5`}
</CodeBlock>
2) Save the following to `SheetJSAxios.js`:
@ -683,7 +579,7 @@ async function workbook_dl_axios(url) {
}
(async() => {
const wb = await workbook_dl_axios('https://docs.sheetjs.com/pres.numbers');
const wb = await workbook_dl_axios('https://sheetjs.com/pres.numbers');
/* print the first worksheet to console */
var ws = wb.Sheets[wb.SheetNames[0]];
console.log(XLSX.utils.sheet_to_csv(ws));
@ -698,27 +594,6 @@ node SheetJSAxios.js
If successful, the script will print CSV contents of the test file.
:::caution pass
Legacy NodeJS versions do not support `async` functions. The `async` function
must be manually translated to a `then` chain:
```js title="SheetJSAxios.js (ES5)"
const XLSX = require("xlsx"), axios = require("axios");
var url = 'https://docs.sheetjs.com/pres.numbers';
axios(url, {responseType:'arraybuffer'}).then(function(res) {
/* at this point, res.data is a Buffer */
var wb = XLSX.read(res.data, {type: "buffer"});
/* print the first worksheet to console */
var ws = wb.Sheets[wb.SheetNames[0]];
console.log(XLSX.utils.sheet_to_csv(ws));
});
```
:::
</details>
## Other Platforms

@ -17,7 +17,7 @@ cloud storage solutions. Spreadsheets can be written using SheetJS and uploaded.
This demo explores file uploads using a number of browser APIs and wrapper
libraries. The upload process will generate a sample XLSX workbook, upload the
file to [a test server](https://s2c.sheetjs.com), and display the response.
file to [a test server](#test-server), and display the response.
:::info pass
@ -60,120 +60,14 @@ flowchart LR
form --> |POST\nrequest| server
```
### Generating Files
In a typical scenario, a process generates arrays of simple objects.
The SheetJS `json_to_sheet` method[^2] generates a SheetJS worksheet object[^3].
The `book_new` method[^4] creates a workbook object that includes the worksheet.
The `write` method[^5] generates the file in memory.
The following snippet creates a sample dataset and generates an `ArrayBuffer`
object representing the workbook bytes:
```js title="Generating an XLSX file in memory"
```js
/* create sample SheetJS workbook object */
var aoa = [
["S", "h", "e", "e", "t", "J", "S"],
[ 5, 4, 3, 3, 7, 9, 5]
];
var ws = XLSX.utils.aoa_to_sheet(aoa);
var wb = XLSX.utils.book_new(ws, "Sheet1");
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
/* export SheetJS workbook object to XLSX file bytes */
var data = XLSX.write(wb, {bookType: 'xlsx', type: 'array'});
```
### Creating Form Data
`File` objects represent files. The `File` constructor accepts an array of data
fragments and a filename.
Browser APIs typically represent form body data using `FormData` objects. The
`append` method adds fields to the `FormData` object. Adding `File` objects
effectively "attaches" a file in the upload.
The following snippet constructs a new `FormData` object. The `file` field in
the form will be set to the data from the previous snippet:
```js title="Creating Form Data and attaching the generated file"
/* create File */
var file = new File([data], 'sheetjs.xlsx')
// generated XLSX ^^^^ ^^^^^^^^^^^^ file name
/* build FormData with the generated file */
var fdata = new FormData();
fdata.append('file', file);
// ^^^^ field name in the form body
```
### POST Request
This demo explores a number of APIs and libraries for making POST requests. Each
approach will upload data stored in `FormData` objects.
This snippet uses `XMLHttpRequest` to upload data to https://s2c.sheetjs.com:
```js title="Uploading Form Data with XMLHttpRequest"
/* send data using XMLHttpRequest */
var req = new XMLHttpRequest();
req.open("POST", "https://s2c.sheetjs.com", true);
req.send(fdata);
```
## Browser Demos
When the upload button is clicked, the browser will build up a new workbook,
generate a XLSX file, upload it to https://s2c.sheetjs.com and show the
response. If the process was successful, a HTML table will be displayed
:::note Tested Deployments
Each browser demo was tested in the following environments:
| Browser | Date |
|:-------------|:-----------|
| Chromium 133 | 2025-03-30 |
| Safari 18.3 | 2025-03-30 |
| Konqueror 22 | 2025-04-23 |
:::
#### Test Server
The https://s2c.sheetjs.com service is currently hosted on Deno Deploy. The
["Deno Deploy" demo](/docs/demos/cloud/deno#demo) covers the exact steps for
deploying the service.
The CORS-enabled service handles POST requests by looking for uploaded files in
the `"file"` key. If a file is found, the file will be parsed using the SheetJS
`read` method[^6] and the first worksheet will be converted to HTML using the
`sheet_to_html` method[^7].
### XMLHttpRequest
Using the `XMLHttpRequest` API, the `send` method can accept `FormData` objects:
```js title="Uploading Form Data with XMLHttpRequest"
/* send data using XMLHttpRequest */
var req = new XMLHttpRequest();
req.open("POST", "https://s2c.sheetjs.com", true);
req.send(fdata);
```
<details>
<summary><b>Complete Code Snippet</b> (click to show)</summary>
```js title="SheetJS + XMLHttpRequest example"
/* create sample SheetJS workbook object */
var aoa = [
["S", "h", "e", "e", "t", "J", "S"],
[ 5, 4, 3, 3, 7, 9, 5]
];
const ws = XLSX.utils.aoa_to_sheet(aoa);
const wb = XLSX.utils.book_new();
var wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
/* export SheetJS workbook object to XLSX file bytes */
@ -190,13 +84,42 @@ req.open("POST", "https://s2c.sheetjs.com", true);
req.send(fdata);
```
</details>
## Test Server
<details>
<summary><b>Live demo</b> (click to show)</summary>
The <https://s2c.sheetjs.com> service is currently hosted on Deno Deploy. The
["Deno Deploy" demo](/docs/demos/cloud/deno#demo) covers the exact steps for
deploying the service.
The CORS-enabled service handles POST requests by looking for uploaded files in
the `"file"` key. If a file is found, the file will be parsed using the SheetJS
`read` method[^2] and the first worksheet will be converted to HTML using the
`sheet_to_html` method[^3].
## Browser Demos
When the upload button is clicked, the browser will build up a new workbook,
generate a XLSX file, upload it to <https://s2c.sheetjs.com> and show the
response. If the process was successful, a HTML table will be displayed
:::note Tested Deployments
Each browser demo was tested in the following environments:
| Browser | Date |
|:------------|:-----------|
| Chrome 120 | 2024-01-15 |
| Safari 17.3 | 2024-02-21 |
:::
### XMLHttpRequest
This demo uses [the code snippet from the intro](#uploading-binary-data).
<details><summary><b>Live demo</b> (click to show)</summary>
This demo starts from an array of arrays of data. When the button is clicked, a
workbook file will be generated and uploaded to https://s2c.sheetjs.com. The
workbook file will be generated and uploaded to <https://s2c.sheetjs.com>. The
service will return a HTML table.
```jsx live
@ -261,15 +184,7 @@ function SheetJSXHRUL() {
`fetch` takes a second parameter which allows for setting POST request body:
```js title="Uploading Form Data with fetch"
/* send data using fetch */
fetch("https://s2c.sheetjs.com", { method: "POST", body: fdata });
```
<details>
<summary><b>Complete Code Snippet</b> (click to show)</summary>
```js title="SheetJS + fetch example"
```js
/* create sample SheetJS workbook object */
var aoa = [
["S", "h", "e", "e", "t", "J", "S"],
@ -291,12 +206,9 @@ fdata.append('file', new File([data], 'sheetjs.xlsx'));
fetch("https://s2c.sheetjs.com", { method: "POST", body: fdata });
```
</details>
<details><summary><b>Live demo</b> (click to show)</summary>
<details>
<summary><b>Live demo</b> (click to show)</summary>
This demo uses `fetch` to upload data to https://s2c.sheetjs.com. It will parse
This demo uses `fetch` to upload data to <https://s2c.sheetjs.com>. It will parse
the workbook and return an HTML table.
```jsx live
@ -364,15 +276,7 @@ are still relevant.
Uploading form data is nearly identical to the `fetch` example:
```js title="Uploading Form Data with axios"
/* send data using axios */
axios("https://s2c.sheetjs.com", { method: "POST", body: fdata });
```
<details>
<summary><b>Complete Code Snippet</b> (click to show)</summary>
```js title="SheetJS + axios example"
```js
/* create sample SheetJS workbook object */
var aoa = [
["S", "h", "e", "e", "t", "J", "S"],
@ -394,12 +298,9 @@ fdata.append('file', new File([data], 'sheetjs.xlsx'));
axios("https://s2c.sheetjs.com", { method: "POST", data: fdata });
```
</details>
<details><summary><b>Live demo</b> (click to show)</summary>
<details>
<summary><b>Live demo</b> (click to show)</summary>
This demo uses `axios` to upload data to https://s2c.sheetjs.com. It will parse
This demo uses `axios` to upload data to <https://s2c.sheetjs.com>. It will parse
the workbook and return an HTML table.
:::caution pass
@ -474,15 +375,7 @@ with a "Fluent Interface".
The `send` method accepts a `FormData` object as the first argument:
```js title="Uploading Form Data with superagent"
/* send data using superagent */
superagent.post("https://s2c.sheetjs.com").send(fd);
```
<details>
<summary><b>Complete Code Snippet</b> (click to show)</summary>
```js title="SheetJS + superagent example"
```js
/* create sample SheetJS workbook object */
var aoa = [
["S", "h", "e", "e", "t", "J", "S"],
@ -504,12 +397,9 @@ fdata.append('file', new File([data], 'sheetjs.xlsx'));
superagent.post("https://s2c.sheetjs.com").send(fd);
```
</details>
<details><summary><b>Live demo</b> (click to show)</summary>
<details>
<summary><b>Live demo</b> (click to show)</summary>
This demo uses `superagent` to upload data to https://s2c.sheetjs.com. It will
This demo uses `superagent` to upload data to <https://s2c.sheetjs.com>. It will
parse the workbook and return an HTML table.
:::caution pass
@ -579,27 +469,21 @@ function SheetJSSuperAgentUL() {
## NodeJS Demos
These examples show how to upload data in NodeJS scripts.
These examples show how to upload data in NodeJS.
### fetch
NodeJS `fetch`, available in version 20, mirrors the [browser `fetch`](#fetch).
The `fetch` implementation mirrors the [browser `fetch`](#fetch).
:::note Tested Deployments
This demo was tested in the following environments:
| NodeJS | Date |
|:-----------|:-----------|
| `22.13.0` | 2025-01-08 |
| `20.18.1` | 2025-01-08 |
This demo was last tested on 2023 November 19 against NodeJS `20.9.0`
:::
<details>
<summary><b>Complete Example</b> (click to show)</summary>
<details><summary><b>Complete Example</b> (click to show)</summary>
This demo uses `fetch` to upload data to https://s2c.sheetjs.com. It will parse
This demo uses `fetch` to upload data to <https://s2c.sheetjs.com>. It will parse
the workbook and return data in CSV rows.
1) Install the [SheetJS NodeJS module](/docs/getting-started/installation/nodejs):
@ -649,164 +533,6 @@ It will print CSV contents of the test file.
</details>
### request
The deprecated [`request`](https://github.com/request/request) library is useful
in legacy NodeJS deployments where `fetch` may not be available.
The SheetJS `write` method will generate NodeJS Buffer objects when the `type`
option is set to `"buffer"`:
```js
/* export SheetJS workbook object to XLSX file bytes */
const data = XLSX.write(wb, {bookType: 'xlsx', type: 'buffer'});
```
A `request` file object can be built using the Buffer. The file object must
include an `options` object that specifies the file name and content type:
```js
/* create a file object for the `request` form data */
const request_file = {
/* `value` can be a Buffer object */
value: data,
options: {
/* `options.filename` is the filename that the server will see */
filename: "sheetjs.xlsx",
/* `options.contentType` must be set */
contentType: "application/octet-stream"
}
};
```
The `request` and `request.post` methods accept an options argument. The
`formData` property specifies the body to be uploaded. Property names correspond
to the uploaded form names and values describe the uploaded content.
The `request` file object should be added to the `formData` object:
```js
request({
// ... other options ...
formData: {
// ... other form fields ...
/* the server will see the uploaded file in the `file` body property */
/* highlight-next-line */
file: request_file
}
}, function(err, res) { /* handle response ... */ });
```
:::note Tested Deployments
This demo was tested in the following environments:
| NodeJS | `request` | Date |
|:-----------|:----------|:-----------|
| `22.13.0` | `2.88.2` | 2025-01-08 |
| `20.18.1` | `2.88.2` | 2025-01-08 |
| `18.20.5` | `2.88.2` | 2025-01-08 |
| `16.20.2` | `2.88.2` | 2025-01-08 |
| `14.21.3` | `2.88.2` | 2025-01-08 |
| `12.22.12` | `2.88.2` | 2025-01-08 |
| `10.24.1` | `2.88.2` | 2025-01-08 |
| `8.17.0` | `2.88.2` | 2025-01-08 |
| `6.17.1` | `2.88.2` | 2025-01-08 |
| `4.9.1` | `2.81.0` | 2025-01-08 |
| `0.12.18` | `2.81.0` | 2025-01-08 |
| `0.10.48` | `2.81.0` | 2025-01-08 |
:::
<details>
<summary><b>Complete Example</b> (click to show)</summary>
This demo uses `request` to upload data to https://s2c.sheetjs.com. It will
parse the workbook and return data in CSV rows.
1) Install the [SheetJS NodeJS module](/docs/getting-started/installation/nodejs)
and `request` module:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz request`}
</CodeBlock>
:::note pass
The current version of `request` requires NodeJS 6 or later. For older versions
of NodeJS, `request` version `2.81.0` should be installed.
:::
2) Save the following to `SheetJSRequest.js`:
```js title="SheetJSRequest.js"
const XLSX = require("xlsx");
const request = require("request");
/* create sample SheetJS workbook object */
var aoa = [
["S", "h", "e", "e", "t", "J", "S"],
[ 5, 4, 3, 3, 7, 9, 5]
];
const ws = XLSX.utils.aoa_to_sheet(aoa);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
/* export SheetJS workbook object to XLSX file bytes */
var data = XLSX.write(wb, {bookType: 'xlsx', type: 'buffer'});
request({
method: "POST",
url: "https://s2c.sheetjs.com",
headers: {
Accept: "text/html"
},
formData: {
type: "csv",
file: {
value: data,
options: {
filename: "sheetjs.xlsx",
contentType: "application/octet-stream"
}
}
}
}, function(err, res, body) {
if(err) return console.error(err);
console.log(body);
});
```
3) Run the script:
```bash
node SheetJSRequest.js
```
It will print CSV contents of the test file.
:::caution pass
For legacy versions of NodeJS, the process may fail with a certificate error:
```
{ [Error: certificate not trusted] code: 'CERT_UNTRUSTED' }
```
The environment variable `NODE_TLS_REJECT_UNAUTHORIZED` can be set to `0`:
```bash
env NODE_TLS_REJECT_UNAUTHORIZED="0" node SheetJSRequest.js
```
**It is strongly recommended to upgrade to a newer version of NodeJS!**
:::
</details>
## Troubleshooting
@ -845,9 +571,5 @@ const res = await fetch(url, {method:"POST", body: fdata});
If the generated file is valid, then the issue is in the server infrastructure.
[^1]: See [`write` in "Writing Files"](/docs/api/write-options)
[^2]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
[^3]: See ["Worksheet Object" in "SheetJS Data Model"](/docs/csf/sheet) for more details.
[^4]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)
[^5]: See [`write` in "Writing Files"](/docs/api/write-options)
[^6]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^7]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output)
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^3]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output)

@ -22,12 +22,8 @@ The ["Complete Example"](#complete-example) section includes a complete server.
:::note Tested Deployments
This demo was tested in the following deployments:
| Version | Date |
|:---------|:-----------|
| `4.21.2` | 2025-01-03 |
| `5.0.1` | 2025-01-03 |
This demo was tested on 2024 March 11 using `express-formidable@1.2.0` and
ExpressJS `4.18.3`
:::
@ -140,28 +136,26 @@ app.get('/download', function(req, res) {
res.status(200).end(buf);
// highlight-end
});
app.listen(+process.env.PORT||3000, function() { console.log("Ready to go"); });
app.listen(+process.env.PORT||3000);
```
2) Install dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz express@4.21.2 express-formidable@1.2.0`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz express@4.18.3 express-formidable@1.2.0`}
</CodeBlock>
3) Start server
3) Start server (note: it will not print anything to console when running)
```bash
node SheetJSExpressCSV.js
```
Once the server starts, the process will print `Ready to go`.
4) Test POST requests using https://docs.sheetjs.com/pres.numbers . The commands
should be run in a new terminal window:
4) Test POST requests using <https://sheetjs.com/pres.numbers> . The following
commands should be run in a new terminal window:
```bash
curl -LO https://docs.sheetjs.com/pres.numbers
curl -LO https://sheetjs.com/pres.numbers
curl -X POST -F upload=@pres.numbers http://localhost:3000/upload
```

@ -22,12 +22,7 @@ The ["Complete Example"](#complete-example) section includes a complete server.
:::note Tested Deployments
This demo was tested in the following deployments:
| Drash | Deno | Date |
|:--------|:-------|:-----------|
| `2.8.1` | 1.46.0 | 2025-05-21 |
| `2.8.1` | 2.3.3 | 2025-05-21 |
This demo was last tested on 2024 March 11 against Drash 2.8.1 and Deno 1.41.2.
:::
@ -128,24 +123,13 @@ curl -LO https://docs.sheetjs.com/drash/SheetJSDrash.ts
deno run --allow-net SheetJSDrash.ts
```
:::caution pass
Deno 2 requires the `--allow-import` entitlement:
```bash
deno run --allow-net --allow-write --allow-import SheetJSDrash.ts
```
:::
3) Download the test file https://docs.sheetjs.com/pres.numbers
3) Download the test file <https://sheetjs.com/pres.numbers>
4) Open `http://localhost:7262/` in your browser.
Click "Choose File" and select `pres.numbers` from the Downloads folder.
Click "Choose File" and select `pres.numbers`. Then click "Submit"
Click "Submit" to make a request to the Drash server. The response should show
the contents of the file as an HTML table.
The page should show the contents of the file as an HTML table.
5) Open `http://localhost:7262/export` in your browser.

@ -1,250 +0,0 @@
---
title: Sheets on Fire with HonoJS
sidebar_label: HonoJS
pagination_prev: demos/net/network/index
pagination_next: demos/net/email/index
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
[HonoJS](https://hono.dev/) is a lightweight server-side framework.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses HonoJS and SheetJS to read and write data. We'll explore how to
parse uploaded files in a POST request handler and respond to GET requests with
downloadable spreadsheets.
The ["Complete Example"](#complete-example) section includes a complete server.
:::note Tested Deployments
This demo was tested in the following deployments:
| Platform | HonoJS | Date |
|:---------------|:----------|:-----------|
| BunJS `1.2.13` | `2.7.8` | 2025-05-21 |
| BunJS `1.2.13` | `3.12.12` | 2025-05-21 |
| BunJS `1.2.13` | `4.7.10` | 2025-05-21 |
:::
## Integration Details
The [SheetJS BunJS module](/docs/getting-started/installation/bun) can be
imported from HonoJS server scripts.
### Reading Data
The HonoJS body parser[^1] processes files in POST requests. The body parser
returns an object that can be indexed by field name:
```js
/* /import route */
app.post('/import', async(c) => {
/* parse body */
const body = await c.req.parseBody();
/* get a file uploaded in the `upload` field */
// highlight-next-line
const file = body["upload"];
/* `file` is a `File` object */
// ...
});
```
:::caution pass
By default, the HonoJS body parser will use the last value when the form body
specifies multiple values for a given field. To force the body parser to process
all files, the field name must end with `[]`:
```js
/* parse body */
const body = await c.req.parseBody();
/* get all files uploaded in the `upload` field */
// highlight-next-line
const files = body["upload[]"];
```
:::
HonoJS exposes each file as a `Blob` object. The `Blob#arrayBuffer` method
returns a Promise that resolves to an `ArrayBuffer`. That `ArrayBuffer` can be
parsed with the SheetJS `read` method[^2].
This example server responds to POST requests. The server will look for a file
in the request body under the `"upload"` key. If a file is present, the server
will parse the file and, generate CSV rows using the `sheet_to_csv` method[^3],
and respond with text:
```js
import { Hono } from 'hono';
import { read, utils } from 'xlsx';
const app = new Hono();
app.post('/import', async(c) => {
/* get file data */
const body = await c.req.parseBody();
const file = body["upload"];
const ab = await file.arrayBuffer();
/* parse */
const wb = read(ab);
/* generate CSV */
const csv = utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]);
return c.text(csv);
});
export default app;
```
### Writing Data
Given a SheetJS workbook object, the `write` method using `type: "buffer"`[^4]
generates data objects which can be passed to the response `body` method.
This example server responds to GET requests. The server will generate a SheetJS
worksheet object from an array of arrays[^5], build up a new workbook using the
`book_new`[^6] utility method, generate a XLSX file using `write`, and send the
file with appropriate headers to download `SheetJSHonoJS.xlsx`:
```js
import { Hono } from 'hono';
import { utils, write } from "xlsx";
const app = new Hono();
app.get("/export", (c) => {
/* generate SheetJS workbook object */
var ws = utils.aoa_to_sheet(["SheetJS".split(""), [5,4,3,3,7,9,5]]);
var wb = utils.book_new(ws, "Data");
/* generate buffer */
var buf = write(wb, {type: "buffer", bookType: "xlsx"});
/* set headers */
c.header('Content-Disposition', 'attachment; filename="SheetJSHonoJS.xlsx"');
c.header('Content-Type', 'application/vnd.ms-excel');
/* export buffer */
return c.body(buf);
});
export default app;
```
## Complete Example
This example creates a simple server that stores an array of arrays. There are
three server endpoints:
- `/import` POST request expects a file in the `upload` field. It will parse the
file, update the internal array of arrays, and responds with CSV data.
- `/export` GET request generates a workbook from the internal array of arrays.
It will respond with XLSX data and initiate a download to `SheetJSHonoJS.xlsx` .
- `/json` GET request responds with the internal state.
1) Create a new BunJS + HonoJS project:
```bash
bun create hono sheetjs-hono --template bun --install --pm bun
cd sheetjs-hono
```
2) Install the [SheetJS BunJS module](/docs/getting-started/installation/bun):
<CodeBlock language="bash">{`\
bun i xlsx@https://sheet.lol/balls/xlsx-${current}.tgz`}
</CodeBlock>
3) Save the following script to `src/index.ts`:
```ts title="src/index.ts"
import { Hono } from 'hono';
import { read, write, utils } from 'xlsx';
const app = new Hono();
let data = ["SheetJS".split(""), [5,4,3,3,7,9,5]];
app.get('/export', (c) => {
const ws = utils.aoa_to_sheet(data);
const wb = utils.book_new(ws, "SheetJSHono");
const buf = write(wb, { type: "buffer", bookType: "xlsx" });
c.header('Content-Disposition', 'attachment; filename="SheetJSHonoJS.xlsx"');
c.header('Content-Type', 'application/vnd.ms-excel');
return c.body(buf);
});
app.post('/import', async(c) => {
const body = await c.req.parseBody();
const file = body["upload"];
const ab = await file.arrayBuffer();
const wb = read(ab);
data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header:1 });
return c.text(utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
});
app.get('/json', (c) => c.json(data));
export default app;
```
4) Run the server:
```bash
bun run dev
```
The process will display a URL (typically `http://localhost:3000`):
```text
% bun run dev
$ bun run --hot src/index.ts
// highlight-next-line
Started server http://localhost:3000
```
5) Test exports by opening `http://localhost:3000/export` in your browser.
The page should attempt to download `SheetJSHonoJS.xlsx` . Save the download and
open the new file. The contents should match the original data:
<table>
<tr><td>S</td><td>h</td><td>e</td><td>e</td><td>t</td><td>J</td><td>S</td></tr>
<tr><td>5</td><td>4</td><td>3</td><td>3</td><td>7</td><td>9</td><td>5</td></tr>
</table>
6) Test imports using https://docs.sheetjs.com/pres.numbers . The commands
should be run in a new terminal window:
```bash
curl -LO https://docs.sheetjs.com/pres.numbers
curl -X POST -F upload=@pres.numbers http://localhost:3000/import
```
The terminal will display CSV rows generated from the first worksheet:
```text title="Expected output"
Name,Index
Bill Clinton,42
GeorgeW Bush,43
Barack Obama,44
Donald Trump,45
Joseph Biden,46
```
7) Confirm the state was updated by loading `http://localhost:3000/json` :
```bash
curl -LO http://localhost:3000/json
```
The terminal will display the worksheet data in an array of arrays:
```json title="Expected output"
[["Name","Index"],["Bill Clinton",42],["GeorgeW Bush",43],["Barack Obama",44],["Donald Trump",45],["Joseph Biden",46]]
```
[^1]: See ["`parseBody()`"](https://hono.dev/docs/api/request#parsebody) in the HonoJS documentation.
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^3]: See [`sheet_to_csv` in "Utilities"](/docs/api/utilities/csv#delimiter-separated-output)
[^4]: See [`write` in "Writing Files"](/docs/api/write-options)
[^5]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input)
[^6]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)

@ -8,8 +8,7 @@ pagination_next: demos/net/email/index
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
[ElysiaJS](https://elysiajs.com/) is a server-side framework for
[BunJS](/docs/getting-started/installation/bun).
[Elysia](https://elysiajs.com/) is a BunJS server-side framework.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
@ -22,12 +21,7 @@ The ["Complete Example"](#complete-example) section includes a complete server.
:::note Tested Deployments
This demo was tested in the following deployments:
| Platform | ElysiaJS | Date |
|:---------------|:---------|:-----------|
| BunJS `1.2.13` | `0.8.17` | 2025-05-21 |
| BunJS `1.2.13` | `1.3.1` | 2025-05-21 |
This demo was last tested on 2024 March 11 with ElysiaJS 0.8.17 and BunJS 1.0.30.
:::
@ -118,16 +112,6 @@ bun create elysia sheetjs-elysia
cd sheetjs-elysia
```
:::note pass
The `bun install` command can install specific versions of ElysiaJS:
```bash
bun install elysia@0.8.17
```
:::
2) Install the [SheetJS BunJS module](/docs/getting-started/installation/bun):
<CodeBlock language="bash">{`\
@ -158,7 +142,7 @@ app.post("/", async({ body: { upload } }) => {
upload: t.File()
})
});
app.listen(3000, () => { console.log("SheetJS ElysiaJS server is up")});
app.listen(3000);
```
4) Run the server:
@ -167,13 +151,11 @@ app.listen(3000, () => { console.log("SheetJS ElysiaJS server is up")});
bun run src/SheetJSElysia.ts
```
The script will print `SheetJS ElysiaJS server is up`.
5) Test POST requests using https://docs.sheetjs.com/pres.numbers . The commands
should be run in a new terminal window:
5) Test POST requests using <https://sheetjs.com/pres.numbers> . The following
commands should be run in a new terminal window:
```bash
curl -LO https://docs.sheetjs.com/pres.numbers
curl -LO https://sheetjs.com/pres.numbers
curl -X POST -F upload=@pres.numbers http://localhost:3000/
```

@ -22,14 +22,7 @@ The ["Complete Example"](#complete-example) section includes a complete server.
:::note Tested Deployments
This demo was tested in the following deployments:
| NestJS | Date |
|:----------|:-----------|
| `11.1.1` | 2025-05-21 |
| `10.4.17` | 2025-05-21 |
| `9.4.3` | 2025-05-21 |
| `8.4.7` | 2025-05-21 |
This demo was tested on 2024 March 11 using NestJS `10.3.3`.
:::
@ -131,23 +124,10 @@ npx @nestjs/cli@latest new -p npm sheetjs-nest
cd sheetjs-nest
```
:::info pass
The NestJS CLI generates a project using the latest version of NestJS. To force
an older version, install older versions of dependencies in the `@nestjs` scope.
For example, the following command rolls back to NestJS major version 8:
2) Install the `@types/multer` package as a development dependency:
```bash
npm install --save @nestjs/cli@8.x @nestjs/common@8.x @nestjs/core@8.x @nestjs/platform-express@8.x @nestjs/schematics@8.x @nestjs/testing@8.x --force
```
:::
2) Install the `@types/multer` package as a dependency:
```bash
npm i --save @types/multer
npm i --save-dev @types/multer
```
3) Install the SheetJS library:
@ -207,7 +187,7 @@ npx @nestjs/cli start
:::note pass
In some tests, the process failed with a message referencing `Multer`:
In the most recent test, the process failed with a message referencing Multer:
```
src/sheetjs/sheetjs.controller.ts:9:54 - error TS2694: Namespace 'global.Express' has no exported member 'Multer'.
@ -223,17 +203,17 @@ This error indicates that `@types/multer` is not available.
The recommended fix is to install `@types/multer` again:
```bash
npm i --save @types/multer
npm i --save-dev @types/multer
npx @nestjs/cli start
```
:::
8) Test POST requests using https://docs.sheetjs.com/pres.numbers . The commands
should be run in a new terminal window:
8) Test POST requests using <https://sheetjs.com/pres.numbers> . The following
commands should be run in a new terminal window:
```bash
curl -LO https://docs.sheetjs.com/pres.numbers
curl -LO https://sheetjs.com/pres.numbers
curl -X POST -F upload=@pres.numbers http://localhost:3000/sheetjs/upload
```

@ -21,12 +21,7 @@ The ["Complete Example"](#complete-example) section includes a complete server.
:::note Tested Deployments
This demo was tested in the following deployments:
| Version | Date |
|:---------|:-----------|
| `4.29.0` | 2025-01-02 |
| `5.2.0` | 2025-01-02 |
This demo was verified on 2024 March 11 using `fastify@4.26.2`
:::
@ -106,8 +101,8 @@ fastify.post('/', async(req, reply) => {
:::caution pass
Out of the box, FastifyJS will return an error `FST_ERR_CTP_BODY_TOO_LARGE` when
processing large spreadsheets (`statusCode 413`). This is a FastifyJS issue.
Out of the box, Fastify will return an error `FST_ERR_CTP_BODY_TOO_LARGE` when
processing large spreadsheets (`statusCode 413`). This is a Fastify issue.
The default body size limit (including all uploaded files and fields) is 1 MB.
It can be increased by setting the `bodyLimit` option during server creation:
@ -162,7 +157,7 @@ fastify.listen({port: process.env.PORT || 3000}, (err, addr) => { if(err) throw
1) Install dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz fastify@4.29.0 @fastify/multipart@8`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz fastify@4.26.2 @fastify/multipart@8.1.0`}
</CodeBlock>
2) Start server
@ -171,11 +166,11 @@ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz fastify
node SheetJSFastify.js
```
3) Test POST requests using https://docs.sheetjs.com/pres.numbers . The commands
should be run in a new terminal window:
3) Test POST requests using <https://sheetjs.com/pres.numbers> . The following
commands should be run in a new terminal window:
```bash
curl -LO https://docs.sheetjs.com/pres.numbers
curl -LO https://sheetjs.com/pres.numbers
curl -X POST -F upload=@pres.numbers http://localhost:3000/
```

@ -120,25 +120,20 @@ That approach is not explored in this demo.
:::
<details>
<summary><b>Complete Example</b> (click to show)</summary>
<details><summary><b>Complete Example</b> (click to show)</summary>
:::note Tested Deployments
This demo was tested in the following environments:
| NodeJS | Date | ExpressJS |
|:----------|:-----------|:----------|
| `18.20.8` | 2025-04-17 | `5.1.0` |
| `20.18.0` | 2025-04-17 | `5.1.0` |
| `22.14.0` | 2025-04-17 | `5.1.0` |
| `18.20.8` | 2025-04-17 | `4.21.2` |
| `20.18.0` | 2025-04-17 | `4.21.2` |
| `22.14.0` | 2025-04-17 | `4.21.2` |
| NodeJS | Date | Dependencies |
|:----------|:-----------|:------------------------------------|
| `18.19.1` | 2024-02-23 | ExpressJS 4.18.2 + Formidable 2.1.2 |
| `20.11.1` | 2024-02-23 | ExpressJS 4.18.2 + Formidable 2.1.2 |
:::
0) Create a new project with a `package.json` that enables ESM:
0) Create a new project with a ESM-enabled `package.json`:
```bash
mkdir sheetjs-worker
@ -149,7 +144,7 @@ echo '{ "type": "module" }' > package.json
1) Install the dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz express@4.19.2 formidable@2.1.2`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz express@4.18.2 formidable@2.1.2`}
</CodeBlock>
2) Create a worker script `worker.js` that listens for messages. When a message
@ -234,11 +229,11 @@ node main.mjs
Keep the server process running during the test.
6) Test with the [`pres.numbers` sample file](https://docs.sheetjs.com/pres.numbers).
6) Test with the [`pres.numbers` sample file](https://sheetjs.com/pres.numbers).
The following commands should be run in a new terminal window:
```bash
curl -LO https://docs.sheetjs.com/pres.numbers
curl -LO https://sheetjs.com/pres.numbers
curl -X POST -F upload=@pres.numbers http://localhost:7262/ -J -O
```
@ -260,8 +255,7 @@ Bun provides the basic elements to implement a web server.
:::caution pass
Many hosted services, including [Deno Deploy](/docs/demos/cloud/deno#demo), do
not offer filesystem access from scripts.
Many hosted services like Deno Deploy do not offer filesystem access.
This breaks web frameworks that use the filesystem in body parsing.
@ -273,6 +267,6 @@ a body parser out of the box.
#### Drash
In testing, [Drash](https://drash.land/drash/) had an in-memory body parser
which could handle file uploads on [Deno Deploy](/docs/demos/cloud/deno#demo).
which could handle file uploads on hosted services like Deno Deploy.
**[The exposition has been moved to a separate page.](/docs/demos/net/server/drash)**

@ -34,15 +34,7 @@ the file can be downloaded or previewed in the browser.
:::note Tested Deployments
This demo was tested in the following deployments:
| Platform | Version | Date |
|:--------------|:---------|:-----------|
| Chromium 136 | `1.9.0` | 2025-05-07 |
| Safari 17.5 | `1.9.0` | 2025-05-07 |
| Konqueror 22 | `1.9.0` | 2025-05-07 |
| NodeJS 24.0.0 | `1.11.0` | 2025-05-07 |
| BunJS 1.2.10 | `1.11.0` | 2025-05-07 |
This demo was last tested on 2024 March 11 against `pst-extractor` 1.9.0
:::
@ -170,8 +162,7 @@ added and exposed in a script.
[`pstextractor.js`](pathname:///pst/pstextractor.js) is loaded in the demo page.
<details>
<summary><b>Build instructions</b> (click to show)</summary>
<details><summary><b>Build instructions</b> (click to show)</summary>
1) Initialize a new NodeJS project and install the dependency:
@ -266,23 +257,23 @@ saving file 0 |RedRockA.xls| to file0.xls
```
Lines starting with `####` show the attachment file name and the worksheet name.
The following line explains that there is a worksheet named `"Oneok at 2500"` in
The following line explains that there is a worksheet named `"Oct 26, 2001"` in
the file `RedRockA.xls`:
```
#### RedRockA.xls ! Oneok at 2500
#### RedRockA.xls ! Oct 26, 2001
```
Every other line is a CSV row from the named worksheet. For example, the first
four lines of worksheet `"Oneok at 2500"` in `RedRockA.xls` are shown below:
four lines of worksheet `"Oct 26, 2001"` in `RedRockA.xls` are shown below:
```text
#### RedRockA.xls ! Oneok at 2500
#### RedRockA.xls ! Oct 26, 2001
// highlight-start
RED ROCK EXPANSION PROJECT,,,,,,,,
,,,,,,,,
,,REQUESTED,REQUESTED,,,,,
,,RECEIPT,DELIVERY,,,Allocation,,
RED ROCK EXPANSION PROJECT,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,
,,,, , , ,,,,,,,,,,,,
SHIPPER,CONTRACT #,Term,MMBtu/d,RECEIPT POINT,DELIVERY POINT,MMBtu/d,,,,,,,,,,,,
// highlight-end
```
@ -294,7 +285,7 @@ This demo reads PST mailboxes. Due to browser limitations, PST files larger than
100 MB may crash the browser.
After parsing the PST file, the "Attachments" table will list attached XLSX and
XLS spreadsheets in the file. The "preview" link will display an HTML table with
XLS spreadsheets in the file. The "preview" link will display a HTML table with
the data in the spreadsheet. The "download" link will download the attachment.
The [test file](pathname:///pst/enron.pst) was based on the EDRM clean extract

@ -19,7 +19,7 @@ This demo covers three workflows:
- [Reading mail](#reading-mail) covers libraries for reading messages
- [Data files](#data-files) covers mailbox file formats
:::danger pass
:::warning pass
There are a number of caveats when dealing with live mail servers. It is advised
to follow connector module documentation carefully and test with new accounts
@ -29,28 +29,56 @@ before integrating with important inboxes or accounts.
## Live Servers
:::danger pass
:::warning pass
It is strongly advised to use a test email address before using an important
address. One small mistake could erase decades of messages or result in a block
address. One small mistake could erase decades of messages or result in a block
or ban from Google services.
:::
### App Passwords
Many email providers (including GMail and Yahoo Mail) require "app passwords" or
passwords for "less secure apps". Attempting to connect and send using the
account password will throw errors.
Many email providers (including Fastmail, GMail, and Yahoo Mail) require "app
passwords" or passwords for "less secure apps". Attempting to connect and send
using the account password will throw errors.
### Test Account
It is strongly recommended to first test with a separate account.
It is strongly recommended to first test with an independent service provider.
#### Fastmail
This demo will start with a free 30-day trial of Fastmail. At the time the demo
was last tested, no payment details were required.
:::caution pass
A valid phone number (for SMS verification) was required.
:::
0) Create a new Fastmail email account and verify with a mobile number.
_Create App Password_
1) Open the settings screen (click on the icon in the top-left corner of the
screen and select "Settings").
2) Select "Privacy & Security" in the left pane, then click "Integrations" near
the top of the main page. Click "New app password".
3) Select any name in the top drop-down (the default "iPhone" can be used). In
the second drop-down, select "Mail (IMAP/POP/SMTP)". Click "Generate password".
A new password will be displayed. This is the app password that will be used in
the demo script. **Copy the displayed password or write it down.**
#### Gmail
This demo will start with a free Gmail account. When the demo was last tested,
no payment details were required.
This demo will start with a free Gmail account. At the time the demo was last
tested, no payment details were required.
:::caution pass
@ -70,8 +98,7 @@ _Create App Password_
4) Click "2-Step Verification"
5) Click the right arrow (`>`) next to "App passwords". If that option is not
available, open https://myaccount.google.com/apppasswords
5) Click the right arrow (`>`) next to "App passwords".
6) Type a name ("SheetJS Test") and click "Create".
@ -125,7 +152,8 @@ This demo was tested in the following deployments:
| Email Provider | Date | Library | Version |
|:---------------|:-----------|:-------------|:---------|
| `gmail.com` | 2025-01-05 | `nodemailer` | `6.9.16` |
| `gmail.com` | 2024-03-11 | `nodemailer` | `6.9.12` |
| `fastmail.com` | 2024-03-11 | `nodemailer` | `6.9.12` |
:::
@ -147,7 +175,7 @@ const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
// highlight-next-line
service: 'gmail',
service: 'fastmail',
auth: {
// highlight-start
user: '**',
@ -182,7 +210,7 @@ transporter.sendMail(mailOptions, function (err, info) {
3) Edit `SheetJSend.js` and replace the highlighted lines:
- `service: 'gmail',` the value should be one of the supported providers[^1]
- `service: 'fastmail',` the value should be one of the supported providers[^1]
- `user: "**",` the value should be the sender email address
- `pass: "**"` the value should be the app password from earlier
- `from: "**",` the value should be the sender email address
@ -272,7 +300,8 @@ This demo was tested in the following deployments:
| Email Provider | Date | Library | Version |
|:---------------|:-----------|:-----------|:----------|
| `gmail.com` | 2025-01-05 | `imapflow` | `1.0.172` |
| `gmail.com` | 2024-03-11 | `imapflow` | `1.0.156` |
| `fastmail.com` | 2024-03-11 | `imapflow` | `1.0.156` |
:::
@ -283,7 +312,7 @@ This demo was tested in the following deployments:
<CodeBlock language="bash">{`\
mkdir sheetjs-recv
cd sheetjs-recv
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz imapflow@1.0.172`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz imapflow@1.0.156`}
</CodeBlock>
2) Save the following script to `SheetJSIMAP.js`:
@ -294,7 +323,7 @@ const { ImapFlow } = require('imapflow');
const client = new ImapFlow({
// highlight-next-line
host: 'imap.gmail.com',
host: 'imap.fastmail.com',
port: 993, secure: true, logger: false,
auth: {
// highlight-start
@ -342,19 +371,18 @@ const concat_RS = (stream) => new Promise((res, rej) => {
- `user: "**",` the value should be the account address
- `pass: "**"` the value should be the app password from earlier
- `host: 'imap.gmail.com',` the value should be the host name:
- `host: 'imap.fastmail.com',` the value should be the host name:
| Service | `host` value |
|:---------------|:--------------------|
| `gmail.com` | `imap.gmail.com` |
| `fastmail.com` | `imap.fastmail.com` |
4) Download https://docs.sheetjs.com/pres.numbers .
4) Download <https://sheetjs.com/pres.numbers>. Using a different account, send
an email to the test account and attach the file. At the end of this step, the
test account should have an email in the inbox that has an attachment.
5) Send an email to the test account and attach `pres.numbers` from step 4.
6) Wait until the test account receives the email.
7) Run the script:
5) Run the script:
```bash
node SheetJSIMAP.js

@ -25,17 +25,6 @@ not generally support passing objects between the browser context and the
automation script, so the file data must be generated in the browser context
and sent back to the automation script for saving in the file system.
This demo exports data from https://sheetjs.com/demos/table.
:::note pass
It is also possible to parse files from the browser context, but parsing from
the automation context is more efficient and strongly recommended.
:::
#### Key Steps
```mermaid
sequenceDiagram
autonumber off
@ -61,23 +50,38 @@ sequenceDiagram
end
```
<details open><summary><b>Key Steps</b> (click to hide)</summary>
1) Launch the headless browser and load the target site.
2) Add the standalone SheetJS build to the page in a `SCRIPT` tag.
3) Add a script to the page (in the browser context) that will:
- Make a SheetJS workbook object[^1] from the first table using the SheetJS
`table_to_book`[^2] method.
- Generate the bytes for an XLSB file using the SheetJS `write`[^3] method.
- Make a workbook object from the first table using `XLSX.utils.table_to_book`
- Generate the bytes for an XLSB file using `XLSX.write`
- Send the bytes back to the automation script
4) When the automation context receives data, save to a file
</details>
This demo exports data from <https://sheetjs.com/demos/table>.
:::note pass
It is also possible to parse files from the browser context, but parsing from
the automation context is more efficient and strongly recommended.
:::
## Puppeteer
[Puppeteer](https://pptr.dev/) enables headless Chromium automation for NodeJS
and BunJS. Releases ship with a script that installs a headless browser.
[Puppeteer](https://pptr.dev/) enables headless Chromium automation for NodeJS.
Releases ship with an installer script that installs a headless browser.
<Tabs>
<TabItem value="nodejs" label="NodeJS">
Binary strings are the favored data type. They can be safely passed from the
browser context to the automation script. NodeJS provides an API to write
@ -126,62 +130,116 @@ const puppeteer = require('puppeteer');
:::note Tested Deployments
This demo was tested in the following deployments:
| Puppeteer | Date |
|:----------|:-----------|
| `24.9.0` | 2025-05-21 |
| `23.11.1` | 2025-05-21 |
| `22.15.0` | 2025-05-21 |
| `21.11.0` | 2025-05-21 |
| `20.9.0` | 2025-05-21 |
| `15.5.0` | 2025-05-21 |
| `10.4.0` | 2025-05-21 |
This demo was last tested on 2024 January 27 against Puppeteer 21.9.0.
:::
1) Install SheetJS and Puppeteer:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz puppeteer@24.9.0`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz puppeteer@21.9.0`}
</CodeBlock>
</TabItem>
<TabItem value="bunjs" label="BunJS">
<CodeBlock language="bash">{`\
bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz puppeteer@24.9.0`}
</CodeBlock>
</TabItem>
</Tabs>
2) Save the `SheetJSPuppeteer.js` code snippet to `SheetJSPuppeteer.js`.
3) Run the script:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
```bash
node SheetJSPuppeteer.js
```
When the script finishes, the file `SheetJSPuppeteer.xlsb` will be created.
This file can be opened with Excel.
</TabItem>
<TabItem value="bunjs" label="BunJS">
<TabItem value="deno" label="Deno">
:::caution pass
Deno Puppeteer is a fork. It is not officially supported by the Puppeteer team.
:::
Base64 strings are the favored data type. They can be safely passed from the
browser context to the automation script. Deno can decode the Base64 strings
and write the decoded `Uint8Array` data to file with `Deno.writeFileSync`
The key steps are commented below:
<CodeBlock language="ts" title="SheetJSPuppeteer.ts">{`\
import puppeteer from "https://deno.land/x/puppeteer@16.2.0/mod.ts";
import { decode } from "https://deno.land/std/encoding/base64.ts"
\n\
/* (1) Load the target page */
const browser = await puppeteer.launch();
const page = await browser.newPage();
page.on("console", msg => console.log("PAGE LOG:", msg.text()));
await page.setViewport({width: 1920, height: 1080});
await page.goto('https://sheetjs.com/demos/table');
\n\
/* (2) Load the standalone SheetJS build from the CDN */
await page.addScriptTag({ url: 'https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js' });
\n\
/* (3) Run the snippet in browser and return data */
const b64 = await page.evaluate(() => {
/* NOTE: this function will be evaluated in the browser context.
\`page\`, \`fs\` and \`puppeteer\` are not available.
\`XLSX\` will be available thanks to step 2 */
\n\
/* find first table */
var table = document.body.getElementsByTagName('table')[0];
\n\
/* call table_to_book on first table */
var wb = XLSX.utils.table_to_book(table);
\n\
/* generate XLSB and return binary string */
return XLSX.write(wb, {type: "base64", bookType: "xlsb"});
});
/* (4) write data to file */
Deno.writeFileSync("SheetJSPuppeteer.xlsb", decode(b64));
\n\
await browser.close();`}
</CodeBlock>
**Demo**
:::note Tested Deployments
This demo was last tested on 2024 January 27 against deno-puppeteer 16.2.0.
:::
1) Install deno-puppeteer:
```bash
bun SheetJSPuppeteer.js
env PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts
```
:::note pass
In PowerShell, the environment variable should be set separately:
```powershell
[Environment]::SetEnvironmentVariable('PUPPETEER_PRODUCT', 'chrome')
deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts
```
:::
2) Save the `SheetJSPuppeteer.ts` code snippet to `SheetJSPuppeteer.ts`.
3) Run the script:
```bash
deno run -A --unstable SheetJSPuppeteer.ts
```
When the script finishes, the file `SheetJSPuppeteer.xlsb` will be created.
This file can be opened with Excel.
</TabItem>
</Tabs>
When the script finishes, the file `SheetJSPuppeteer.xlsb` will be created.
This file can be opened with a spreadsheet editor that supports XLSB workbooks.
## Playwright
@ -235,56 +293,26 @@ const { webkit } = require('playwright'); // import desired browser
:::note Tested Deployments
This demo was tested in the following deployments:
| Playwright | Browser | Date |
|:-----------|:------------|:-----------|
| `1.52.0` | Webkit 18.4 | 2025-05-21 |
This demo was last tested on 2024 January 27 against Playwright 1.41.1.
:::
1) Install SheetJS and Playwright:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz playwright@1.49.1`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz playwright@1.41.1`}
</CodeBlock>
</TabItem>
<TabItem value="bunjs" label="BunJS">
<CodeBlock language="bash">{`\
bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz playwright@1.49.1`}
</CodeBlock>
</TabItem>
</Tabs>
2) Save the `SheetJSPlaywright.js` code snippet to `SheetJSPlaywright.js`.
3) Run the script
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
```bash
node SheetJSPlaywright.js
```
</TabItem>
<TabItem value="bunjs" label="BunJS">
```bash
bun SheetJSPlaywright.js
```
</TabItem>
</Tabs>
When the script finishes, the file `SheetJSPlaywright.xlsb` will be created.
This file can be opened with a spreadsheet editor that supports XLSB workbooks.
This file can be opened with Excel.
:::caution pass
@ -315,7 +343,7 @@ After installing engines, re-run the script.
PhantomJS is a headless web browser powered by WebKit.
:::danger pass
:::warning pass
This information is provided for legacy deployments. PhantomJS development has
been suspended and there are known vulnerabilities, so new projects should use
@ -327,20 +355,7 @@ Binary strings are the favored data type. They can be safely passed from the
browser context to the automation script. PhantomJS provides an API to write
binary strings to file (`fs.write` using mode `wb`).
:::note Tested Deployments
This demo was tested in the following environments:
| Architecture | PhantomJS | Date |
|:-------------|:----------|:-----------|
| `darwin-x64` | `2.1.1` | 2025-03-31 |
| `win11-x64` | `2.1.1` | 2025-06-18 |
| `linux-x64` | `2.1.1` | 2025-06-16 |
:::
<details>
<summary><b>Integration Details and Demo</b> (click to show)</summary>
<details><summary><b>Integration Details and Demo</b> (click to show)</summary>
The steps are marked in the comments:
@ -384,6 +399,17 @@ strongly recommended to add verbose logging and to lint scripts before use.
**Demo**
:::note Tested Deployments
This demo was tested in the following environments:
| Architecture | PhantomJS | Date |
|:-------------|:----------|:-----------|
| `darwin-x64` | `2.1.1` | 2024-03-15 |
| `win10-x64` | `2.1.1` | 2024-03-24 |
| `linux-x64` | `2.1.1` | 2024-03-29 |
:::
1) [Download and extract PhantomJS](https://phantomjs.org/download.html)
2) Save the `SheetJSPhantom.js` code snippet to `SheetJSPhantom.js`.
@ -421,13 +447,9 @@ a different error after assignment:
This error is resolved by ignoring SSL errors. The complete command is:
```bash
env OPENSSL_CONF=/dev/null QT_QPA_PLATFORM=phantom ./phantomjs-2.1.1-linux-x86_64/bin/phantomjs --ignore-ssl-errors=true SheetJSPhantom.js
env OPENSSL_CONF=/dev/null QT_QPA_PLATFORM=phantom ./phantomjs-2.1.1-linux-x86_64/bin/phantomjs --ignore-ssl-errors=true test.js
```
:::
</details>
[^1]: See ["Workbook Object"](/docs/csf/book) for more details about the SheetJS workbook object.
[^2]: See [`table_to_book` in "HTML" Utilities](/docs/api/utilities/html#create-new-sheet)
[^3]: See [`write` in "Writing Files"](/docs/api/write-options)

@ -31,25 +31,25 @@ web browser. ["Browser Automation"](/docs/demos/net/headless) includes demos.
## Integration Details
Synthetic DOM implementations typically provide a function that accept an HTML
Synthetic DOM implementations typically provide a function that accept a HTML
string and return an object that represents `document`. An API method such as
`getElementsByTagName` or `querySelector` can pull TABLE elements.
```mermaid
flowchart LR
subgraph Synthetic DOM Operations
html(HTML\nstring)
subgraph Synthetic DOM Operations
html(HTML\nstring)
doc{{`document`\nDOM Object}}
end
subgraph SheetJS Operations
table{{DOM\nTable}}
wb(((SheetJS\nWorkbook)))
file(workbook\nfile)
end
end
subgraph SheetJS Operations
table{{DOM\nTable}}
wb(((SheetJS\nWorkbook)))
file(workbook\nfile)
end
html --> |Library\n\n| doc
doc --> |DOM\nAPI| table
table --> |`table_to_book`\n\n| wb
wb --> |`writeFile`\n\n| file
wb --> |`writeFile`\n\n| file
```
SheetJS methods use features that may be missing from some DOM implementations.
@ -107,35 +107,14 @@ const workbook = XLSX.utils.table_to_book(doc);
XLSX.writeFile(workbook, "SheetJSDOM.xlsx");
```
<details><summary><b>Complete Demo</b> (click to show)</summary>
:::note Tested Deployments
This demo was tested in the following deployments:
| JSDOM | Date |
|:----------|:-----------|
| `26.1.0` | 2025-04-24 |
| `25.0.1` | 2025-04-24 |
| `24.1.3` | 2025-04-24 |
| `23.2.0` | 2025-04-24 |
| `22.1.0` | 2025-04-24 |
| `21.1.2` | 2025-04-24 |
| `20.0.3` | 2025-04-24 |
| `19.0.0` | 2025-04-24 |
| `18.1.1` | 2025-04-24 |
| `17.0.0` | 2025-04-24 |
| `16.7.0` | 2025-04-24 |
| `15.2.1` | 2025-04-24 |
| `14.1.0` | 2025-04-24 |
| `13.2.0` | 2025-04-24 |
| `12.2.0` | 2025-04-24 |
| `11.12.0` | 2025-04-24 |
| `10.1.0` | 2025-04-24 |
This demo was last tested on 2024 January 27 against JSDOM `24.0.0`
:::
<details>
<summary><b>Complete Demo</b> (click to show)</summary>
1) Install SheetJS and JSDOM libraries:
<CodeBlock language="bash">{`\
@ -162,42 +141,20 @@ The script will create a file `SheetJSDOM.xlsx` that can be opened.
### HappyDOM
HappyDOM provides a DOM framework for NodeJS. Older versions required the
following patches:
HappyDOM provides a DOM framework for NodeJS. For the tested version (`13.3.1`),
the following patches were needed:
- TABLE `rows` property (explained above)
- TR `cells` property (explained above)
HappyDOM `15.7.4` did not require any workarounds.
<details><summary><b>Complete Demo</b> (click to show)</summary>
:::note Tested Deployments
This demo was tested in the following deployments:
| HappyDOM | Date |
|:---------|:-----------|
| 17.4.4 | 2025-04-24 |
| 16.8.1 | 2025-04-24 |
| 15.11.7 | 2025-04-24 |
| 14.12.3 | 2025-04-24 |
| 13.10.1 | 2025-04-24 |
| 12.10.3 | 2025-04-24 |
| 11.2.0 | 2025-04-24 |
| 10.11.2 | 2025-04-24 |
| 9.20.3 | 2025-04-24 |
| 8.9.0 | 2025-04-24 |
| 7.8.1 | 2025-04-24 |
| 6.0.4 | 2025-04-24 |
| 5.4.0 | 2025-04-24 |
| 4.1.0 | 2025-04-24 |
| 3.2.2 | 2025-04-24 |
| 2.55.0 | 2025-04-24 |
This demo was last tested on 2024 January 27 against HappyDOM `13.3.1`
:::
<details>
<summary><b>Complete Demo</b> (click to show)</summary>
1) Install SheetJS and HappyDOM libraries:
<CodeBlock language="bash">{`\
@ -237,30 +194,24 @@ tested version (`0.8.10`), the following patches were needed:
```js
Object.defineProperty(tbl.__proto__, "innerHTML", { get: function() {
var outerHTML = new XMLSerializer().serializeToString(this);
if(outerHTML.match(/</g).length == 1) return "";
return outerHTML.slice(0, outerHTML.lastIndexOf("</")).replace(/<[^"'>]*(("[^"]*"|'[^']*')[^"'>]*)*>/, "");
var outerHTML = new XMLSerializer().serializeToString(this);
if(outerHTML.match(/</g).length == 1) return "";
return outerHTML.slice(0, outerHTML.lastIndexOf("</")).replace(/<[^"'>]*(("[^"]*"|'[^']*')[^"'>]*)*>/, "");
}});
```
<details><summary><b>Complete Demo</b> (click to show)</summary>
:::note Tested Deployments
This demo was tested in the following deployments:
| XMLDOM | Date |
|:---------|:-----------|
| `0.9.8` | 2025-04-24 |
| `0.8.10` | 2025-04-24 |
This demo was last tested on 2024 March 12 against XMLDOM `0.8.10`
:::
<details>
<summary><b>Complete Demo</b> (click to show)</summary>
1) Install SheetJS and XMLDOM libraries:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @xmldom/xmldom@0.9.5`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @xmldom/xmldom@0.8.10`}
</CodeBlock>
2) Download [the sample script `SheetJSXMLDOM.js`](pathname:///dom/SheetJSXMLDOM.js):
@ -292,24 +243,18 @@ can be shimmed, but it is strongly recommended to use a more compliant library.
[`SheetJSCheerio.js`](pathname:///dom/SheetJSCheerio.js) implements the missing
features to ensure that SheetJS DOM methods can process TABLE elements.
<details><summary><b>Complete Demo</b> (click to show)</summary>
:::note Tested Deployments
This demo was tested in the following deployments:
| CheerioJS | Date |
|:--------------|:-----------|
| `1.0.0` | 2025-04-24 |
| `1.0.0-rc.12` | 2025-04-24 |
This demo was last tested on 2024 March 12 against Cheerio `1.0.0-rc.12`
:::
<details>
<summary><b>Complete Demo</b> (click to show)</summary>
1) Install SheetJS and CheerioJS libraries:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz cheerio@1.0.0`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz cheerio@1.0.0-rc.12`}
</CodeBlock>
2) Download [the sample script `SheetJSCheerio.js`](pathname:///dom/SheetJSCheerio.js):
@ -339,7 +284,7 @@ The script will create a file `SheetJSCheerio.xlsx` that can be opened.
### DenoDOM
[DenoDOM](https://deno.land/x/deno_dom) provides a DOM framework for Deno. For
the tested version (`0.1.48`), the following patches were needed:
the tested version (`0.1.43`), the following patches were needed:
- TABLE `rows` property (explained above)
- TR `cells` property (explained above)
@ -350,7 +295,7 @@ This example fetches [a sample table](pathname:///dom/SheetJSTable.html):
// @deno-types="https://cdn.sheetjs.com/xlsx-${current}/package/types/index.d.ts"
import * as XLSX from 'https://cdn.sheetjs.com/xlsx-${current}/package/xlsx.mjs';
\n\
import { DOMParser } from 'https://deno.land/x/deno_dom@v0.1.48/deno-dom-wasm.ts';
import { DOMParser } from 'https://deno.land/x/deno_dom@v0.1.43/deno-dom-wasm.ts';
\n\
const doc = new DOMParser().parseFromString(
await (await fetch('https://docs.sheetjs.com/dom/SheetJSTable.html')).text(),
@ -369,48 +314,28 @@ const workbook = XLSX.utils.table_to_book(tbl);
XLSX.writeFile(workbook, "SheetJSDenoDOM.xlsx");`}
</CodeBlock>
<details><summary><b>Complete Demo</b> (click to show)</summary>
:::note Tested Deployments
This demo was tested in the following deployments:
| Architecture | DenoDOM | Deno | Date |
|:-------------|:--------|:-------|:-----------|
| `darwin-x64` | 0.1.48 | 2.2.6 | 2025-03-31 |
| `darwin-arm` | 0.1.48 | 2.2.12 | 2025-04-24 |
| `win11-x64` | 0.1.48 | 2.2.12 | 2025-04-28 |
| `win11-arm` | 0.1.48 | 2.2.1 | 2025-02-23 |
| `linux-x64` | 0.1.48 | 2.3.6 | 2025-06-16 |
| `linux-arm` | 0.1.48 | 2.1.10 | 2025-02-16 |
This demo was last tested on 2024 January 27 against DenoDOM `0.1.43`
:::
<details>
<summary><b>Complete Demo</b> (click to show)</summary>
1) Save the previous codeblock to `SheetJSDenoDOM.ts`.
2) Run the script with `--allow-net` and `--allow-write` entitlements:
```bash
deno run --allow-net --allow-write --allow-import SheetJSDenoDOM.ts
deno run --allow-net --allow-write SheetJSDenoDOM.ts
```
The script will create a file `SheetJSDenoDOM.xlsx` that can be opened.
:::note pass
In older versions of Deno, the `--allow-import` flag must be omitted:
```bash
deno run --allow-net --allow-write SheetJSDenoDOM.ts
```
:::
</details>
[^1]: See [`table_to_sheet` in "HTML" Utilities](/docs/api/utilities/html#create-new-sheet)
[^2]: See ["Worksheet Object" in "SheetJS Data Model"](/docs/csf/sheet) for more details.
[^2]: See ["Worksheet Object" in "SheetJS Data Model"](/docs/csf/book) for more details.
[^3]: See [`table_to_book` in "HTML" Utilities](/docs/api/utilities/html#create-new-sheet)
[^4]: See ["Workbook Object" in "SheetJS Data Model"](/docs/csf/book) for more details.
[^5]: See [`sheet_add_dom` in "HTML" Utilities](/docs/api/utilities/html#add-to-sheet)

@ -16,13 +16,7 @@ With a familiar UI, `x-spreadsheet` is an excellent choice for a modern editor.
:::note Tested Deployments
This demo was tested in the following environments:
| Browser | Date |
|:-------------|:-----------|
| Chromium 136 | 2025-05-21 |
| Safari 18.2 | 2025-05-21 |
| Konqueror 22 | 2025-04-23 |
This demo was last verified on 2023 December 04.
:::
@ -39,7 +33,7 @@ features like scrolling may not work as expected.
```jsx live
function SheetJSXSpread() {
const [url, setUrl] = React.useState("https://docs.sheetjs.com/pres.numbers");
const [url, setUrl] = React.useState("https://sheetjs.com/pres.numbers");
const [done, setDone] = React.useState(false);
const ref = React.useRef(); // ref to DIV container
const set_url = (evt) => setUrl(evt.target.value);
@ -78,7 +72,7 @@ The following snippet fetches a spreadsheet and loads the grid:
```js
(async() => {
const ab = await (await fetch("https://docs.sheetjs.com/pres.numbers")).arrayBuffer();
const ab = await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer();
grid.loadData(stox(XLSX.read(ab)));
})();
```

@ -15,13 +15,7 @@ with a straightforward API.
:::note Tested Deployments
This demo was tested in the following environments:
| Browser | Date |
|:-------------|:-----------|
| Chromium 136 | 2025-05-21 |
| Safari 18.2 | 2025-05-21 |
| Konqueror 22 | 2025-04-23 |
This demo was last verified on 2023 December 04.
:::
@ -38,7 +32,7 @@ features like scrolling may not work as expected.
```jsx live
function SheetJSCDG() {
const [url, setUrl] = React.useState("https://docs.sheetjs.com/pres.numbers");
const [url, setUrl] = React.useState("https://sheetjs.com/pres.numbers");
const [done, setDone] = React.useState(false);
const ref = React.useRef(); // ref to DIV container
const set_url = (evt) => setUrl(evt.target.value);

@ -1,168 +0,0 @@
---
title: Tabulator
pagination_prev: demos/frontend/index
pagination_next: demos/net/index
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
[Tabulator](https://tabulator.info/) is a powerful data table library designed
for ease of use.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
Tabulator offers deep integration with SheetJS for importing and exporting data.
This demo covers additional detail including document customization.
[Click here for a live standalone integration demo.](pathname:///tabulator/)
:::note Tested Deployments
This demo was tested in the following deployments:
| Browser | Version | Date |
|:-------------|:--------|:-----------|
| Chromium 133 | `6.3.1` | 2025-03-31 |
| Konqueror 22 | `6.3.1` | 2025-04-23 |
:::
## Integration Details
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
are appropriate for sites that use the Tabulator CDN scripts.
[The "Frameworks" section](/docs/getting-started/installation/frameworks) covers
installation instructions for projects using a framework.
:::info pass
**The Tabulator script must be loaded after the SheetJS scripts!**
```html
<!-- Load SheetJS Scripts -->
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<!-- Tabulator must be loaded after SheetJS scripts -->
<script type="text/javascript" src="https://unpkg.com/tabulator-tables@6.3.1/dist/js/tabulator.min.js"></script>
```
:::
### Previewing Data
Tabulator offers a special `setData` method for assigning data after the table
is created. Coupled with the `autoColumns` option, Tabulator will automatically
refresh the table.
:::info pass
The library scans the first row object to determine the header labels. If a
column is missing a value in the first object, it will not be loaded!
:::
#### Fetching Files
When files are stored remotely, the recommended approach is to fetch the files,
parse with the SheetJS `read` method, generate arrays of objects from the target
sheet using `sheet_to_json`, and load data with the Tabulator `setData` method.
The following snippet fetches a sample file and loads the first sheet:
```html title="Fetching a spreadsheet and Displaying the first worksheet"
<!-- Tabulator DIV -->
<div id="htmlout"></div>
<script>
/* Initialize Tabulator with the `autoColumns: true` setting */
var tbl = new Tabulator('#htmlout', { autoColumns: true });
/* fetch and display https://docs.sheetjs.com/pres.numbers */
(function() { try {
fetch("https://docs.sheetjs.com/pres.numbers")
.then(function(res) { return res.arrayBuffer(); })
.then(function(ab) {
/* parse ArrayBuffer */
var wb = XLSX.read(ab);
/* get first worksheet from SheetJS workbook object */
var ws = wb.Sheets[wb.SheetNames[0]];
/* generate array of row objects */
var data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
/* update Tabulator */
tbl.setData(data);
});
} catch(e) {} })();
</script>
```
#### Local Files
Tabulator provides a special `import` method to show a dialog and load data.
Since the importer requires the raw binary data, the method must be called with
the third argument set to `"buffer"`:
```html title="Parsing a local spreadsheet and Displaying the first worksheet"
<button id="imp"><b>Click here to import from XLSX file</b></button>
<!-- Tabulator DIV -->
<div id="htmlout"></div>
<script>
/* Initialize Tabulator with the `autoColumns: true` setting */
var tbl = new Tabulator('#htmlout', { autoColumns: true });
/* use Tabulator SheetJS integration to import data */
document.getElementById("imp").addEventListener("click", function() {
tbl.import("xlsx", ".xlsx", "buffer");
})
</script>
```
### Saving Data
Tabulator provides a special `download` method to initiate the export:
```html title="Exporting data from Tabulator to XLSX"
<input type="submit" value="Export to XLSX!" id="xport" onclick="export_xlsx();">
<!-- Tabulator DIV -->
<div id="htmlout"></div>
<script>
/* Initialize Tabulator with the `autoColumns: true` setting */
var tbl = new Tabulator('#htmlout', { autoColumns: true });
/* use Tabulator SheetJS integration to import data */
function export_xlsx() {
/* use Tabulator SheetJS integration */
tbl.download("xlsx", "SheetJSTabulator.xlsx");
}
</script>
```
[The official documentation](https://tabulator.info/docs/6.3/download#xlsx)
covers supported options.
#### Post-processing
The `documentProcessing` event handler is called after Tabulator generates a
SheetJS workbook object. This allows for adjustments before creating the final
workbook file. The following example adds a second sheet that includes the date:
```js title="Exporting data and metadata"
tbl.download("xlsx", "SheetJSTabulator.xlsx", {
documentProcessing: function(wb) {
/* create a new worksheet */
var ws = XLSX.utils.aoa_to_sheet([
["SheetJS + Tabulator Demo"],
["Export Date:", new Date()]
]);
/* add to workbook */
XLSX.utils.book_append_sheet(wb, ws, "Metadata");
return wb;
}
});
```

@ -7,46 +7,23 @@ pagination_next: demos/net/index
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
[Vue 3 Table Lite](https://vue3-lite-table.vercel.app/) is a data table library
designed for the VueJS web framework.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses Vue 3 Table Lite and SheetJS to pull data from a spreadsheet and
display the content in a data table. We'll explore how to import data from files
into the data grid and how to export modified data from the grid to workbooks.
The ["Demo"](#demo) section includes a complete example that displays data from
user-supplied sheets and exports data to XLSX workbooks:
![vue3-table-lite screenshot](pathname:///vtl/vtl1.png)
:::note Tested Deployments
This demo was tested in the following deployments:
| Browser | Version | Date |
|:-------------|:--------|:-----------|
| Chromium 135 | `1.4.3` | 2025-04-23 |
| Konqueror 22 | `1.4.3` | 2025-04-23 |
This demo was tested against `vue3-table-lite 1.3.9`, VueJS `3.3.10` and ViteJS
`5.0.5` on 2023 December 04.
:::
The demo creates a site that looks like the screenshot below:
![vue3-table-lite screenshot](pathname:///vtl/vtl1.png)
## Integration Details
[The "Frameworks" section](/docs/getting-started/installation/frameworks) covers
installation in ViteJS projects using Vue 3 Table Lite.
Using the `npm` tool, this command installs SheetJS and Vue 3 Table Lite:
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz vue3-table-lite@1.4.3`}
</CodeBlock>
#### Rows and Columns Bindings
Vue 3 Table Lite presents two attribute bindings: an array of column metadata
`vue3-table-lite` presents two attribute bindings: an array of column metadata
(`columns`) and an array of objects representing the displayed data (`rows`).
Typically both are `ref` objects:
@ -137,7 +114,7 @@ cd sheetjs-vtl
2) Install dependencies:
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz vue3-table-lite@1.4.3`}
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz vue3-table-lite@1.3.9`}
</CodeBlock>
3) Download [`src/App.vue`](pathname:///vtl/App.vue) and replace the contents:
@ -154,5 +131,5 @@ npm run dev
5) Load the displayed URL (typically `http://localhost:5173`) in a web browser.
When the page loads, it will try to fetch https://docs.sheetjs.com/pres.numbers
When the page loads, it will try to fetch <https://sheetjs.com/pres.numbers>
and display the data. Click "Export" to generate a workbook.

@ -26,32 +26,7 @@ user-supplied sheets and exports data to XLSX workbooks:
:::note Tested Deployments
This demo was tested in the following environments:
| Browser | Version | Date |
|:-------------|:--------|:-----------|
| Chromium 135 | `5.3.2` | 2025-04-23 |
| Konqueror 22 | `5.3.2` | 2025-04-23 |
:::
:::danger pass
**Glide Data Grid is not compatible with ReactJS 19!**
When trying to install in a new project, `npm install` will fail:
```
npm error Found: react@19.1.0
npm error node_modules/react
npm error react@"^19.0.0" from the root project
npm error
npm error Could not resolve dependency:
npm error peer react@"^16.12.0 || 17.x || 18.x" from @glideapps/glide-data-grid@5.3.2
npm error node_modules/@glideapps/glide-data-grid
```
This demo explicitly uses ReactJS 18.
This demo was last tested on 2023 December 04 with Glide Data Grid 5.3.2
:::
@ -365,19 +340,13 @@ cd sheetjs-gdg
npm i
```
2) Explicitly downgrade ReactJS to version 18:
```bash
npm i --save react@18 react-dom@18
```
3) Install SheetJS and Glide Data Grid libraries:
2) Install SheetJS and Glide Data Grid libraries:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @glideapps/glide-data-grid@5.3.2`}
</CodeBlock>
4) Start the dev server:
3) Start dev server:
```bash
npm run dev
@ -386,21 +355,21 @@ npm run dev
The terminal window will display a URL (typically `http://localhost:5173`).
Open the URL with a web browser and confirm that a page loads.
5) Download [`App.tsx`](pathname:///gdg/App.tsx) and replace `src/App.tsx`:
4) Download [`App.tsx`](pathname:///gdg/App.tsx) and replace `src/App.tsx`:
```bash
curl -L -o src/App.tsx https://docs.sheetjs.com/gdg/App.tsx
```
#### Testing
**Testing**
6) Refresh the browser window. A grid should be displayed:
5) Refresh the browser window. A grid should be displayed:
![glide-data-grid initial view](pathname:///gdg/pre.png)
The demo downloads and processes https://docs.sheetjs.com/pres.numbers .
The demo downloads and processes <https://sheetjs.com/pres.numbers>.
7) Make some changes to the grid data.
6) Make some changes to the grid data.
:::note pass
@ -415,14 +384,13 @@ values should be 41, 42, 43, 44, and 45, as shown in the screenshot below:
![glide-data-grid after edits](pathname:///gdg/post.png)
8) Click on the "Export" button. The browser should attempt to download a XLSX
file (`sheetjs-gdg.xlsx`). Save the file.
7) Click on the "Export" button to create a XLSX file (`sheetjs-gdg.xlsx`).
Open the generated file and verify the contents match the grid.
9) Reload the page. The contents will revert back to the original table.
8) Reload the page. The contents will revert back to the original table.
10) Click "Choose File" and select the new `sheetjs-gdg.xlsx` file. The table
9) Click "Choose File" and select the new `sheetjs-gdg.xlsx` file. The table
should update with the data in the file.
[^1]: See ["Array of Objects" in the ReactJS demo](/docs/demos/frontend/react#array-of-objects)

@ -26,21 +26,19 @@ user-supplied sheets and exports data to XLSX workbooks:
This demo was tested in the following environments:
| Browser | Version | Date | Notes |
|:-------------|:----------------|:-----------|:----------------------------|
| Chromium 135 | `7.0.0-beta.19` | 2025-04-23 | Requires ReactJS 18 |
| Chromium 135 | `7.0.0-beta.52` | 2025-04-23 | No edit support |
| Konqueror 22 | `7.0.0-beta.52` | 2025-04-23 | No edit support, CSS errors |
| Version | Date | Notes |
|:----------------|:-----------|:---------------------|
| `7.0.0-beta.19` | 2023-12-04 | |
| `7.0.0-beta.41` | 2023-12-04 | Editing did not work |
:::
:::danger pass
:::warning pass
When this demo was last tested against the latest version, the grid correctly
displayed data but data could not be edited by the user.
When this demo was last tested, the grid correctly displayed data but could not
be edited by the user.
The current recommendation is to use version `7.0.0-beta.19` and to forcefully
downgrade ReactJS to version 18.
The current recommendation is to use version `7.0.0-beta.19`.
:::
@ -52,7 +50,7 @@ installation with Yarn and other package managers.
Using the `npm` tool, this command installs SheetJS and React Data Grid:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-data-grid@7.0.0-beta.19`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-data-grid@7.0.0-beta.41`}
</CodeBlock>
Methods and components in both libraries can be loaded in pages using `import`:
@ -148,31 +146,19 @@ function rdg_to_ws(rows: Row[]): WorkSheet {
## Demo
1) Create a new ViteJS app using the `react-ts` template:
1) Create a new TypeScript `create-react-app` app:
```bash
npm create vite@latest -- sheetjs-rdg --template react-ts
npx create-react-app sheetjs-rdg --template typescript
cd sheetjs-rdg
```
2) Install dependencies:
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-data-grid@7.0.0-beta.19`}
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-data-grid@7.0.0-beta.41`}
</CodeBlock>
<details>
<summary><b>Installing RDG version that supports editing</b> (click to show)</summary>
Editing support requires ReactJS 18 and React DataGrid version `7.0.0-beta.19`:
<CodeBlock language="bash">{`\
npm i -S react@18 react-dom@18
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-data-grid@7.0.0-beta.19`}
</CodeBlock>
</details>
3) Download [`App.tsx`](pathname:///rdg/App.tsx) and replace `src/App.tsx`.
```bash
@ -182,48 +168,12 @@ curl -L -o src/App.tsx https://docs.sheetjs.com/rdg/App.tsx
4) Start the development server:
```bash
npm run dev
npm start
```
The terminal window will display a URL (typically `http://localhost:5173`).
Open the URL with a web browser and confirm that a page loads.
:::caution pass
**There were breaking changes in point releases of React DataGrid!**
The JavaScript console may show an error message referencing `default`:
```
Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/react-data-grid.js?v=f9b1b87a' does not provide an export named 'default' (at App.tsx:2:8)
```
In a point release, `DataGrid` was moved from the default export to a named
export. The `src/App.tsx` script must be edited to reflect the change:
```js title="src/App.tsx (edit highlighted lines)"
import React, { useEffect, useState, ChangeEvent } from "react";
// highlight-next-line
import { textEditor, Column, DataGrid } from "react-data-grid";
import { read, utils, WorkSheet, writeFile } from "xlsx";
```
After updating the script, the webpage must be manually refreshed.
:::
#### Testing
5) Confirm the table shows a list of Presidents.
When the page loads, it will fetch https://docs.sheetjs.com/pres.numbers, parse
5) When the page loads, it will fetch <https://sheetjs.com/pres.numbers>, parse
with SheetJS, and load the data in the data grid.
6) Click the "export [.xlsx]" button to export the grid data to XLSX. It should
attempt to download `SheetJSRDG.xlsx`.
7) Open the generated file in a spreadsheet editor. Set cell A7 to "SheetJS Dev"
and set cell B7 to 47. Save the file.
8) Use the file picker to select the modified file. The table will refresh and
show the new data.
6) Click one of the "export" buttons to export the grid data to file.

@ -5,174 +5,44 @@ pagination_next: demos/net/index
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
Material UI is a collection of ReactJS Components that follows the
[Google Material Design system](https://material.io/)
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses Material UI and SheetJS to pull data from a spreadsheet and
display the data. We'll explore how to import data from spreadsheets and export
data to spreadsheets. The following Material UI components will be tested:
- ["Table"](#material-ui-table) is based on the core HTML TABLE element.
- ["Data Grid"](#material-ui-data-grid) is a data grid for larger datasets.
:::note pass
The [ReactJS demo](/docs/demos/frontend/react) covers basic ReactJS concepts.
It should be perused before reading this demo.
:::
This demo covers the traditional Material UI Table as well as the MUI Data Grid.
## Integration Details
[The "Frameworks" section](/docs/getting-started/installation/frameworks) covers
installation in projects using Material UI.
After installing the SheetJS module in a ReactJS project, `import` statements
can load relevant parts of the library.
```js
import { read, utils, writeFileXLSX } from 'xlsx';
```
## Material UI Table
The `Table` component abstracts the `<table>` element in HTML.
The `Table` component abstracts the `<table>` element in HTML. `table_to_book`
can process a `ref` attached to the `Table` element:
### Importing Data
Starting from a SheetJS worksheet object[^1], the `sheet_to_json` method[^2]
generates an array of row objects.
In the [ReactJS "Array of Objects" demo](/docs/demos/frontend/react), the array
of objects is rendered by manually mapping over data. For example, starting from
the following spreadsheet and data:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
<tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
</td><td>
```js
[
{ Name: "Bill Clinton", Index: 42 },
{ Name: "GeorgeW Bush", Index: 43 },
{ Name: "Barack Obama", Index: 44 },
{ Name: "Donald Trump", Index: 45 },
{ Name: "Joseph Biden", Index: 46 }
]
```
</td></tr></tbody></table>
The HTML table elements map to MUI components:
| HTML | MUI |
|:--------|:------------|
| `TABLE` | `Table` |
| `THEAD` | `TableHead` |
| `TBODY` | `TableBody` |
| `TR` | `TableRow` |
| `TD` | `TableCell` |
The library requires a `TableContainer` container component.
The following example JSX shows a table using HTML and using MUI components:
<Tabs>
<TabItem value="ReactJS" label="ReactJS">
```jsx title="Example JSX for displaying arrays of objects"
<table>
{/* The `thead` section includes the table header row */}
<thead><tr><th>Name</th><th>Index</th></tr></thead>
{/* The `tbody` section includes the data rows */}
<tbody>
{/* generate row (TR) for each president */}
// highlight-start
{pres.map(row => (
<tr>
{/* Generate cell (TD) for name / index */}
<td>{row.Name}</td>
<td>{row.Index}</td>
</tr>
))}
// highlight-end
</tbody>
</table>
```
</TabItem>
<TabItem value="MUI" label="Material UI">
```jsx title="Example JSX for displaying arrays of objects"
<TableContainer><Table>
{/* The `TableHead` section includes the table header row */}
<TableHead><TableRow><TableCell>Name</TableCell><TableCell>Index</TableCell></TableRow></TableHead>
{/* The `TableBody` section includes the data rows */}
<TableBody>
{/* generate row (TableRow) for each president */}
// highlight-start
{pres.map((row, idx) => (
<TableRow key={idx}>
{/* Generate cell (TableCell) for name / index */}
<TableCell>{row.Name}</TableCell>
<TableCell>{row.Index}</TableCell>
</TableRow>
))}
// highlight-end
</TableBody>
</Table></TableContainer>
```
</TabItem>
</Tabs>
### Exporting Data
The SheetJS `table_to_book` method[^3] can parse data from a DOM element.
The MUI `Table` element is really an HTML TABLE element under the hood. A `ref`
attached to the `Table` element can be processed by `table_to_book`.
The following snippet uses the `writeFileXLSX` method[^4] to generate and
download a XLSX workbook:
```tsx title="Skeleton Component for exporting a Material UI Table"
```tsx
import TableContainer from '@mui/material/TableContainer';
import Table from '@mui/material/Table';
// ...
// highlight-start
import { utils, writeFileXLSX } from "xlsx";
import { useRef } from "react";
// highlight-end
export default function MUITableSheetJSExport() {
/* This ref will be attached to the <Table> component */
// ...
export default function BasicTable() {
// highlight-next-line
const tbl = useRef<HTMLTableElement>(null);
const xport = () => {
/* the .current field will be a TABLE element */
const table_elt = tbl.current;
/* generate SheetJS workbook */
// highlight-next-line
const wb = utils.table_to_book(table_elt);
/* export to XLSX */
writeFileXLSX(wb, "SheetJSMaterialUI.xlsx");
};
return ( <>
<button onClick={xport}>Export</button>
<TableContainer>
<button onClick={() => {
// highlight-next-line
const wb = utils.table_to_book(tbl.current);
writeFileXLSX(wb, "SheetJSMaterialUI.xlsx");
}}>Export</button>
<TableContainer {...}>
// highlight-next-line
<Table ref={tbl}>{/* ... */}</Table>
<Table {...} ref={tbl}>
{/* ... material ui table machinations ... */}
</Table>
</TableContainer>
<>);
}
@ -182,12 +52,8 @@ export default function MUITableSheetJSExport() {
:::note Tested Deployments
This demo was tested in the following deployments:
| Browser | Material UI | Emotion | Date |
|:-------------|:------------|:----------|:-----------|
| Chromium 135 | `7.0.2` | `11.11.4` | 2025-04-23 |
| Konqueror 22 | `7.0.2` | `11.11.4` | 2025-04-23 |
This demo was last run on 2023 December 04 against Material UI 5.14.19 paired
with Emotion 11.11.1
:::
@ -201,7 +67,7 @@ cd sheetjs-mui
2) Install dependencies:
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/material@7.0.2 @emotion/react@11.11.4 @emotion/styled@11.11.5`}
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/material@5.14.19 @emotion/react@11.11.1 @emotion/styled@11.11.0`}
</CodeBlock>
3) Download [`App.tsx`](pathname:///mui/table/App.tsx) and replace `src/App.tsx`.
@ -216,14 +82,8 @@ curl -L -o src/App.tsx https://docs.sheetjs.com/mui/table/App.tsx
npm run dev
```
The terminal window will display a URL (typically `http://localhost:5173`).
Open the URL with a web browser and confirm that a page loads.
Confirm the table shows a list of Presidents. When the page loads, it will fetch
https://docs.sheetjs.com/pres.numbers, parse with SheetJS, and load the data in
the data grid.
Click the "Export" button and open the generated file in a spreadsheet editor.
The script should open the live demo in a web browser. Click the "Export" button
to save the file. Open the generated file in a spreadsheet editor.
## Material UI Data Grid
@ -335,12 +195,8 @@ export default function App() {
:::note Tested Deployments
This demo was tested in the following deployments:
| Browser | Data Grid | Emotion | Date |
|:-------------|:----------|:----------|:-----------|
| Chromium 125 | `8.0.0` | `11.11.4` | 2025-04-23 |
| Konqueror 22 | `8.0.0` | `11.11.4` | 2025-04-23 |
This demo was last run on 2023 December 04 against MUI data grid 6.18.3 paired
with Emotion 11.11.1
:::
@ -354,7 +210,7 @@ cd sheetjs-muidg
2) Install dependencies:
<CodeBlock language="bash">{`\
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/x-data-grid@8.0.0 @emotion/react@11.11.4 @emotion/styled@11.11.5`}
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @mui/x-data-grid@6.18.3 @emotion/react@11.11.1 @emotion/styled@11.11.0`}
</CodeBlock>
3) Download [`App.tsx`](pathname:///mui/dg/App.tsx) and replace `src/App.tsx`.
@ -369,31 +225,4 @@ curl -L -o src/App.tsx https://docs.sheetjs.com/mui/dg/App.tsx
npm run dev
```
The terminal window will display a URL (typically `http://localhost:5173`).
Open the URL with a web browser and confirm that a page loads.
When the page loads, it will process https://docs.sheetjs.com/pres.numbers
:::caution pass
The data grid uses nascent ECMAScript features that are not supported in older
browsers. Shims can be added in the `<head>` section of `index.html`:
```html title="index.html (add highlighted lines in HEAD)"
<head>
<meta charset="UTF-8" />
<!-- highlight-start -->
<script>
/* workarounds for legacy browsers */
if(!Object.hasOwn) Object.hasOwn = function(o, v) { return o.hasOwnProperty(v); };
if(!Array.prototype.at) Array.prototype.at = function(idx) { return this[idx < 0 ? idx + this.length : idx]; };
</script>
<!-- highlight-end -->
```
:::
[^1]: See ["Sheet Objects"](/docs/csf/sheet)
[^2]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^3]: See [`table_to_book` in "HTML" Utilities](/docs/api/utilities/html#create-new-sheet)
[^4]: See [`writeFileXLSX` in "Writing Files"](/docs/api/write-options)
When the page loads, it will fetch and process <https://sheetjs.com/pres.numbers>

@ -30,7 +30,8 @@ The `sheet_to_json` utility function generates arrays of objects, which is
suitable for a number of libraries. When more advanced shapes are needed,
it is easier to process an array of arrays.
#### x-spreadsheet
### x-spreadsheet
With a familiar UI, `x-spreadsheet` is an excellent choice for a modern editor.
@ -38,7 +39,7 @@ With a familiar UI, `x-spreadsheet` is an excellent choice for a modern editor.
**[The exposition has been moved to a separate page.](/docs/demos/grid/xs)**
#### Canvas Datagrid
### Canvas Datagrid
After extensive testing, `canvas-datagrid` stood out as a high-performance grid
with a straightforward API.
@ -47,16 +48,15 @@ with a straightforward API.
**[The exposition has been moved to a separate page.](/docs/demos/grid/cdg)**
#### Tabulator
### Tabulator
[Tabulator](https://tabulator.info/docs/5.4/download#xlsx) includes deep support
through a special Export button. It handles the SheetJS operations internally.
**[The exposition has been moved to a separate page.](/docs/demos/grid/tabulator)**
#### Angular UI Grid
### Angular UI Grid
:::danger pass
:::warning pass
This UI Grid is for AngularJS, not the modern Angular. New projects should not
use AngularJS. This demo is included for legacy applications.
@ -67,8 +67,7 @@ The [AngularJS demo](/docs/demos/frontend/angularjs) covers more general strateg
[Click here for a live integration demo.](pathname:///angularjs/ui-grid.html)
<details>
<summary><b>Notes</b> (click to show)</summary>
<details><summary><b>Notes</b> (click to show)</summary>
The library does not provide any way to modify the import button, so the demo
includes a simple directive for a File Input HTML element. It also includes a
@ -93,21 +92,21 @@ and idioms. The same `sheet_to_json` and `json_to_sheet` / `aoa_to_sheet`
methods are used, but they pull from a shared state object that can be mutated
with other buttons and components on the page.
#### React Data Grid
### React Data Grid
**[The exposition has been moved to a separate page.](/docs/demos/grid/rdg)**
#### Glide Data Grid
### Glide Data Grid
**[The exposition has been moved to a separate page.](/docs/demos/grid/gdg)**
#### Material UI Data Grid
### Material UI Data Grid
**[The exposition has been moved to a separate page.](/docs/demos/grid/mui#material-ui-data-grid)**
<!-- spellchecker-disable -->
#### vue3-table-lite
### vue3-table-lite
<!-- spellchecker-enable -->
@ -134,7 +133,7 @@ TABLE elements and when writing to XLSX and other spreadsheet formats.
:::
#### Fixed Tables
### Fixed Tables
When the page has a raw HTML table, the easiest solution is to attach an `id`:
@ -171,10 +170,10 @@ XLSX.writeFile(wb, "HTMLFlicker.xlsx");
document.body.removeChild(tbl);
```
#### React
### React
**[The exposition has been moved to a separate page.](/docs/demos/frontend/react#html)**
#### Material UI Table
### Material UI Table
**[The exposition has been moved to a separate page.](/docs/demos/grid/mui#material-ui-table)**

@ -57,8 +57,8 @@ The lines are automatically added if `sheets` plugin is enabled during setup.
Spreadsheet files added in the `_data` subdirectory are accessible from template
files using the name stem.
For example, [`pres.xlsx`](https://docs.sheetjs.com/pres.xlsx) can be accessed
using the variable `pres` in a template.
For example, [`pres.xlsx`](https://sheetjs.com/pres.xlsx) can be accessed using
the variable `pres` in a template.
#### Single-Sheet Workbooks
@ -133,8 +133,8 @@ This demo was tested in the following environments:
| Lume | Date |
|:---------|:-----------|
| `1.19.4` | 2025-01-02 |
| `2.4.3` | 2025-01-02 |
| `1.19.4` | 2024-03-16 |
| `2.1.2` | 2024-03-16 |
This example uses the Nunjucks template format. Lume plugins support additional
template formats, including Markdown and JSX.
@ -150,17 +150,15 @@ template formats, including Markdown and JSX.
```bash
mkdir -p sheetjs-lume
cd sheetjs-lume
deno run -Ar https://deno.land/x/lume@v2.4.3/init.ts
deno run -Ar https://deno.land/x/lume@v2.1.2/init.ts
```
When prompted, enter the following options. The initialization script has
changed over time, so not all questions will be asked.
When prompted, enter the following options:
- `What kind of setup do you want?`: select `Basic + plugins`
- `Choose the configuration file format`: select `_config.ts`
- `Do you want to install some plugins now?`: select `Yes`
- `Select the plugins to install`: select `sheets` and `nunjucks`
- `Do you want to setup a CMS?`: select `No` or `Maybe later`
- `Do you want to setup a CMS?`: select `Maybe later`
The project will be configured and modules will be installed.
@ -170,11 +168,11 @@ The `nunjucks` plugin was included by default in Lume version 1.
:::
2) Download https://docs.sheetjs.com/pres.xlsx and place in a `_data` subfolder:
2) Download <https://sheetjs.com/pres.xlsx> and place in a `_data` subfolder:
```bash
mkdir -p _data
curl -L -o _data/pres.xlsx https://docs.sheetjs.com/pres.xlsx
curl -L -o _data/pres.xlsx https://sheetjs.com/pres.xlsx
```
3) Create a `index.njk` file that references the file:
@ -218,9 +216,7 @@ After saving the spreadsheet, the page will refresh and show the new contents.
### Static Site
6) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
7) Build the static site:
6) Stop the server (press `CTRL+C` in the terminal window) and run
```bash
deno task lume
@ -228,10 +224,10 @@ deno task lume
This will create a static site in the `_site` folder
8) Test the generated site by starting a web server:
7) Test the generated site by running
```bash
npx -y http-server _site
npx http-server _site
```
The program will display a URL (typically `http://localhost:8080`). Accessing

@ -13,9 +13,6 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
export const r = {style: {color:"red"}};
export const R = {style: {backgroundColor:"darkred"}};
GatsbyJS is a framework for creating websites. It uses React components for page
templates and GraphQL for loading data.
@ -51,26 +48,14 @@ overridden through a `package.json` override in the latest versions of NodeJS:
:::
:::danger Telemetry
:::warning Telemetry
Older versions of GatsbyJS collect telemetry by default. The `telemetry`
subcommand can disable telemetry:
GatsbyJS collects telemetry by default. The `telemetry` subcommand can disable it:
```js
npx gatsby telemetry --disable
```
---
GatsbyJS `5.14.1` does not transmit telemetry:
```text title="Expected output in GatsbyJS 5.14.1"
Telemetry is no longer gathered and is always disabled
```
It is still strongly encouraged to disable telemetry since older projects may
use a GatsbyJS version that embeds telemetry.
:::
## Integration Details
@ -191,8 +176,8 @@ This demo was tested in the following environments:
| GatsbyJS | Date |
|:---------|:-----------|
| `5.14.1` | 2025-01-19 |
| `4.25.8` | 2025-01-02 |
| `5.12.1` | 2023-12-04 |
| `4.25.8` | 2024-03-27 |
:::
@ -204,25 +189,6 @@ This demo was tested in the following environments:
npx gatsby telemetry --disable
```
:::info pass
In NodeJS 22, the process displayed an error:
<pre>
<span {...R}> ERROR </span><span {...r}> UNKNOWN</span>
{`\n`}
{`\n`}
(node:25039) [DEP0040] DeprecationWarning: The `punycode` module is deprecated.
Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
</pre>
**This is a false report!**
The error can be safely ignored.
:::
1) Create a template site:
```bash
@ -233,8 +199,8 @@ npx gatsby new sheetjs-gatsby
For older Gatsby versions, the project must be built from the starter project.
The starter commit for GatsbyJS 4 is `6bc4466090845f20650117b3d27e68e6e46dc8d5`.
This version of the starter can be fetched with `git`:
For GatsbyJS 4, the starter commit is `6bc4466090845f20650117b3d27e68e6e46dc8d5`
and the steps are shown below:
```bash
git clone https://github.com/gatsbyjs/gatsby-starter-default sheetjs-gatsby
@ -246,7 +212,7 @@ cd ..
:::
2) Start the local development server:
2) Follow the on-screen instructions for starting the local development server:
```bash
cd sheetjs-gatsby
@ -269,7 +235,7 @@ Open a web browser to the displayed URL (typically `http://localhost:8000/`)
`}
</CodeBlock>
4) Install the SheetJS library and GatsbyJS plugins:
4) Install the library and plugins:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
@ -289,12 +255,12 @@ npm i --save gatsby-transformer-excel@4 gatsby-source-filesystem@4
:::
5) Make a `src/data` directory, download https://docs.sheetjs.com/pres.xlsx, and
5) Make a `src/data` directory, download <https://sheetjs.com/pres.xlsx>, and
move the downloaded file into the new folder:
```bash
mkdir -p src/data
curl -L -o src/data/pres.xlsx https://docs.sheetjs.com/pres.xlsx
curl -L -o src/data/pres.xlsx https://sheetjs.com/pres.xlsx
```
6) Edit `gatsby-config.js` and add the following lines to the `plugins` array:
@ -341,18 +307,14 @@ If the `plugins` array exists, the two plugins should be added at the beginning:
:::
7) Stop and restart the development server process:
```bash
npm run develop
```
Stop and restart the development server process (`npm run develop`).
### GraphiQL test
8) Open the GraphiQL editor in a web browser. The output of the previous step
displayed the URL (typically `http://localhost:8000/___graphql` ).
7) Open the GraphiQL editor. The output of the previous step displayed the URL
(typically `http://localhost:8000/___graphql` )
9) Paste the following query into the code editor:
There is an editor in the left pane. Paste the following query into the editor:
```graphql title="GraphQL Query (paste into editor)"
{
@ -371,60 +333,11 @@ Press the Execute Query button (`▶`) and data should show up in the right pane
![GraphiQL Screenshot](pathname:///gatsby/graphiql.png)
<details>
<summary><b>Sample Output</b> (click to show)</summary>
In GatsbyJS versions `5.13.4` and `4.25.8`, the raw output was:
```json title="GraphQL query result from GatsbyJS 5.13.4"
{
"data": {
"allPresXlsxSheet1": {
"edges": [
{
"node": {
"Name": "Bill Clinton",
"Index": 42
}
},
{
"node": {
"Name": "GeorgeW Bush",
"Index": 43
}
},
{
"node": {
"Name": "Barack Obama",
"Index": 44
}
},
{
"node": {
"Name": "Donald Trump",
"Index": 45
}
},
{
"node": {
"Name": "Joseph Biden",
"Index": 46
}
}
]
}
},
"extensions": {}
}
```
</details>
### React page
10) Create a new page `src/pages/pres.js` that displays the raw query result:
8) Create a new file `src/pages/pres.js` that uses the query and displays the result:
```jsx title="src/pages/pres.js (create new file)"
```jsx title="src/pages/pres.js"
import { graphql } from "gatsby"
import * as React from "react"
@ -445,10 +358,8 @@ const PageComponent = ({data}) => {
export default PageComponent;
```
Save the file.
11) Access the `/pres` page (typically `http://localhost:8000/pres` ) in a web
browser. The displayed JSON is the data that the component receives:
After saving the file, access `http://localhost:8000/pres` in the browser. The
displayed JSON is the data that the component receives:
```js title="Expected contents of /pres"
{
@ -463,7 +374,7 @@ browser. The displayed JSON is the data that the component receives:
// ....
```
12) Change `PageComponent` to display a table based on the data:
9) Change `PageComponent` to display a table based on the data:
```jsx title="src/pages/pres.js (replace PageComponent)"
import { graphql } from "gatsby"
@ -502,7 +413,7 @@ Going back to the browser, `http://localhost:8000/pres` will show a table:
### Live refresh
13) Open the file `src/data/pres.xlsx` in Excel or another spreadsheet editor.
10) Open the file `src/data/pres.xlsx` in Excel or another spreadsheet editor.
Add a new row at the end of the file, setting cell `A7` to "SheetJS Dev" and
cell `B7` to `47`. The sheet should look like the following screenshot:
@ -514,7 +425,7 @@ Save the file and observe that the table has refreshed with the new data:
### Static site
14) Stop the development server and build the site:
11) Stop the development server and build the site:
```bash
npm run build
@ -523,6 +434,13 @@ npm run build
The build output will confirm that the `/pres` route is static:
```text title="Output from GatsbyJS build process"
Pages
┌ src/pages/404.js
│ ├ /404/
│ └ /404.html
├ src/pages/index.js
│ └ /
└ src/pages/pres.js
└ /pres/
@ -538,7 +456,7 @@ The build output will confirm that the `/pres` route is static:
The generated page will be placed in `public/pres/index.html`.
15) Open `public/pres/index.html` with a text editor and search for "SheetJS".
12) Open `public/pres/index.html` with a text editor and search for "SheetJS".
There will be a HTML row:
```html title="public/pres/index.html (Expected contents)"

@ -32,8 +32,8 @@ importing the SheetJS library in a browser script.
## ESBuild Loader
ESBuild releases starting from `0.9.1` support custom loader plugins. The loader
receives an absolute path to the spreadsheet on the filesystem.
ESBuild supports custom loader plugins. The loader receives an absolute path to
the spreadsheet on the filesystem.
The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be
imported from ESBuild loader plugins.
@ -69,9 +69,9 @@ flowchart LR
aoo --> |app.js\nfrontend code| html
```
### ESBuild Configuration
### ESBuild Config
Plugins can be referenced in the `plugins` array of the build configuration:
Plugins can be referenced in the `plugins` array of the build config object:
```js title="build.mjs (structure)"
import * as esbuild from 'esbuild'
@ -200,27 +200,7 @@ document.body.appendChild(elt);
:::note Tested Deployments
This demo was tested in the following environments:
| `esbuild` | Date |
|:----------|:-----------|
| `0.24.2` | 2025-01-07 |
| `0.23.1` | 2025-01-07 |
| `0.22.0` | 2025-01-07 |
| `0.21.5` | 2025-01-07 |
| `0.20.2` | 2025-01-07 |
| `0.19.12` | 2025-01-07 |
| `0.18.20` | 2025-01-07 |
| `0.17.19` | 2025-01-07 |
| `0.16.17` | 2025-01-07 |
| `0.15.18` | 2025-01-07 |
| `0.14.54` | 2025-01-07 |
| `0.13.15` | 2025-01-07 |
| `0.12.29` | 2025-01-07 |
| `0.11.23` | 2025-01-07 |
| `0.10.2` | 2025-01-07 |
| `0.9.7` | 2025-01-07 |
| `0.9.1` | 2025-01-07 |
This demo was last tested on 2023 December 04 against ESBuild 0.19.8
:::
@ -232,7 +212,7 @@ This demo was tested in the following environments:
mkdir sheetjs-esb
cd sheetjs-esb
npm init -y
npm i --save esbuild@0.20.2
npm i --save esbuild@0.19.8
```
1) Install the SheetJS NodeJS module:
@ -275,10 +255,10 @@ document.body.appendChild(elt);
curl -LO https://docs.sheetjs.com/esbuild/build.mjs
```
5) Download https://docs.sheetjs.com/pres.numbers to the project folder:
5) Download <https://sheetjs.com/pres.numbers> to the project folder:
```bash
curl -LO https://docs.sheetjs.com/pres.numbers
curl -LO https://sheetjs.com/pres.numbers
```
### Static Site Test
@ -294,7 +274,7 @@ The final script will be saved to `out.js`
7) Start a local web server to host the project folder:
```bash
npx -y http-server .
npx http-server .
```
The command will print a list of URLs.

@ -12,15 +12,15 @@ sidebar_custom_props:
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
[ViteJS](https://vitejs.dev/) is a build tool for generating static websites. It
has a robust JavaScript-powered plugin system[^1].
[ViteJS](https://vitejs.dev/) is a modern build tool for generating static sites.
It has a robust JavaScript-powered plugin system[^1]
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses ViteJS and SheetJS to pull data from a spreadsheet and display
the content in an HTML table. We'll explore how to load SheetJS in a ViteJS
plugin and evaluate data loading strategies.
plugin and compare a few different data loading strategies.
The ["Complete Demo"](#complete-demo) section creates a complete website powered
by a XLSX spreadsheet.
@ -32,97 +32,16 @@ suitable for end of week or end of month (EOM) reports published in HTML tables.
For processing user-submitted files in the browser, the
[ViteJS "Bundlers" demo](/docs/demos/frontend/bundler/vitejs) shows client-side
bundling of SheetJS libraries. The ["ReactJS" demo](/docs/demos/frontend/react)
bundling of the SheetJS library. The ["ReactJS" demo](/docs/demos/frontend/react)
shows example sites using ViteJS with the ReactJS starter.
:::
## Plugins
ViteJS supports static asset imports[^2], but the default raw loader interprets
data as UTF-8 strings. This corrupts binary formats including XLSX and XLS. A
custom loader can bypass the raw loader and directly read files.
Since a custom loader must be used, some data processing work can be performed
by the loader. Three approaches are explored in this demo.
The following diagrams show the ViteJS data flow. The pink "Main Script import"
boxes represent the division between the loader and the main script. The green
"SheetJS Operations" boxes represent the steps performed by SheetJS libraries.
<table>
<tr>
<th>[HTML](#html-plugin)</th>
<th>[Data](#pure-data-plugin)</th>
<th>[Base64](#base64-plugin)</th>
</tr>
<tr>
<td style={{verticalAlign: "top"}}>
```mermaid
flowchart TB
file[(workbook\nfile)]
buffer(NodeJS\nBuffer)
sheetjs[[SheetJS Operations]]
tabeller{{HTML\nString}}
handoff[[Main Script import]]
html{{HTML\nTABLE}}
style handoff fill:#FFC7CE
style sheetjs fill:#C6EFCE
file --> buffer
buffer --> sheetjs
sheetjs --> tabeller
tabeller --> handoff
handoff --------> html
```
</td>
<td style={{verticalAlign: "top"}}>
```mermaid
flowchart TB
file[(workbook\nfile)]
buffer(NodeJS\nBuffer)
sheetjs[[SheetJS Operations]]
aoo(array of\nobjects)
handoff[[Main Script import]]
import(array of\nobjects)
html{{HTML\nTABLE}}
style handoff fill:#FFC7CE
style sheetjs fill:#C6EFCE
file --> buffer
buffer --> sheetjs
sheetjs --> aoo
aoo --> handoff
handoff ------> import
import --> html
```
</td>
<td style={{verticalAlign: "top"}}>
```mermaid
flowchart TB
file[(workbook\nfile)]
base64(Base64\nString)
handoff[[Main Script import]]
import(Base64\nString)
sheetjs[[SheetJS Operations]]
aoo(array of\nobjects)
html{{HTML\nTABLE}}
style handoff fill:#FFC7CE
style sheetjs fill:#C6EFCE
file --> base64
base64 ------> handoff
handoff --> import
import --> sheetjs
sheetjs --> aoo
aoo --> html
```
</td>
</tr>
</table>
ViteJS supports static asset imports[^2], but the default raw loader interprets data
as UTF-8 strings. This corrupts binary formats like XLSX and XLS, but a custom
loader can override the default behavior.
For simple tables of data, ["Pure Data Plugin"](#pure-data-plugin) is strongly
recommended. The file processing is performed at build time and the generated
@ -132,9 +51,6 @@ For more complex parsing or display logic, ["Base64 Plugin"](#base64-plugin) is
preferable. Since the raw parsing logic is performed in the page, the library
will be included in the final bundle.
The ["HTML Plugin"](#html-plugin) generates HTML in the loader script. The
SheetJS HTML writer renders merged cells and other features.
### Pure Data Plugin
For a pure static site, a plugin can load data into an array of row objects. The
@ -156,7 +72,7 @@ flowchart LR
```
This ViteJS plugin will read spreadsheets using the SheetJS `read` method[^3]
and generate arrays of row objects with the SheetJS `sheet_to_json`[^4] method:
and generate arrays of row objects with `sheet_to_json`[^4]:
```js title="vite.config.js"
import { readFileSync } from 'fs';
@ -169,27 +85,17 @@ export default defineConfig({
plugins: [
{ // this plugin handles ?sheetjs tags
name: "vite-sheet",
transform(_code, id) {
transform(code, id) {
if(!id.match(/\?sheetjs$/)) return;
var wb = read(readFileSync(id.replace(/\?sheetjs$/, "")));
var data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
return `export default JSON.parse('${JSON.stringify(data).replace(/\\/g, "\\\\")}')`;
return `export default JSON.parse('${JSON.stringify(data)}')`;
}
}
]
});
```
:::info pass
ViteJS plugins are expected to return strings representing ECMAScript modules.
The plugin uses `JSON.stringify` to encode the array of objects. The generated
string is injected into the new module code. When ViteJS processes the module,
`JSON.parse` recovers the array of objects.
:::
In frontend code, the loader will look for all modules with a `?sheetjs`
query string. The default export is an array of row objects.
@ -209,75 +115,9 @@ document.querySelector('#app').innerHTML = `<table>
</table>`;
```
### HTML Plugin
A plugin can generate raw HTML strings that can be added to a page. The SheetJS
libraries are used in the plugin but will not be added to the site.
The following diagram depicts the workbook waltz:
```mermaid
flowchart LR
file[(workbook\nfile)]
subgraph SheetJS operations
buffer(NodeJS\nBuffer)
tavolo{{HTML\nString}}
end
html{{HTML\nTABLE}}
file --> |vite.config.js\ncustom plugin| buffer
buffer --> |vite.config.js\ncustom plugin| tavolo
tavolo --> |main.js\nfrontend code| html
```
This ViteJS plugin will read spreadsheets using the SheetJS `read` method[^5]
and generate HTML using the SheetJS `sheet_to_html`[^6] method:
```js title="vite.config.js"
import { readFileSync } from 'fs';
import { read, utils } from 'xlsx';
import { defineConfig } from 'vite';
export default defineConfig({
assetsInclude: ['**/*.xlsx'], // xlsx file should be treated as assets
plugins: [
{ // this plugin handles ?html tags
name: "vite-sheet-html",
transform(_code, id) {
if(!id.match(/\?html/)) return;
var wb = read(readFileSync(id.replace(/\?html/, "")));
var html = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
return (`export default JSON.parse('${JSON.stringify(html).replace(/\\/g, "\\\\")}')`);
}
}
]
});
```
:::info pass
ViteJS plugins are expected to return strings representing ECMAScript modules.
The plugin uses `JSON.stringify` to encode the HTML string. The generated string
is injected into the new module code. When ViteJS processes the module,
`JSON.parse` recovers the original HTML string.
:::
In frontend code, the loader will look for all modules with a `?html` query
string. The default export is a string that can be directly added to the page.
The following example script sets the `innerHTML` property of the container:
```js title="main.js"
import html from './data/pres.xlsx?html';
document.querySelector('#app').innerHTML = html;
```
### Base64 Plugin
This plugin pulls in data as a Base64 string that can be read with `read`[^7].
This plugin pulls in data as a Base64 string that can be read with `read`[^5].
While this approach works, it is not recommended since it loads the library in
the front-end site.
@ -309,7 +149,7 @@ export default defineConfig({
plugins: [
{ // this plugin handles ?b64 tags
name: "vite-b64-plugin",
transform(_code, id) {
transform(code, id) {
if(!id.match(/\?b64$/)) return;
var path = id.replace(/\?b64/, "");
var data = readFileSync(path, "base64");
@ -321,7 +161,7 @@ export default defineConfig({
```
When importing using the `b64` query, the raw Base64 string will be exposed.
`read` will process the Base64 string using the `base64` input type[^8]:
`read` will process the Base64 string using the `base64` input type[^6]:
```js title="main.js"
import { read, utils } from "xlsx";
@ -347,23 +187,22 @@ document.querySelector('#app').innerHTML = `<table>
## Complete Demo
The demo walks through the process of creating a new ViteJS website from scratch.
A Git repository with the completed site can be cloned[^9].
:::note Tested Deployments
This demo was tested in the following environments:
| ViteJS | Date |
|:---------|:-----------|
| `6.2.3` | 2025-03-30 |
| `5.4.15` | 2025-03-30 |
| `4.5.10` | 2025-03-30 |
| `3.2.11` | 2025-03-30 |
| `2.9.18` | 2025-03-30 |
| `5.0.5` | 2023-12-04 |
| `4.5.0` | 2023-12-04 |
| `3.2.7` | 2023-12-04 |
| `2.9.16` | 2023-12-04 |
:::
The demo walks through the process of creating a new ViteJS website from scratch.
A Git repository with the completed site can be cloned[^7].
### Initial Setup
1) Create a new site with the `vue-ts` template and install the SheetJS package:
@ -388,11 +227,11 @@ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
curl -O https://docs.sheetjs.com/vitejs/vite.config.ts
```
3) Make a `data` folder and download https://docs.sheetjs.com/pres.xlsx :
3) Make a `data` folder and download <https://sheetjs.com/pres.xlsx> :
```bash
mkdir -p data
curl -L -o data/pres.xlsx https://docs.sheetjs.com/pres.xlsx
curl -L -o data/pres.xlsx https://sheetjs.com/pres.xlsx
```
### Pure Data Test
@ -430,36 +269,15 @@ Save and refresh the page. A data table should be displayed
```bash
npm run build
npx -y http-server dist/
npx http-server dist/
```
The terminal will display a URL, typically `http://127.0.0.1:8080` . Access
that page with a web browser.
:::caution pass
When this demo was tested against ViteJS `2.9.18`, the build failed:
```
src/App.vue:8:3 - error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
8 <img alt="Vue logo" src="./assets/logo.png" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
**As it affects the project template, this is a bug in ViteJS.**
The simplest workaround is to force upgrade the `vue-tsc` dependency:
```bash
npm i vue-tsc@latest
```
:::
7) To confirm that only the raw data is present in the page, view the page
source. The code will reference a script `/assets/index-HASH.js` where `HASH` is
a string of characters. Open that script.
source. The code will reference some script like `/assets/index-HASH.js`.
Open that script.
Searching for `Bill Clinton` reveals the following:
@ -473,11 +291,11 @@ included in the final site!
:::info pass
ViteJS also supports "Server-Side Rendering". In SSR, only the HTML table
would be added to the final page. Details are covered in the ViteJS docs[^10].
would be added to the final page. Details are covered in the ViteJS docs[^8].
:::
### HTML Test
### Base64 Test
8) Run the dev server:
@ -485,88 +303,10 @@ would be added to the final page. Details are covered in the ViteJS docs[^10].
npm run dev
```
Open a browser window to the displayed URL (typically `http://localhost:5173` )
Open a browser window to the displayed URL.
9) Replace the component `src/components/HelloWorld.vue` with:
```html title="src/components/HelloWorld.vue"
<script setup lang="ts">
// @ts-ignore
import html from '../../data/pres.xlsx?html';
</script>
<template>
<div v-html="html"></div>
</template>
```
Save and refresh the page. A data table should be displayed
10) Stop the dev server and build the site
```bash
npm run build
npx -y http-server dist/
```
The terminal will display a URL, typically `http://127.0.0.1:8080` . Access
that page with a web browser.
:::caution pass
When this demo was tested against ViteJS `2.9.18`, the build failed:
```
src/App.vue:8:3 - error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
8 <img alt="Vue logo" src="./assets/logo.png" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
**As it affects the project template, this is a bug in ViteJS.**
The simplest workaround is to force upgrade the `vue-tsc` dependency:
```bash
npm i vue-tsc@latest
```
:::
11) To confirm that only the raw HTML is present in the page, view the page
source. The code will reference a script `/assets/index-HASH.js` where `HASH` is
a string of characters. Open that script.
Searching for `Bill Clinton` reveals the following encoded HTML element:
```
<td data-t=\\"s\\" data-v=\\"Bill Clinton\\" id=\\"sjs-A2\\">Bill Clinton</td>
```
Searching for `BESSELJ` should reveal no results. The SheetJS scripts are not
included in the final site!
:::info pass
The HTML code is still stored in a script and is injected dynamically.
ViteJS "Server-Side Rendering" offers the option to render the site at build
time, ensuring that the HTML table is directly added to the page.
:::
### Base64 Test
12) Run the dev server:
```bash
npm run dev
```
Open a browser window to the displayed URL (typically `http://localhost:5173` )
13) Replace the component `src/components/HelloWorld.vue` with:
```html title="src/components/HelloWorld.vue"
<script setup lang="ts">
// @ts-ignore
@ -590,40 +330,19 @@ const data = utils.sheet_to_json<IPresident>(ws);
</template>
```
14) Stop the dev server and build the site
10) Stop the dev server and build the site
```bash
npm run build
npx -y http-server dist/
npx http-server dist/
```
The terminal will display a URL ( `http://127.0.0.1:8080` ). Access that page
with a web browser.
:::caution pass
When this demo was tested against ViteJS `2.9.18`, the build failed:
```
src/App.vue:8:3 - error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
8 <img alt="Vue logo" src="./assets/logo.png" />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
**As it affects the project template, this is a bug in ViteJS.**
The simplest workaround is to force upgrade the `vue-tsc` dependency:
```bash
npm i vue-tsc@latest
```
:::
15) To confirm that the object data is not present in the page, view the page
source. The code will reference a script `/assets/index-HASH.js` where `HASH` is
a string of characters. Open that script.
11) To confirm that the object data is not present in the page, view the page
source. The code will reference some script like `/assets/index-HASH.js` with
a different hash from the previous test. Open that script.
Searching for `BESSELJ` should match the code:
@ -637,10 +356,8 @@ embedded in the final site and the data is parsed when the page is loaded.
[^1]: See ["Using Plugins"](https://vitejs.dev/guide/using-plugins.html) in the ViteJS documentation.
[^2]: See ["Static Asset Handling"](https://vitejs.dev/guide/assets.html) in the ViteJS documentation.
[^3]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^4]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output)
[^4]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^5]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^6]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^7]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^8]: See [the "base64" type in "Reading Files"](/docs/api/parse-options#input-type)
[^9]: See [`examples/sheetjs-vite`](https://git.sheetjs.com/examples/sheetjs-vite/) on the SheetJS Git server.
[^10]: See ["Server-Side Rendering"](https://vitejs.dev/guide/ssr.html) in the ViteJS documentation.
[^6]: See [the "base64" type in "Reading Files"](/docs/api/parse-options#input-type)
[^7]: See [`SheetJS/sheetjs-vite`](https://git.sheetjs.com/sheetjs/sheetjs-vite/) on the SheetJS git server.
[^8]: See ["Server-Side Rendering"](https://vitejs.dev/guide/ssr.html) in the ViteJS documentation.

@ -55,25 +55,11 @@ flowchart LR
aoo --> |src/index.js\nfrontend code| html
```
### Webpack Configuration
### Webpack Config
The Webpack configuration is normally saved to `webpack.config.js`.
A special rule should be added to `module.rules`:
#### Required Settings
`module.rules` is an array of rule objects that controls module synthesis.[^2]
For the SheetJS Webpack integration, the following properties are required:
- `test` describes whether the rule is relevant. If the property is a regular
expression, Webpack will test the filename against the `test` property.
- `use` lists the loaders that will process files matching the `test`. The
loaders are specified using the `loader` property of the loader object.
The following example instructs Webpack to use the `sheetjs-loader.js` script
when the file name ends in `.numbers` or `.xls` or `.xlsx` or `.xlsb`:
```js title="webpack.config.js (define loader)"
```js title="webpack.config.js"
// ...
module.exports = {
// ...
@ -82,7 +68,7 @@ module.exports = {
// highlight-start
{
/* `test` matches file extensions */
test: /\.(numbers|xls|xlsx|xlsb)$/,
test: /\.(numbers|xls|xlsx|xlsb)/,
/* use the loader script */
use: [ { loader: './sheetjs-loader' } ]
}
@ -92,21 +78,24 @@ module.exports = {
};
```
#### Recommended Settings
Hot Module Replacement enables reloading when files are updated:
It is strongly recommended to enable other Webpack features:
```js title="webpack.config.js"
// ...
module.exports = {
// ...
// highlight-start
devServer: {
static: './dist',
hot: true,
}
// highlight-end
};
```
- `resolve.alias` defines path aliases. If data files are stored in one folder,
an alias ensures that each page can reference the files using the same name[^3].
It is strongly recommended to add an alias to simplify imports:
- `devServer.hot` enables "hot module replacement"[^4], ensuring that pages will
refresh in development mode when spreadsheets are saved.
The following example instructs Webpack to treat `~` as the root of the project
(so `~/data/pres.xlsx` refers to `pres.xlsx` in the data folder) and to enable
live reloading:
```js title="webpack.config.js (other recommended settings)"
```js title="webpack.config.js"
// ...
module.exports = {
// ...
@ -118,32 +107,21 @@ module.exports = {
}
},
// highlight-end
// ...
// highlight-start
/* enable live reloading in development mode */
devServer: { static: './dist', hot: true }
// highlight-end
};
```
### SheetJS Loader
The SheetJS loader script must be saved to the script referenced in the Webpack
configuration (`sheetjs-loader.js`).
As with [ViteJS](/docs/demos/static/vitejs), Webpack will interpret data as
UTF-8 strings. This corrupts binary formats including XLSX and XLS. To suppress
this behavior and instruct Webpack to pass a NodeJS `Buffer` object, the loader
script must export a `raw` property that is set to `true`[^5].
The SheetJS loader script must export a `raw` property that is set to `true`.
The base export is expected to be the loader function. The loader receives the
file bytes as a Buffer, which can be parsed with the SheetJS `read` method[^6].
`read` returns a SheetJS workbook object[^7].
file bytes as a Buffer, which can be parsed with the SheetJS `read` method[^2].
`read` returns a SheetJS workbook object[^3].
The loader in this demo will parse the workbook, pull the first worksheet, and
generate an array of row objects using the `sheet_to_json` method[^8]:
generate an array of row objects using the `sheet_to_json` method[^4]:
```js title="sheetjs-loader.js (Webpack loader)"
```js title="sheetjs-loader.js"
const XLSX = require("xlsx");
function loader(content) {
@ -153,11 +131,8 @@ function loader(content) {
var data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
return `export default JSON.parse('${JSON.stringify(data)}')`;
}
/* ensure the function receives a Buffer */
loader.raw = true;
/* export the loader */
module.exports = loader;
```
@ -166,7 +141,7 @@ module.exports = loader;
Spreadsheets can be imported using the plugin. Assuming `pres.xlsx` is stored
in the `data` subfolder, `~/data/pres.xlsx` can be imported from any script:
```js title="src/index.js (main script)"
```js title="src/index.js"
import data from '~/data/pres.xlsx';
/* `data` is an array of objects from data/pres.xlsx */
@ -184,11 +159,7 @@ document.body.appendChild(elt);
:::note Tested Deployments
This demo was tested in the following deployments:
| Version | Date |
|:---------|:-----------|
| `5.97.1` | 2025-01-08 |
This demo was last tested on 2023 December 04 against Webpack 5.89.0
:::
@ -200,7 +171,7 @@ This demo was tested in the following deployments:
mkdir sheetjs-wp5
cd sheetjs-wp5
npm init -y
npm install webpack@5.97.1 webpack-cli@6.0.1 webpack-dev-server@5.2.0 --save
npm install webpack@5.89.0 webpack-cli@5.1.4 webpack-dev-server@4.15.1 --save
mkdir -p dist
mkdir -p src
mkdir -p data
@ -264,7 +235,7 @@ module.exports = {
module: {
rules: [
{
test: /\.(numbers|xls|xlsx|xlsb)$/,
test: /\.(numbers|xls|xlsx|xlsb)/,
use: [ { loader: './sheetjs-loader' } ]
}
]
@ -289,10 +260,10 @@ loader.raw = true;
module.exports = loader;
```
6) Download https://docs.sheetjs.com/pres.xlsx and save to the `data` folder:
6) Download <https://sheetjs.com/pres.xlsx> and save to the `data` folder:
```bash
curl -L -o data/pres.xlsx https://docs.sheetjs.com/pres.xlsx
curl -L -o data/pres.xlsx https://sheetjs.com/pres.xlsx
```
### Live Reload Test
@ -316,10 +287,9 @@ The terminal will print URLs for the development server:
It should display a table of Presidents with "Name" and "Index" columns
10) Add a new row to the spreadsheet (set `A7` to "SheetJS Dev" and `B7` to 47)
and save the file.
10) Add a new row to the spreadsheet and save the file.
After saving the file, the page should automatically refresh with the new data.
Upon saving, the page should refresh with the new data.
### Static Site Test
@ -334,7 +304,7 @@ The final site will be placed in the `dist` folder.
12) Start a local web server to host the `dist` folder:
```bash
npx -y http-server dist
npx http-server dist
```
The command will print a list of URLs.
@ -350,10 +320,6 @@ To verify that the data was added to the page, append `main.js` to the URL
president names. It will not include SheetJS library references!
[^1]: See ["Plugins"](https://webpack.js.org/concepts/plugins/) in the Webpack documentation.
[^2]: See [`module.rules`](https://webpack.js.org/configuration/module/#modulerules) in the Webpack documentation.
[^3]: See [`resolve.alias`](https://webpack.js.org/configuration/resolve/#resolvealias) in the Webpack documentation.
[^4]: See ["Hot Module Replacement"](https://webpack.js.org/concepts/hot-module-replacement/) in the Webpack documentation.
[^5]: See ["Raw" Loader](https://webpack.js.org/api/loaders/#raw-loader) in the Webpack documentation.
[^6]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^7]: See ["Workbook Object"](/docs/csf/book)
[^8]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^3]: See ["Workbook Object"](/docs/csf/book)
[^4]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)

@ -32,7 +32,6 @@ flowchart LR
file --> |.eleventy.js\ncustom parser| buffer
buffer --> |.eleventy.js\ncustom parser| aoo
aoo --> |index.njk\ntemplate| html
linkStyle 1 color:blue,stroke:blue;
```
:::tip No Telemetry
@ -92,8 +91,8 @@ module.exports = (eleventyConfig) => {
Spreadsheet files added in the `_data` subdirectory are accessible from template
files using the name stem.
For example, [`pres.numbers`](https://docs.sheetjs.com/pres.numbers) can be
accessed using the variable `pres` in a template:
For example, [`pres.numbers`](https://sheetjs.com/pres.numbers) can be accessed
using the variable `pres` in a template:
```liquid title="index.njk"
<table><thead><tr><th>Name</th><th>Index</th></tr></thead>
@ -115,11 +114,10 @@ accessed using the variable `pres` in a template:
This demo was tested in the following environments:
| Eleventy | Date |
|:---------------|:-----------|
| `2.0.1` | 2025-05-07 |
| `3.0.0` | 2025-05-07 |
| `3.1.0-beta.1` | 2025-05-07 |
| Eleventy | Date |
|:----------------|:-----------|
| `2.0.1` | 2024-03-15 |
| `3.0.0-alpha.5` | 2024-03-15 |
:::
@ -136,28 +134,28 @@ npm init -y
2) Install Eleventy and SheetJS libraries:
<Tabs groupId="11ty">
<TabItem value="2" label="2.x">
<TabItem value="2" label="Stable">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @11ty/eleventy@2.0.1`}
</CodeBlock>
</TabItem>
<TabItem value="3" label="3.x">
<TabItem value="3" label="Alpha">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @11ty/eleventy@3.0.0`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @11ty/eleventy@3.0.0-alpha.5`}
</CodeBlock>
</TabItem>
</Tabs>
3) Make a new `_data` subdirectory in the project. Download the example file
[`pres.xlsx`](https://docs.sheetjs.com/pres.xlsx) into `_data`:
[`pres.xlsx`](https://sheetjs.com/pres.xlsx) into `_data`:
```bash
mkdir _data
curl -Lo _data/pres.xlsx https://docs.sheetjs.com/pres.xlsx
curl -Lo _data/pres.xlsx https://sheetjs.com/pres.xlsx
```
4) Download the following files to the project folder:
@ -214,7 +212,7 @@ Eleventy will place the generated site in the `_site` subfolder.
9) Start a web server to host the static site:
```bash
npx -y http-server _site
npx http-server _site
```
Open a web browser and access the displayed URL ( `http://localhost:8080` ).

@ -31,18 +31,18 @@ The ["Demo"](#demo) uses NextJS and SheetJS to pull data from a spreadsheet.
We'll explore how to create asset modules that process spreadsheet data at build
time and how to read files on the server in NextJS lifecycle methods.
:::danger Telemetry
:::warning Telemetry
NextJS collects telemetry by default. The `telemetry` subcommand can disable it:
```js
npx -y next@13.5.6 telemetry disable
npx next@13.5.6 telemetry disable
```
The setting can be verified by running
```js
npx -y next@13.5.6 telemetry status
npx next@13.5.6 telemetry status
```
:::
@ -75,27 +75,12 @@ This demo was tested in the following environments:
| NextJS | NodeJS | Date |
|:----------|:----------|:-----------|
| ` 9.5.5` | `16.20.2` | 2025-04-24 |
| `10.2.3` | `16.20.2` | 2025-04-24 |
| `11.1.4` | `16.20.2` | 2025-04-24 |
| `12.3.7` | `20.14.0` | 2025-04-24 |
| `13.5.11` | `20.14.0` | 2025-04-24 |
| `14.2.28` | `20.14.0` | 2025-04-24 |
| `15.3.1` | `20.18.0` | 2025-04-24 |
:::
:::info pass
SheetJS libraries work in legacy NextJS apps. Older versions of this demo have
been tested against versions `3.2.3`, `4.2.3`, `5.1.0`, `6.1.2` and `7.0.3`.
NextJS has made a number of breaking changes over the years. Older versions of
NextJS use legacy versions of ReactJS that do not support function components
and other idioms.
[`examples/reactjs-legacy`](https://git.sheetjs.com/examples/reactjs-legacy) on
the SheetJS Git server includes code samples for legacy NextJS versions.
| ` 9.5.5` | `16.20.2` | 2023-12-04 |
| `10.2.3` | `16.20.2` | 2023-12-04 |
| `11.1.4` | `16.20.2` | 2023-12-04 |
| `12.3.4` | `20.10.0` | 2023-12-04 |
| `13.5.6` | `20.10.0` | 2023-12-04 |
| `14.0.3` | `20.10.0` | 2023-12-04 |
:::
@ -114,7 +99,7 @@ but does not support live reloading.
:::caution pass
When the demo was last tested, Turbopack did not support true raw loaders. For
development use, the normal `npx -y next dev` should be used.
development use, the normal `npx next dev` should be used.
:::
@ -211,7 +196,7 @@ export async function getStaticProps() {
The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be
imported from page scripts.
:::danger pass
:::warning pass
[The SheetJS ESM build](/docs/getting-started/installation/nodejs#esm-import)
does not load NodeJS native modules directly. The Installation section includes
@ -258,7 +243,7 @@ export async function getServerSideProps() {
}
```
:::danger Reading and writing files during the build process
:::warning Reading and writing files during the build process
As the NextJS workaround is non-traditional, it bears repeating:
@ -569,13 +554,13 @@ When upgrading NextJS is not an option, NodeJS should be downgraded to v16.
0) Disable NextJS telemetry:
```js
npx -y next@13.5.6 telemetry disable
npx next@13.5.6 telemetry disable
```
Confirm it is disabled by running
```js
npx -y next@13.5.6 telemetry status
npx next@13.5.6 telemetry status
```
1) Set up folder structure. At the end, a `pages` folder with a `sheets`
@ -584,7 +569,6 @@ npx -y next@13.5.6 telemetry status
```bash
mkdir sheetjs-next
cd sheetjs-next
npm init -y
mkdir -p pages/sheets/
```
@ -667,7 +651,7 @@ cd ../..
6) Test the deployment:
```bash
npx -y next
npx next
```
Open a web browser and access:
@ -693,7 +677,7 @@ After saving the file, the website should refresh with the new row.
8) Stop the server and run a production build:
```bash
npx -y next build
npx next build
```
The final output will show a list of the routes and types:
@ -715,26 +699,19 @@ As explained in the summary, the `/getStaticPaths` and `/getStaticProps` routes
are completely static. 2 `/sheets/#` pages were generated, corresponding to 2
worksheets in the file. `/getServerSideProps` is server-rendered.
:::info pass
NextJS historically used lowercase Lambda (`λ`) to denote dynamic paths. This
was changed to a stylized lowercase F (`ƒ`) in recent versions of NextJS.
:::
9) Try to build a static site:
<Tabs groupId="nextver">
<TabItem value="13" label="NextJS 9 - 13">
```bash
npx -y next export
npx next export
```
</TabItem>
<TabItem value="14" label="NextJS 14">
:::danger NextJS breaking changes
:::warning NextJS breaking changes
**NextJS 14 removed the `export` subcommand!**
@ -753,7 +730,7 @@ module.exports = {
After adding the line, run the `build` command:
```bash
npx -y next build
npx next build
```
</TabItem>
@ -764,13 +741,7 @@ This build will fail. A static page cannot be generated at this point because
### Static Site
10) Delete `pages/getServerSideProps.js`:
```bash
rm -f pages/getServerSideProps.js
```
11) Rebuild the static site:
10) Delete `pages/getServerSideProps.js` and rebuild:
<Tabs groupId="nextver">
<TabItem value="13" label="NextJS 9 - 13">
@ -787,16 +758,17 @@ module.exports = {
webpack: (config) => {
```
After editing `next.config.js`, run the build command:
After editing `next.config.js`:
</TabItem>
</Tabs>
```bash
npx -y next build
rm -f pages/getServerSideProps.js
npx next build
```
Inspecting the output, there should be no lines with `λ` or `ƒ`:
Inspecting the output, there should be no lines with the `λ` symbol:
```
Route (pages) Size First Load JS
@ -810,13 +782,13 @@ Route (pages) Size First Load JS
└ /sheets/1
```
12) Generate the static site:
11) Generate the static site:
<Tabs groupId="nextver">
<TabItem value="13" label="NextJS 9 - 13">
```bash
npx -y next export
npx next export
```
</TabItem>
@ -834,7 +806,7 @@ module.exports = {
After adding the line, run the `build` command:
```bash
npx -y next build
npx next build
```
</TabItem>
@ -842,10 +814,10 @@ npx -y next build
The static site will be written to the `out` subfolder
13) Serve the static site:
12) Serve the static site:
```bash
npx -y http-server out
npx http-server out
```
The command will start a local HTTP server at `http://localhost:8080/` for
@ -853,16 +825,16 @@ testing the generated site. Note that `/getServerSideProps` will 404 since the
page was removed.
[^1]: See the ["Webpack" asset module demo](/docs/demos/static/webpack) for more details.
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options).
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^3]: See ["SheetJS Data Model"](/docs/csf/) for more details.
[^4]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output).
[^5]: See [`readFile` in "Reading Files"](/docs/api/parse-options).
[^6]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output).
[^4]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^5]: See [`readFile` in "Reading Files"](/docs/api/parse-options)
[^6]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^7]: See [`getStaticProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props) in the NextJS documentation.
[^8]: See [`getStaticPaths`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-paths) in the NextJS documentation.
[^9]: See [`getServerSideProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props) in the NextJS documentation.
[^10]: See [`fallback` in `getStaticPaths`](https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-false) in the NextJS documentation.
[^10]: See [`fallback` in getStaticPaths](https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-false) in the NextJS documentation.
[^11]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output)
[^12]: [`dangerouslySetInnerHTML`](https://react.dev/reference/react-dom/components/common#common-props) is a ReactJS prop supported for all built-in components.
[^13]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output).
[^14]: See ["Array of Objects" in the ReactJS demo](/docs/demos/frontend/react#rendering-data).
[^13]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^14]: See ["Array of Objects" in the ReactJS demo](/docs/demos/frontend/react#rendering-data)

@ -33,9 +33,6 @@ Content v1 (paired with VueJS 2.x and NuxtJS 2.x)
The ["Nuxt Content v2"](#nuxt-content-v2) section explores "transformers" for
NuxtJS Content v2 (paired with VueJS 3.x and NuxtJS 3.x)
The ["Nuxt Content v3"](#nuxt-content-v3) section explores raw ViteJS modules
(paired with VueJS 3.x and NuxtJS 3.x)
:::info pass
This demo focuses on server-side processing with NuxtJS and VueJS.
@ -49,23 +46,22 @@ that process spreadsheets in the browser.
This demo was tested in the following environments:
| Nuxt Content | Nuxt | Date |
|:-------------|:-----------|:-----------|
| `1.15.1` | `2.18.1` | 2025-04-23 |
| `2.13.4` | `3.17.2` | 2025-05-12 |
| `3.5.1` | `3.17.3` | 2025-05-18 |
| Nuxt Content | Nuxt | Date |
|:-------------|:---------|:-----------|
| `1.15.1` | `2.17.2` | 2023-12-04 |
| `2.9.0` | `3.8.2` | 2023-12-04 |
:::
:::danger Telemetry
:::warning Telemetry
Nuxt embeds telemetry. According to the docs, it can be disabled with:
```bash
npx -y nuxt telemetry disable
npx nuxt telemetry disable
```
**When the demo was last tested, this command did not work.**
**At the time the demo was last tested, this command did not work.**
Disabling telemetry requires a few steps:
@ -104,14 +100,6 @@ Click "OK" in each window (3 windows) and restart your computer.
telemetry.enabled=false
```
The following command can be run in the Linux / MacOS terminal:
```bash
cat >~/.nuxtrc <<EOF
telemetry.enabled=false
EOF
```
3) For Nuxt 3 sites, set the `telemetry` option in the Nuxt config file (either `nuxt.config.ts` or `nuxt.config.js`):
```js title="nuxt.config.js"
@ -272,23 +260,23 @@ The recommended solution is to switch to Node 18.
1) Create a stock app:
```bash
npx -y create-nuxt-app@4.0.0 sheetjs-nuxt
npx create-nuxt-app@4.0.0 sheetjs-nuxt
```
When prompted, enter the following options:
- `Project name`: press <kbd>Enter</kbd> (use default `sheetjs-nuxt`)
- `Programming language`: press <kbd></kbd> (`TypeScript` selected) then <kbd>Enter</kbd>
- `Package manager`: select `Npm` and press <kbd>Enter</kbd>
- `UI framework`: select `None` and press <kbd>Enter</kbd>
- `Nuxt.js modules`: scroll to `Content`, select with <kbd>Space</kbd>, then press <kbd>Enter</kbd>
- `Linting tools`: press <kbd>Enter</kbd> (do not select any Linting tools)
- `Testing framework`: select `None` and press <kbd>Enter</kbd>
- `Rendering mode`: select `Universal (SSR / SSG)` and press <kbd>Enter</kbd>
- `Deployment target`: select `Static (Static/Jamstack hosting)` and press <kbd>Enter</kbd>
- `Development tools`: press <kbd>Enter</kbd> (do not select any Development tools)
- `What is your GitHub username?`: press <kbd>Enter</kbd> (use default)
- `Version control system`: select `None` and press <kbd>Enter</kbd>
- `Project name`: press Enter (use default `sheetjs-nuxt`)
- `Programming language`: press Down Arrow (`TypeScript` selected) then Enter
- `Package manager`: select `Npm` and press Enter
- `UI framework`: select `None` and press Enter
- `Nuxt.js modules`: scroll to `Content`, select with Space, then press Enter
- `Linting tools`: press Enter (do not select any Linting tools)
- `Testing framework`: select `None` and press Enter
- `Rendering mode`: select `Universal (SSR / SSG)` and press Enter
- `Deployment target`: select `Static (Static/Jamstack hosting)` and press Enter
- `Development tools`: press Enter (do not select any Development tools)
- `What is your GitHub username?`: press Enter
- `Version control system`: select `None`
The project will be configured and modules will be installed.
@ -308,17 +296,17 @@ When the build finishes, the terminal will display a URL like:
The server is listening on that URL. Open the link in a web browser.
3) Download https://docs.sheetjs.com/pres.xlsx and move to the `content` folder.
3) Download <https://sheetjs.com/pres.xlsx> and move to the `content` folder.
```bash
curl -L -o content/pres.xlsx https://docs.sheetjs.com/pres.xlsx
curl -L -o content/pres.xlsx https://sheetjs.com/pres.xlsx
```
4) Modify `nuxt.config.js` as follows:
- Add the following to the top of the script:
```js title="nuxt.config.js (add to top)"
```js
import { readFile, utils } from 'xlsx';
// This will be called when the files change
@ -339,7 +327,7 @@ const parseSheet = (file, { path }) => {
Replace the property with the following definition:
```js title="nuxt.config.js (replace content key in object)"
```js
// content.extendParser allows us to hook into the parsing step
content: {
extendParser: {
@ -356,7 +344,7 @@ Replace the property with the following definition:
5) Replace `pages/index.vue` with the following:
```html title="pages/index.vue (replace contents)"
```html title="pages/index.vue"
<!-- sheetjs (C) 2013-present SheetJS -- https://sheetjs.com -->
<template><div>
<div v-for="item in data.data" v-bind:key="item.name">
@ -403,31 +391,21 @@ The page should automatically refresh with the new content:
![Nuxt Demo end of step 6](pathname:///nuxt/nuxt6.png)
7) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
8) Build the static site:
7) Stop the server (press `CTRL+C` in the terminal window) and run
```bash
npm run generate
```
This will create a static site in the `dist` folder.
9) Serve the static site:
This will create a static site in the `dist` folder, which can be served with:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) in a web browser.
To confirm that the spreadsheet data is added to the site, view the page source.
Searching for `Bill Clinton` reveals the following encoded HTML row:
```html
<tr><td>Bill Clinton</td> <td>42</td></tr>
```
Accessing the page `http://localhost:8080` will show the page contents. Verifying
the static nature is trivial: make another change in Excel and save. The page
will not change.
## Nuxt Content v2
@ -503,22 +481,11 @@ export default defineTransformer({
The data object returned by the transformer must have the original `_id` key.
The data is stored in the `body` property of the final object.
:::danger pass
When this demo was last tested, there were errors in the NuxtJS types.
**As this affects the official examples, this is a bug in NuxtJS!**
Until the bugs are fixed, type checking should be disabled.
:::
### Custom Modules
NuxtJS modules are the main mechanism for adding transformers to the pipeline.
<details>
<summary><b>Module Details</b> (click to show)</summary>
<details><summary><b>Module Details</b> (click to show)</summary>
Due to the structure of the NuxtJS system, modules must be defined in separate
script files. The module script is expected to export a module configured with
@ -529,8 +496,8 @@ script files. The module script is expected to export a module configured with
- Add the transformer to Nuxt Content in the `content:context` hook
```js title="sheetmodule.ts (Module)"
import { resolve } from 'path';
import { defineNuxtModule } from '@nuxt/kit';
import { resolve } from 'path'
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
/* module setup method */
@ -553,7 +520,7 @@ The module must be loaded in `nuxt.config.ts` and added to the `modules` array:
```ts title="nuxt.config.ts"
// highlight-next-line
import SheetJSModule from './sheetmodule';
import SheetJSModule from './sheetmodule'
export default defineNuxtConfig({
// @ts-ignore
@ -610,7 +577,7 @@ from the script setup will be shaped like the return value from the transformer.
:::caution pass
For some older versions, parts of the Nuxt dependency tree did not support
NodeJS version 20. If the `pnpm install` step fails with a message like
NodeJS version 20. If the `yarn install` step fails with a message like
```
error @nuxt/kit@3.4.1: The engine "node" is incompatible with this module.
@ -623,18 +590,17 @@ The recommended solution is to switch to Node 18.
1) Create a stock app and install dependencies:
```bash
npx -y nuxi init -t content --packageManager pnpm --no-gitInit sheetjs-nc2 -M ,
npx -y nuxi init -t content sheetjs-nc2
cd sheetjs-nc2
npx -y pnpm install
npx -y pnpm install @nuxt/content@2 --save
npx -y pnpm install @types/node @nuxt/kit --save
npx -y yarn install
npx -y yarn add --dev @types/node
```
2) Install the SheetJS library and start the server:
<CodeBlock language="bash">{`\
npx -y pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npx -y pnpm dev`}
npx -y yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npx -y yarn dev`}
</CodeBlock>
@ -644,22 +610,22 @@ When the build finishes, the terminal will display a URL like:
> Local: http://localhost:3000/
```
The server is listening on that URL. Open the link in a web browser.
The server is listening on that URL. Open the link in a web browser.
3) Download https://docs.sheetjs.com/pres.xlsx and move to the `content` folder.
3) Download <https://sheetjs.com/pres.xlsx> and move to the `content` folder.
```bash
curl -L -o content/pres.xlsx https://docs.sheetjs.com/pres.xlsx
curl -L -o content/pres.xlsx https://sheetjs.com/pres.xlsx
```
4) Create the transformer. Two files must be saved at the root of the project:
- [`sheetformer.ts`](https://docs.sheetjs.com/nuxt/2/sheetformer.ts) (raw transformer module)
- [`sheetformer.ts`](https://docs.sheetjs.com/nuxt/3/sheetformer.ts) (raw transformer module)
- [`sheetmodule.ts`](https://docs.sheetjs.com/nuxt/3/sheetmodule.ts) (Nuxt configuration module)
```bash
curl -O https://docs.sheetjs.com/nuxt/2/sheetformer.ts
curl -O https://docs.sheetjs.com/nuxt/2/sheetmodule.ts
curl -O https://docs.sheetjs.com/nuxt/3/sheetformer.ts
curl -O https://docs.sheetjs.com/nuxt/3/sheetmodule.ts
```
After creating the source files, the module must be added to `nuxt.config.ts`:
@ -673,71 +639,71 @@ export default defineNuxtConfig({
// @ts-ignore
telemetry: false,
// highlight-end
devtools: { enabled: true },
// highlight-start
modules: [
// highlight-next-line
SheetJSModule,
'@nuxt/content'
],
devtools: { enabled: true },
// ...
content: {}
// highlight-end
});
```
Stop the dev server (<kbd>CTRL</kbd>+<kbd>C</kbd>) and run the following:
Restart the dev server by exiting the process (Control+C) and running:
```bash
npx -y nuxi clean
npx -y nuxi cleanup
npx -y pnpm run dev
npx -y nuxi typecheck
npx -y yarn run dev
```
5) Download [`pres.vue`](pathname:///nuxt/2/pres.vue) and save to `app/pages`:
Loading `http://localhost:3000/pres` should show some JSON data:
```json
{
// ...
"data": {
"_path": "/pres",
// ...
"_id": "content:pres.xlsx",
"body": [
{
"name": "Sheet1", // <-- sheet name
"data": [ // <-- array of data objects
{
"Name": "Bill Clinton",
"Index": 42
},
```
5) Download [`pres.vue`](pathname:///nuxt/3/pres.vue) and save to `pages`:
```bash
curl -o app/pages/pres.vue https://docs.sheetjs.com/nuxt/2/pres.vue
curl -o pages/pres.vue https://docs.sheetjs.com/nuxt/3/pres.vue
```
Stop the dev server (<kbd>CTRL</kbd>+<kbd>C</kbd>) and run the following:
Restart the dev server by exiting the process (Control+C) and running:
```bash
npx -y nuxi clean
npx -y nuxi cleanup
npx -y pnpm run dev
npx -y yarn run dev
```
6) From the browser window in step 2, access `/pres` from the site. For example,
if the URL in step 2 was `http://localhost:3000/`, the new page should be
`http://localhost:3000/pres`.
The browser should now display an HTML table.
This page should now display an HTML table.
7) To verify that hot loading works, open `pres.xlsx` from the `content` folder
with a spreadsheet editor.
6) To verify that hot loading works, open `pres.xlsx` from the `content` folder
with Excel or another spreadsheet editor.
Set cell `A7` to "SheetJS Dev" and set `B7` to `47`. Save the spreadsheet.
The page should automatically refresh with the new content.
8) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
9) Copy `app/pages/pres.vue` to `app/pages/index.vue`:
7) Stop the server (press `CTRL+C` in the terminal window) and run
```bash
cp app/pages/pres.vue app/pages/index.vue
```
:::info pass
In older test runs, the Nuxt starter created a default `/` page. The most recent
test did not create the index page, resulting in build errors. This step ensures
some sort of index page exists.
:::
10) Build the static site:
```bash
npx -y pnpm run generate
npx -y yarn run generate
```
This will create a static site in `.output/public`, which can be served with:
@ -746,220 +712,9 @@ This will create a static site in `.output/public`, which can be served with:
npx -y http-server .output/public
```
Access the displayed URL (typically `http://localhost:8080`) in a web browser.
To confirm that the spreadsheet data is added to the site, view the page source.
Searching for `Bill Clinton` reveals the following encoded HTML row:
```html
<tr><td>Bill Clinton</td><td>42</td></tr>
```
## Nuxt Content v3
:::danger pass
When this demo was last tested, the official Nuxt Content v3 custom transformers
and custom collections examples did not work.
:::
[ViteJS modules](/docs/demos/static/vitejs) can be used in Nuxt v3.
### Installation
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
safely imported from `nuxt.config.ts` or transformer or module scripts. As long
as the SheetJS modules are not imported in the various `.vue` pages, the library
will not be added to the final page bundle!
### Configuration
The `vite` property in the NuxtJS config is passed to ViteJS. Plugins and other
configuration options can be copied to the object. `vite.config.js` for the
[Pure Data Plugin](/docs/demos/static/vitejs#pure-data-plugin) is shown below:
```js title="vite.config.js (raw ViteJS)"
import { readFileSync } from 'fs';
import { read, utils } from 'xlsx';
import { defineConfig } from 'vite';
export default defineConfig({
assetsInclude: ['**/*.xlsx'], // xlsx file should be treated as assets
plugins: [
{ // this plugin handles ?sheetjs tags
name: "vite-sheet",
transform(_code, id) {
if(!id.match(/\?sheetjs$/)) return;
var wb = read(readFileSync(id.replace(/\?sheetjs$/, "")));
var data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
return `export default JSON.parse('${JSON.stringify(data).replace(/\\/g, "\\\\")}')`;
}
}
]
});
```
The `assetsInclude` and `plugins` properties should be added within the `vite`
property in the object passed to `defineNuxtConfig`.
:::danger pass
NuxtJS does not properly honor the `?sheetjs` tag! As a result, the transform
explicitly tests for the `.xlsx` extension.
:::
```ts title="nuxt.config.ts"
import { readFileSync } from 'fs';
import { read, utils } from 'xlsx';
export default defineNuxtConfig({
// highlight-next-line
vite: { // these options are passed to ViteJS
assetsInclude: ['**/*.xlsx'], // xlsx file should be treated as assets
plugins: [
{ // this plugin handles .xlsx
name: "vite-sheet",
transform(_code, id) {
if(!id.match(/\.xlsx$/)) return;
var wb = read(readFileSync(id.replace(/\?sheetjs$/, "")));
var data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
return `export default JSON.parse('${JSON.stringify(data).replace(/\\/g, "\\\\")}')`;
}
},
],
},
// ...
```
### Template Use
Pages can reference spreadsheets using a relative file reference. The ViteJS
plugin will transform files with the `.xlsx` extension.
```js title="Script section of .vue VueJS page"
import data from '../../pres.xlsx'; // data is an array of objects
```
In the template, `data` is an array of objects that works with `v-for`[^4]:
```xml title="Template section of .vue VueJS page"
<table>
<thead><tr><th>Name</th><th>Index</th></tr></thead><tbody>
<!-- loop over the rows of each worksheet -->
<tr v-for="row in data" v-bind:key="row.Index">
<!-- here `row` is a row object generated from sheet_to_json -->
<td>{{ row.Name }}</td>
<td>{{ row.Index }}</td>
</tr>
</table>
```
### Nuxt Content 3 Demo
1) Create a stock app and install dependencies:
```bash
npx -y nuxi init -t content --packageManager pnpm --no-gitInit sheetjs-nc3 -M ,
cd sheetjs-nc3
npx -y pnpm install
```
2) Install the SheetJS library and start the server:
<CodeBlock language="bash">{`\
npx -y pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npx -y pnpm dev`}
</CodeBlock>
When the build finishes, the terminal will display a URL like:
```
> Local: http://localhost:3000/
```
The server is listening on that URL. Open the link in a web browser.
3) Download https://docs.sheetjs.com/pres.xlsx and move to the root folder:
```bash
curl -o pres.xlsx https://docs.sheetjs.com/pres.xlsx
```
4) Replace `nuxt.config.ts` with the following codeblock:
```ts title="nuxt.config.ts"
import { readFileSync } from 'fs';
import { read, utils } from 'xlsx';
export default defineNuxtConfig({
vite: { // these options are passed to ViteJS
assetsInclude: ['**/*.xlsx'], // xlsx file should be treated as assets
plugins: [
{ // this plugin handles .xlsx
name: "vite-sheet",
transform(_code, id) {
if(!id.match(/\.xlsx$/)) return;
var wb = read(readFileSync(id.replace(/\?sheetjs$/, "")));
var data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
return `export default JSON.parse('${JSON.stringify(data).replace(/\\/g, "\\\\")}')`;
}
},
],
},
modules: [
'@nuxt/content',
],
devtools: { enabled: true },
});
```
5) Create a new file `app.vue` with the following contents:
```html title="app.vue (create new file)"
<script setup>
import data from '../../pres.xlsx'
</script>
<template>
<table><thead><tr><th>Name</th><th>Index</th></tr></thead><tbody>
<tr v-for="row in data" v-bind:key="row.Index">
<td>{{ row.Name }}</td>
<td>{{ row.Index }}</td>
</tr>
</tbody></table>
</template>
```
6) Refresh the browser window. This page should now display an HTML table.
7) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
8) Build the static site:
```bash
npx -y pnpm run generate
```
This will create a static site in `.output/public`, which can be served with:
```bash
npx -y http-server .output/public
```
Access the displayed URL (typically `http://localhost:8080`) in a web browser.
To confirm that the spreadsheet data is added to the site, view the page source.
Searching for `Bill Clinton` reveals the following encoded HTML row:
```html
<tr><td>Bill Clinton</td><td>42</td></tr>
```
Accessing `http://localhost:8080/pres` will show the page contents. Verifying
the static nature is trivial: make another change in Excel and save. The page
will not change.
[^1]: See [`readFile` in "Reading Files"](/docs/api/parse-options)
[^2]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)

@ -49,11 +49,10 @@ flowchart LR
This demo was tested in the following environments:
| SvelteJS | Kit | Date |
|:----------|:---------|:-----------|
| `5.27.2` | `2.20.7` | 2025-04-17 |
| `4.2.19` | `2.5.10` | 2025-04-17 |
| `3.59.2` | `1.30.4` | 2025-04-17 |
| Svelte | Kit | Date |
|:----------------|:---------|:-----------|
| `4.2.8` | `1.27.6` | 2023-12-04 |
| `5.0.0-next.17` | `1.27.6` | 2023-12-04 |
:::
@ -179,75 +178,39 @@ a simple HTML table without any reference to the existing spreadsheet file!
## Complete Example
:::caution pass
When this demo was last tested, SvelteKit required NodeJS major version 20.
:::
### Initial Setup
1) Create a new site:
```bash
mkdir -p sheetjs-svelte
cd sheetjs-svelte
npx sv create
```
When prompted:
- `Where would you like your project to be created?`: press <kbd>Enter</kbd>
- `Which template would you like?` select `SvelteKit minimal`
- `Add type checking with TypeScript?` select `Yes, using JavaScript with JSDoc`
- `What would you like to add to your project?` press <kbd>Enter</kbd> (do not select options)
- `Which package manager do you want to install dependencies with?`: select `npm` and press <kbd>Enter</kbd>
<details>
<summary><b>Older SvelteJS versions</b> (click to show)</summary>
The following commands create SvelteJS projects for older versions:
**SvelteJS 3.x**
```bash
npm create svelte@4 sheetjs-svelte
```
**SvelteJS 4.x**
```bash
npm create svelte@6 sheetjs-svelte
npm create svelte@latest sheetjs-svelte
```
When prompted:
- `Which Svelte app template?` select `Skeleton Project`
- `Add type checking with TypeScript?` select `Yes, using JavaScript with JSDoc`
- `Select additional options` press <kbd>Enter</kbd> (do not select options)
- `Select additional options` press Enter (do not select options)
After creating the project, enter the project folder and install dependencies:
:::note pass
To test the Svelte 5 beta, select "Try out Svelte 5 beta" before pressing Enter.
:::
2) Enter the project folder and install dependencies:
```bash
cd sheetjs-svelte
npm i
```
</details>
2) Install dependencies:
```bash
npm i
```
3) Fetch the example file [`pres.xlsx`](https://docs.sheetjs.com/pres.xlsx) and
move to a `data` subdirectory in the root of the project:
3) Fetch the example file [`pres.xlsx`](https://sheetjs.com/pres.xlsx) and move
to a `data` subdirectory in the root of the project:
```bash
mkdir -p data
curl -Lo data/pres.xlsx https://docs.sheetjs.com/pres.xlsx
curl -Lo data/pres.xlsx https://sheetjs.com/pres.xlsx
```
4) Install the SheetJS library:
@ -256,7 +219,7 @@ curl -Lo data/pres.xlsx https://docs.sheetjs.com/pres.xlsx
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
5) Replace the contents of `vite.config.js` with the following codeblock:
5) Replace the contents of `vite.config.js` with the following:
```js title="vite.config.js"
import { sveltekit } from '@sveltejs/kit/vite';
@ -286,8 +249,7 @@ declare global {
}
```
7) Replace the contents of `src/routes/+page.server.js` with the following code.
Create the file if it does not exist.
7) Replace the contents of `src/routes/+page.server.js` with following:
```js title="src/routes/+page.server.js"
import b64 from "../../data/pres.xlsx";
@ -304,8 +266,9 @@ export async function load({ params }) {
}
```
8) Replace the contents of `src/routes/+page.svelte` with the following code.
Create the file if it does not exist.
If the file does not exist, create a new file.
8) Replace the contents of `src/routes/+page.svelte` with the following:
```html title="src/routes/+page.svelte"
<script>
@ -342,14 +305,6 @@ observe that the data from the spreadsheet is displayed in the page.
11) In the spreadsheet, set cell A7 to `SheetJS Dev` and cell B7 to `47`. Save
the file. After saving, the browser should automatically refresh with new data.
:::caution pass
**Live reloading may not work in newer projects!**
If the page does not reload, manually refresh the page.
:::
### Static Site
12) Stop the development server and install the static adapter:

@ -36,7 +36,7 @@ flowchart LR
aoo --> |index.astro\ntemplate body| html
```
:::danger Telemetry
:::warning Telemetry
AstroJS enables telemetry by default. The tool has an option to disable telemetry:
@ -50,16 +50,10 @@ npx astro telemetry disable
This demo was tested in the following environments:
| AstroJS | Template | Date |
|:--------|:-----------------|:-----------|
| `4.6.1` | Starlight 0.22.4 | 2025-01-07 |
| `5.1.3` | Starlight 0.30.5 | 2025-01-07 |
In previous test runs, this demo successfully ran using older AstroJS versions:
| AstroJS | Template |
|:--------|:-----------------|
| `3.6.5` | Starlight 0.14.0 |
| AstroJS | Date |
|:---------------|:-----------|
| `3.6.4` | 2023-12-04 |
| `4.0.0-beta.4` | 2023-12-04 |
:::
@ -75,7 +69,7 @@ AstroJS has introduced a number of breaking changes in minor releases.
:::info pass
This demo uses ["Base64 Loader"](/docs/demos/static/vitejs#base64-plugin)
This demo uses ["Base64 Loader"](/docs/demos/static/vitejs#base64-loader)
from the ViteJS demo.
The ViteJS demo used the query `?b64` to identify files. To play nice with
@ -116,7 +110,7 @@ export default defineConfig({
#### Types
For VSCodium integration, types can be specified in `src/env.d.ts`.
For VSCode and VSCodium integration, types can be specified in `src/env.d.ts`.
This data loader returns Base64 strings:
@ -218,13 +212,10 @@ cd sheetjs-astro
:::note pass
To test an older version of AstroJS, install the specific version of `astro` and
a supported starter template after creating the project.
For major version 4, Starlight must be version `0.22.4`:
To test the AstroJS 4 beta release, run the following command:
```bash
npm install --force astro@4 @astrojs/starlight@0.22.4
npm install --force @astrojs/starlight@^0.14.0 astro@4.0.0-beta.4
```
The version can be verified by running:
@ -233,29 +224,13 @@ The version can be verified by running:
npx astro --version
```
**When using older versions of AstroJS, the sidebar must be removed!**
In `astro.config.mjs`, the `sidebar` property in the config must be removed:
```js title="astro.config.mjs (snippet)"
export default defineConfig({
integrations: [
starlight({
// ...
// remove the `sidebar` array if it exists in the object
sidebar: [
// remove this entire array
],
}),
```
:::
2) Fetch the example file [`pres.numbers`](https://docs.sheetjs.com/pres.numbers):
2) Fetch the example file [`pres.numbers`](https://sheetjs.com/pres.numbers):
```bash
mkdir -p src/data
curl -Lo src/data/pres.numbers https://docs.sheetjs.com/pres.numbers
curl -Lo src/data/pres.numbers https://sheetjs.com/pres.numbers
```
3) Install the SheetJS library:
@ -276,7 +251,7 @@ declare module '*.xlsx' { const data: string; export default data; }
- At the top of the script, import `readFileSync`:
```js title="astro.config.mjs (add higlighted lines)"
```js title="astro.config.mjs"
// highlight-start
/* import `readFileSync` at the top of the script*/
import { readFileSync } from 'fs';
@ -286,7 +261,7 @@ import { defineConfig } from 'astro/config';
- In the object argument to `defineConfig`, add a `vite` section:
```js title="astro.config.mjs (add highlighted lines)"
```js title="astro.config.mjs"
export default defineConfig({
// highlight-start
/* this vite section should be added as a property of the object */
@ -345,11 +320,12 @@ AstroJS will place the generated site in the `dist` subfolder.
9) Start a web server to host the static site:
```bash
npx -y http-server dist
npx http-server dist
```
Open a web browser and access the displayed URL ( `http://localhost:8080` ).
View the page source and confirm that the raw data is stored in an HTML table.
View the page source and confirm that no JS was added to the page. It only
contains the content from the file in an HTML table.
:::caution pass

@ -1,5 +1,5 @@
---
title: Sheets on the Go with React Native
title: React Native
sidebar_label: React Native
description: Build data-intensive mobile apps with React Native. Seamlessly integrate spreadsheets into your app using SheetJS. Securely process and generate Excel files in the field.
pagination_prev: demos/static/index
@ -9,6 +9,8 @@ sidebar_custom_props:
summary: React + Native Rendering
---
# Sheets on the Go with React Native
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
@ -67,9 +69,7 @@ imported from any component or script in the app.
For simplicity, this demo uses an "Array of Arrays"[^2] as the internal state.
<table>
<thead><tr><th>Spreadsheet</th><th>Array of Arrays</th></tr></thead>
<tbody><tr><td>
<table><thead><tr><th>Spreadsheet</th><th>Array of Arrays</th></tr></thead><tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
@ -97,7 +97,7 @@ _Complete State_
The complete state is initialized with the following snippet:
```js title="State variables"
```js
const [data, setData] = useState([
"SheetJS".split(""),
[5,4,3,3,7,9,5],
@ -112,7 +112,7 @@ const [widths, setWidths] = useState(Array.from({length:7}, () => 20));
Starting from a SheetJS worksheet object, `sheet_to_json`[^3] with the `header`
option can generate an array of arrays:
```js title="Updating state from a workbook"
```js
/* assuming `wb` is a SheetJS workbook */
function update_state(wb) {
/* convert first worksheet to AOA */
@ -133,7 +133,7 @@ _Calculating Column Widths_
Column widths can be calculated by walking each column and calculating the max
data width. Using the array of arrays:
```js title="Calculating column widths"
```js
/* this function takes an array of arrays and generates widths */
function make_width(aoa) {
/* walk each row */
@ -154,7 +154,7 @@ function make_width(aoa) {
`aoa_to_sheet`[^4] builds a SheetJS worksheet object from the array of arrays:
```js title="Exporting state data to a workbook"
```js
/* generate a SheetJS workbook from the state */
function export_state() {
/* convert AOA back to worksheet */
@ -170,19 +170,13 @@ function export_state() {
### Displaying Data
React Native does not ship with a component for displaying tabular data.
`react-native-table-component` is a simple UI component designed for legacy
versions of React Native.
[`react-native-tabeller`](https://git.sheetjs.com/asadbek064/react-native-tabeller)
uses a similar API and follows modern React Native design patterns.
The demos uses `react-native-table-component` to display the first worksheet.
The demos use components similar to the example below:
```jsx title="Example JSX for displaying data"
```jsx
import { ScrollView } from 'react-native';
import { Table, Row, Rows, TableWrapper } from 'react-native-tabeller';
import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
(
{/* Horizontal scroll */}
@ -213,11 +207,11 @@ row. This neatly skips the first header row.
React Native versions starting from `0.72.0`[^5] support binary data in `fetch`.
This snippet downloads and parses https://docs.sheetjs.com/pres.xlsx:
This snippet downloads and parses <https://sheetjs.com/pres.xlsx>:
```js
/* fetch data into an ArrayBuffer */
const ab = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
const ab = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
/* parse data */
const wb = XLSX.read(ab);
```
@ -228,173 +222,66 @@ const wb = XLSX.read(ab);
This demo was tested in the following environments:
**Simulators**
| OS | Device | RN | Dev Platform | Date |
|:-----------|:------------------|:---------|:-------------|:-----------|
| Android 34 | Pixel 3a | `0.73.6` | `darwin-x64` | 2024-03-13 |
| iOS 17.4 | iPhone 15 Pro Max | `0.73.6` | `darwin-x64` | 2024-03-13 |
| Android 34 | Pixel 3a | `0.73.5` | `win10-x64` | 2024-03-05 |
**Real Devices**
| OS | Device | RN | Date |
|:-----------|:------------------|:---------|:-----------|
| iOS 15.6 | iPhone 13 Pro Max | `0.76.8` | 2025-03-26 |
| Android 34 | NVIDIA Shield | `0.76.8` | 2025-03-26 |
**Simulators**
| OS | Device | RN | Dev Platform | Date |
|:-----------|:--------------------|:---------|:-------------|:-----------|
| Android 34 | Pixel 3a | `0.76.8` | `darwin-arm` | 2025-03-26 |
| iOS 18.3 | iPhone 16 Pro | `0.76.8` | `darwin-arm` | 2025-03-26 |
| Android 35 | Pixel 9 Pro XL | `0.79.2` | `win11-x64` | 2025-06-08 |
| Android 35 | Pixel 9 | `0.76.5` | `linux-x64` | 2025-01-02 |
| iOS 15.1 | iPhone 12 Pro Max | `0.73.6` | 2024-03-13 |
| Android 29 | NVIDIA Shield | `0.73.6` | 2024-03-13 |
:::
:::caution
**Before testing this demo, follow the official React Native CLI Guide!**[^1]
Make sure you can run a basic test app on your phone/simulator before continuing!
:::
0) Install React Native dependencies
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
On the Steam Deck, JDK17 was installed using `pacman`:
```bash
sudo pacman -Syu jdk17-openjdk
```
[The Android Studio tarball](https://developer.android.com/studio) was
downloaded and extracted. After extracting:
```bash
cd ./android-studio/bin
./studio.sh
```
In Android Studio, select "SDK Manager" and switch to the "SDK Tools" tab. Check
"Show Package Details" and install "Android SDK Command-line Tools (latest)".
When this demo was last tested, the following environment variables were used:
```bash
export ANDROID_HOME=~/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
```
</details>
1) Create project:
```bash
npx -y @react-native-community/cli@18 init SheetJSRNFetch --version="0.79.2"
npx -y react-native@0.73.6 init SheetJSRNFetch --version="0.73.6"
```
On macOS, if prompted to install `CocoaPods`, press <kbd>Y</kbd>
:::info pass
If you were prompted to install CocoaPods, verify it worked by opening a new
terminal and running:
```bash
pod --version
```
If you see "command not found", install it via `brew`:
```bash
brew install cocoapods
```
:::
:::note pass
Older versions of this demo used the `react-native` package. The `init` command
was officially deprecated.
React Native now recommends using `@react-native-community/cli`. The versioning
scheme is fundamentally different from `react-native`.[^6]
:::
2) Install shared dependencies:
<CodeBlock language="bash">{`\
cd SheetJSRNFetch
curl -LO https://docs.sheetjs.com/logo.png
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm i -S https://cdn.sheetjs.com/react-native-tabeller-0.1.0/react-native-tabeller-0.1.0.tgz`}
npm i -S react-native-table-component@1.2.0 @types/react-native-table-component`}
</CodeBlock>
:::note pass
In PowerShell, the command may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'LO'.
```
`curl.exe` must be invoked directly:
<CodeBlock language="bash">{`\
cd SheetJSRNFetch
curl.exe -LO https://docs.sheetjs.com/logo.png
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm i -S https://cdn.sheetjs.com/react-native-tabeller-0.1.0/react-native-tabeller-0.1.0.tgz`}
</CodeBlock>
:::
3) Download and replace [`App.tsx`](pathname:///reactnative/App.tsx):
3) Download [`App.tsx`](pathname:///reactnative/App.tsx) and replace:
```bash
curl -LO https://docs.sheetjs.com/reactnative/App.tsx
```
**Android Testing**
4) Install or switch to Java 17[^6]
:::note pass
In PowerShell, the command may fail with a parameter error:
When the demo was last tested on macOS, `java -version` displayed the following:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'LO'.
```
`curl.exe` must be invoked directly:
```bash
curl.exe -LO https://docs.sheetjs.com/reactnative/App.tsx
openjdk version "17.0.10" 2024-01-16
OpenJDK Runtime Environment Temurin-17.0.10+7 (build 17.0.10+7)
OpenJDK 64-Bit Server VM Temurin-17.0.10+7 (build 17.0.10+7, mixed mode)
```
:::
**Android Testing**
4) Install or switch to Java 17[^7]
5) Start the Android emulator:
```bash
npx react-native run-android
```
:::info pass
On Linux, the command may silently stall. It is strongly recommended to launch
the interactive CLI tool:
```bash
npx react-native start
```
Once the dev server is ready, the terminal will display a few options. Press `a`
to run on Android.
:::
:::caution pass
If the initial launch fails with an error referencing the emulator, manually
@ -411,74 +298,6 @@ This error can be resolved by installing and switching to the requested version.
:::
:::caution pass
When this demo was last tested on Windows 11, there was a Gradle error
mentioning a "temporary workspace":
```
> java.io.UncheckedIOException: Could not move temporary workspace (C:\Users\sheetjs\Documents\SheetJSRNFetch\android\.gradle\8.10.2\dependencies-accessors\569c8b261a8a714d7731d5f568e0e5c05babae10-4756c477-c60a-4328-ba8c-e12cc53b4ed0) to immutable location (C:\Users\sheetjs\Documents\SheetJSRNFetch\android\.gradle\8.10.2\dependencies-accessors\569c8b261a8a714d7731d5f568e0e5c05babae10)
```
**This is a known bug in the version of Gradle used by React Native 0.76.5!**
The Gradle version should be set to `8.11.1` in `gradle-wrapper.properties`:
```text title="android/gradle/wrapper/gradle-wrapper.properties (change line)"
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
```
:::
:::caution pass
When this demo was last tested on Linux, the process failed to launch the emulator:
<pre>
<b {...y}>warn</b> Please launch an emulator manually or connect a device. Otherwise app may fail to launch.
</pre>
**This is a known bug in React Native!**
If an emulator is installed, run the following command:
```bash
npx react-native doctor
```
Under `Android`, there will be one error:
<pre>
<span {...gr}>Android</span> {`\n`}
{` `}<span {...r}></span> Adb - No devices and/or emulators connected. Please create emulator with Android Studio or connect Android device.
</pre>
Press `f` and a list of available emulators will be shown. Select the emulator
(typically the last line) and press Enter.
<pre>
<span {...g}></span> <b>Select the device / emulator you want to use</b> <span {...gr}></span> <b>Emulator</b> <span {...g}>Pixel_3a_API_34_extension_level_7_x86_64</span> (disconnected)
</pre>
The text in green is the name of the virtual device
(`Pixel_3a_API_34_extension_level_7_x86_64` in this example).
Run the following command to manually start the emulator:
```bash
$ANDROID_HOME/tools/emulator -avd Pixel_3a_API_34_extension_level_7_x86_64
```
(replace `Pixel_3a_API_34_extension_level_7_x86_64` with the name of the virtual device)
To confirm React Native detects the emulator, run the following command again:
```bash
npx react-native doctor
```
:::
6) When opened, the app should look like the "Before" screenshot below. After
tapping "Import data from a spreadsheet", verify that the app shows new data:
@ -497,7 +316,7 @@ tapping "Import data from a spreadsheet", verify that the app shows new data:
**iOS Testing**
:::danger pass
:::warning pass
**iOS testing can only be performed on Apple hardware running macOS!**
@ -547,12 +366,6 @@ If the device asks to allow USB debugging, tap "Allow".
npx react-native run-android
```
:::note pass
Depending on the device, it may take up to 10 seconds for app to update.
:::
**iOS Device Testing**
13) Connect an iOS device using a USB cable.
@ -561,12 +374,11 @@ If the device asks to trust the computer, tap "Trust" and enter the passcode.
14) Close any Android / iOS emulators.
15) Enable developer code signing certificates[^8].
15) Enable developer code signing certificates[^7].
<details open>
<summary><b>Enabling Code Signing</b> (click to show)</summary>
<details open><summary><b>Enabling Code Signing</b> (click to show)</summary>
These instructions were verified against Xcode 16.2.
These instructions were verified against Xcode 15.3.
A) Open the included iOS workspace in Xcode:
@ -598,29 +410,6 @@ brew install ios-deploy
npx react-native run-ios
```
:::note pass
If the device is not detected, the terminal will print
```
info No booted devices or simulators found. Launching first available simulator...
```
This may happen if the device goes to sleep while connected. Disconnect and
reconnect the device before trying again.
:::
:::info pass
In some test runs, the app requested for local network access:
> "SheetJSRNFetch" would like to find and connect to devices on your local network.
Local network access is not required for the demo. Select "Don't Allow".
:::
:::caution pass
In some tests, the app failed to run on the device due to "Untrusted Developer":
@ -629,7 +418,7 @@ In some tests, the app failed to run on the device due to "Untrusted Developer":
Your device management settings do not allow apps from developer ... on this iPhone. You can allow using these apps in Settings.
```
These instructions were verified against iOS 15.6.
These instructions were verified against iOS 15.1.
A) Open the Settings app and select "General" > "VPN & Device Management".
@ -752,7 +541,7 @@ Click on the icon and select the real device from the list.
## Local Files
:::danger pass
:::warning pass
React Native does not provide a native file picker or a method for reading and
writing data from documents on the devices. A third-party library must be used.
@ -777,17 +566,16 @@ The following table lists tested file plugins. "OS" lists tested platforms
Due to privacy concerns, apps must request file access. There are special APIs
for accessing data and are subject to change in future platform versions.
<details>
<summary><b>Technical Details</b> (click to show)</summary>
<details><summary><b>Technical Details</b> (click to show)</summary>
**iOS**
iOS applications typically require two special settings in `Info.plist`:
- `UIFileSharingEnabled`[^9] allows users to use files written by the app. A
- `UIFileSharingEnabled`[^8] allows users to use files written by the app. A
special folder will appear in the "Files" app.
- `LSSupportsOpeningDocumentsInPlace`[^10] allows the app to open files without
- `LSSupportsOpeningDocumentsInPlace`[^9] allows the app to open files without
creating a local copy.
Both settings must be set to `true`:
@ -910,8 +698,7 @@ is the continuation of other libraries that date back to 2016.
The `ascii` type returns an array of numbers corresponding to the raw bytes.
A `Uint8Array` from the data is compatible with the `buffer` type.
<details>
<summary><b>Reading and Writing snippets</b> (click to hide)</summary>
<details><summary><b>Reading and Writing snippets</b> (click to hide)</summary>
_Reading Data_
@ -992,8 +779,7 @@ is a filesystem API that uses modern iOS and Android development patterns.
The `base64` encoding returns strings compatible with the `base64` type:
<details>
<summary><b>Reading and Writing snippets</b> (click to hide)</summary>
<details><summary><b>Reading and Writing snippets</b> (click to hide)</summary>
_Reading Data_
@ -1053,8 +839,7 @@ The [`expo-document-picker`](#expo-document-picker) snippet makes a local copy.
The `EncodingType.Base64` encoding is compatible with `base64` type.
<details>
<summary><b>Reading and Writing snippets</b> (click to hide)</summary>
<details><summary><b>Reading and Writing snippets</b> (click to hide)</summary>
_Reading Data_
@ -1118,29 +903,28 @@ try {
This demo was tested in the following environments:
**Real Devices**
| OS | Device | RN | Date |
|:-----------|:------------------|:---------|:-----------|
| iOS 15.6 | iPhone 13 Pro Max | `0.76.5` | 2025-01-05 |
| Android 34 | NVIDIA Shield | `0.76.5` | 2025-01-05 |
**Simulators**
| OS | Device | RN | Dev Platform | Date |
|:-----------|:------------------|:---------|:-------------|:-----------|
| Android 34 | Pixel 3a | `0.76.5` | `darwin-arm` | 2025-01-05 |
| iOS 18.2 | iPhone 16 Pro | `0.76.5` | `darwin-arm` | 2025-01-05 |
| Android 35 | Pixel 9 Pro XL | `0.76.5` | `win11-x64` | 2025-06-08 |
| Android 35 | Pixel 9 | `0.76.5` | `linux-x64` | 2025-01-02 |
| Android 34 | Pixel 3a | `0.73.6` | `darwin-x64` | 2024-03-31 |
| iOS 17.4 | iPhone 15 Pro Max | `0.73.6` | `darwin-x64` | 2024-03-31 |
| Android 34 | Pixel 3a | `0.73.6` | `win10-x64` | 2024-03-31 |
**Real Devices**
| OS | Device | RN | Date |
|:-----------|:------------------|:---------|:-----------|
| iOS 15.5 | iPhone 13 Pro Max | `0.73.6` | 2024-03-31 |
| Android 29 | NVIDIA Shield | `0.73.6` | 2024-03-31 |
:::
:::danger pass
:::warning pass
There are many moving parts and pitfalls with React Native apps. It is strongly
recommended to follow the official React Native tutorials for iOS and Android
before approaching this demo.[^11] Details including Android Virtual Device
before approaching this demo.[^10] Details including Android Virtual Device
configuration are not covered here.
:::
@ -1152,10 +936,10 @@ This example tries to separate the library-specific functions.
1) Create project:
```bash
npx -y @react-native-community/cli@15 init SheetJSRN --version="0.76.5"
npx react-native init SheetJSRN --version="0.73.6"
```
On macOS, if prompted to install `CocoaPods`, press <kbd>Y</kbd>
On macOS, if prompted to install `CocoaPods`, press `y`.
2) Install shared dependencies:
@ -1163,50 +947,15 @@ On macOS, if prompted to install `CocoaPods`, press <kbd>Y</kbd>
cd SheetJSRN
curl -LO https://docs.sheetjs.com/logo.png
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm i -S https://cdn.sheetjs.com/react-native-tabeller-0.1.0/react-native-tabeller-0.1.0.tgz react-native-document-picker@9.3.1`}
npm i -S react-native-table-component@1.2.0 react-native-document-picker@9.1.1`}
</CodeBlock>
:::note pass
In PowerShell, the command may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'LO'.
```
`curl.exe` must be invoked directly:
<CodeBlock language="bash">{`\
cd SheetJSRN
curl.exe -LO https://docs.sheetjs.com/logo.png
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm i -S https://cdn.sheetjs.com/react-native-tabeller-0.1.0/react-native-tabeller-0.1.0.tgz react-native-document-picker@9.3.1`}
</CodeBlock>
:::
3) Download [`index.js`](pathname:///mobile/index.js) and replace:
```bash
curl -LO https://docs.sheetjs.com/mobile/index.js
```
:::note pass
In PowerShell, the command may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'LO'.
```
`curl.exe` must be invoked directly:
```bash
curl.exe -LO https://docs.sheetjs.com/mobile/index.js
```
:::
4) Start the Android emulator:
```bash
@ -1219,37 +968,18 @@ The app should look like the following screenshot:
:::caution pass
In some test runs on Windows, the build failed with an error:
When this demo was last tested on Windows, the build failed with an error:
```
> Failed to apply plugin 'com.android.internal.application'.
> Android Gradle plugin requires Java 17 to run. You are currently using Java 11.
```
Java 17 must be installed[^12] and the `JAVA_HOME` environment variable must
Java 17 must be installed[^11] and the `JAVA_HOME` environment variable must
point to the Java 17 location.
:::
:::caution pass
When this demo was last tested on Windows 11, there was a Gradle error
mentioning a "temporary workspace":
```
> java.io.UncheckedIOException: Could not move temporary workspace (C:\Users\sheetjs\Documents\SheetJSRNFetch\android\.gradle\8.10.2\dependencies-accessors\569c8b261a8a714d7731d5f568e0e5c05babae10-4756c477-c60a-4328-ba8c-e12cc53b4ed0) to immutable location (C:\Users\sheetjs\Documents\SheetJSRNFetch\android\.gradle\8.10.2\dependencies-accessors\569c8b261a8a714d7731d5f568e0e5c05babae10)
```
**This is a known bug in the version of Gradle used by React Native 0.76.5!**
The Gradle version should be set to `8.11.1` in `gradle-wrapper.properties`:
```text title="android/gradle/wrapper/gradle-wrapper.properties (change line)"
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
```
:::
Stop the dev server and close the React Native Metro NodeJS window.
**File Integration**
@ -1262,13 +992,13 @@ Stop the dev server and close the React Native Metro NodeJS window.
Install `react-native-blob-util` dependency:
```bash
npm i -S react-native-blob-util@0.22.2
npm i -S react-native-blob-util@0.19.8
```
Add the highlighted lines to `index.js`:
```js title="index.js (add highlighted lines)"
import { Table, Row, Rows, TableWrapper } from 'react-native-tabeller';
```js title="index.js"
import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
// highlight-start
import { read, write } from 'xlsx';
@ -1310,13 +1040,13 @@ const make_width = ws => {
Install `react-native-file-access` dependency:
```bash
npm i -S react-native-file-access@3.1.1
npm i -S react-native-file-access@3.0.7
```
Add the highlighted lines to `index.js`:
```js title="index.js"
import { Table, Row, Rows, TableWrapper } from 'react-native-tabeller';
import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
// highlight-start
import { read, write } from 'xlsx';
@ -1365,25 +1095,10 @@ If prompted to install Expo CLI integration, choose No.
:::
:::info pass
On Windows, `npx install-expo-modules` will throw errors related to CocoaPods:
```
Uncaught Error CocoaPodsError: Command `pod install` failed.
└─ Cause: spawn pod ENOENT
'pod' is not recognized as an internal or external command,
operable program or batch file.
```
These errors can be ignored.
:::
Add the highlighted lines to `index.js`:
```js title="index.js"
import { Table, Row, Rows, TableWrapper } from 'react-native-tabeller';
import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
// highlight-start
import { read, write } from 'xlsx';
@ -1481,7 +1196,7 @@ npx react-native doctor
:::
8) Download https://docs.sheetjs.com/pres.numbers and open the Downloads folder.
8) Download <https://sheetjs.com/pres.numbers> and open the Downloads folder.
9) Click and drag `pres.numbers` from the Downloads folder into the simulator.
@ -1539,7 +1254,7 @@ npx xlsx-cli sheetjsw.xlsx
**iOS Testing**
:::danger pass
:::warning pass
**iOS testing can only be performed on Apple hardware running macOS!**
@ -1561,7 +1276,7 @@ cd ios; pod install; cd -
npx react-native run-ios
```
17) Download https://docs.sheetjs.com/pres.numbers and open the Downloads folder.
17) Download <https://sheetjs.com/pres.numbers> and open the Downloads folder.
18) In the simulator, click the Home icon to return to the home screen.
@ -1637,7 +1352,7 @@ If the device asks to allow USB debugging, tap "Allow".
npx react-native run-android
```
30) Download https://docs.sheetjs.com/pres.numbers on the device.
30) Download <https://sheetjs.com/pres.numbers> on the device.
31) Switch back to the "SheetJSRN" app.
@ -1726,7 +1441,7 @@ npx react-native run-ios
If the build fails, some troubleshooting notes are included in the "iOS Device
Testing" part of the [Fetch Demo](#fetch-demo) (step 17).
41) Download https://docs.sheetjs.com/pres.numbers on the device.
41) Download <https://sheetjs.com/pres.numbers> on the device.
42) Switch back to the "SheetJSRN" app.
@ -1752,15 +1467,14 @@ Swipe left until the "Numbers" app icon appears and tap the app icon.
The Numbers app will load the spreadsheet, confirming that the file is valid.
[^1]: Follow the ["Set Up Your Environment" guide](https://reactnative.dev/docs/set-up-your-environment) and select the appropriate "Development OS".
[^1]: Follow the ["React Native CLI Quickstart"](https://reactnative.dev/docs/environment-setup) and select the appropriate "Development OS".
[^2]: See ["Array of Arrays" in the API reference](/docs/api/utilities/array#array-of-arrays)
[^3]: See ["Array Output" in "Utility Functions"](/docs/api/utilities/array#array-output)
[^4]: See ["Array of Arrays Input" in "Utility Functions"](/docs/api/utilities/array#array-of-arrays-input)
[^5]: React-Native commit [`5b597b5`](https://github.com/facebook/react-native/commit/5b597b5ff94953accc635ed3090186baeecb3873) added the final piece required for `fetch` support. It is available in official releases starting from `0.72.0`.
[^6]: See [the compatibility table](https://github.com/react-native-community/cli/blob/main/README.md#compatibility) in the CLI project repository to determine which version of `@react-native-community/cli` is required for a given `react-native` version.
[^7]: When the demo was last tested, the Temurin distribution of Java 17 was installed through the macOS Brew package manager by running `brew install temurin17`. [Direct downloads are available at `adoptium.net`](https://adoptium.net/temurin/releases/?version=17)
[^8]: See ["Running On Device"](https://reactnative.dev/docs/running-on-device) in the React Native documentation
[^9]: See [`UIFileSharingEnabled`](https://developer.apple.com/documentation/bundleresources/information_property_list/uifilesharingenabled) in the Apple Developer Documentation.
[^10]: See [`LSSupportsOpeningDocumentsInPlace`](https://developer.apple.com/documentation/bundleresources/information_property_list/lssupportsopeningdocumentsinplace) in the Apple Developer Documentation.
[^11]: Follow the ["React Native CLI Quickstart"](https://reactnative.dev/docs/environment-setup) for Android (and iOS, if applicable)
[^12]: See the [JDK Archive](https://jdk.java.net/archive/) for Java 17 JDK download links.
[^5]: React-Native commit [`5b597b5`](https://github.com/facebook/react-native/commit/5b597b5ff94953accc635ed3090186baeecb3873) added the final piece required for `fetch` support. It landed in version `0.72.0-rc.1` and is available in official releases starting from `0.72.0`.
[^6]: When the demo was last tested, the Temurin distribution of Java 17 was installed through the macOS Brew package manager by running `brew install temurin17`. [Direct downloads are available at `adoptium.net`](https://adoptium.net/temurin/releases/?version=17)
[^7]: See ["Running On Device"](https://reactnative.dev/docs/running-on-device) in the React Native documentation
[^8]: See [`UIFileSharingEnabled`](https://developer.apple.com/documentation/bundleresources/information_property_list/uifilesharingenabled) in the Apple Developer Documentation.
[^9]: See [`LSSupportsOpeningDocumentsInPlace`](https://developer.apple.com/documentation/bundleresources/information_property_list/lssupportsopeningdocumentsinplace) in the Apple Developer Documentation.
[^10]: Follow the ["React Native CLI Quickstart"](https://reactnative.dev/docs/environment-setup) for Android (and iOS, if applicable)
[^11]: See the [JDK Archive](https://jdk.java.net/archive/) for Java 17 JDK download links.

@ -13,7 +13,7 @@ import CodeBlock from '@theme/CodeBlock';
export const g = {style: {color:"green"}};
export const r = {style: {color:"red"}};
export const y = {style: {color:"gold"}};
export const y = {style: {color:"yellow"}};
[NativeScript](https://nativescript.org/) is a mobile app framework. It builds
iOS and Android apps that use JavaScript for describing layouts and events.
@ -25,8 +25,7 @@ This demo uses NativeScript and SheetJS to process and generate spreadsheets.
We'll explore how to load SheetJS in a NativeScript app; parse and generate
spreadsheets stored on the device; and fetch and parse remote files.
The ["Complete Example"](#complete-example) creates an app that looks like the
screenshots below:
The "Complete Example" creates an app that looks like the screenshots below:
<table><thead><tr>
<th><a href="#complete-example">iOS</a></th>
@ -52,25 +51,16 @@ Angular and TypeScript is assumed.
This demo was tested in the following environments:
**Real Devices**
| OS | Device | NS | Date |
|:-----------|:--------------------|:---------|:-----------|
| Android 30 | NVIDIA Shield | `8.9.2` | 2025-05-06 |
| iOS 15.1 | iPad Pro | `8.9.2` | 2025-05-06 |
**Simulators**
| OS | Device | NS | Dev Platform | Date |
|:-----------|:--------------------|:---------|:-------------|:-----------|
| Android 35 | Pixel 9 Pro XL | `8.9.2` | `darwin-x64` | 2025-05-06 |
| iOS 18.4 | iPhone 16 Pro Max | `8.9.2` | `darwin-x64` | 2025-05-06 |
| Android 35 | Pixel 9 | `8.9.2` | `win11-x64` | 2025-06-08 |
| Android 35 | Pixel 9 | `8.8.3` | `linux-x64` | 2025-01-02 |
| OS | Type | Device | NS | Date |
|:-----------|:-----|:--------------------|:---------|:-----------|
| Android 34 | Sim | Pixel 3a | `8.6.1` | 2023-12-04 |
| iOS 17.0.1 | Sim | iPhone SE (3rd gen) | `8.6.1` | 2023-12-04 |
| Android 29 | Real | NVIDIA Shield | `8.6.1` | 2023-12-04 |
| iOS 15.1 | Real | iPad Pro | `8.6.1` | 2023-12-04 |
:::
:::danger Telemetry
:::warning Telemetry
Before starting this demo, manually disable telemetry.
@ -78,15 +68,15 @@ NativeScript 8.6.1 split the telemetry into two parts: "usage" and "error". Both
must be disabled separately:
```bash
npx -y -p nativescript ns usage-reporting disable
npx -y -p nativescript ns error-reporting disable
npx -p nativescript ns usage-reporting disable
npx -p nativescript ns error-reporting disable
```
To verify telemetry was disabled:
```bash
npx -y -p nativescript ns usage-reporting status
npx -y -p nativescript ns error-reporting status
npx -p nativescript ns usage-reporting status
npx -p nativescript ns error-reporting status
```
:::
@ -98,9 +88,9 @@ imported from any component or script in the app.
The `@nativescript/core/file-system` package provides classes for file access.
The `File` class does not support binary data, but the file access singleton
from `@nativescript/core` does support reading and writing `ArrayBuffer` data.
from `@nativescript/core` does support reading and writing `ArrayBuffer`.
Reading and writing data require a URL. The following snippet searches typical
Reading and writing data require a URL. The following snippet searches typical
document folders for a specified filename:
```ts
@ -112,85 +102,6 @@ function get_url_for_filename(filename: string): string {
}
```
### App Configuration
Due to privacy concerns, apps must request file access. There are special APIs
for accessing data and are subject to change in future platform versions.
<details>
<summary><b>Technical Details</b> (click to show)</summary>
**iOS**
The following key/value pairs must be added to `Info.plist`:
```xml title="App_Resources/iOS/Info.plist (add highlighted lines)"
<dict>
<!-- highlight-start -->
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<!-- highlight-end -->
```
---
**Android**
Android security has evolved over the years. In newer Android versions, the
following workarounds were required:
- `READ_EXTERNAL_STORAGE` and `WRITE_EXTERNAL_STORAGE` allow apps to access
files outside of the app scope. These are required for scoped storage access.
When the demo was last tested, this option was enabled by default.
- `android:requestLegacyExternalStorage="true"` enabled legacy behavior in some
older releases.
The manifest is saved to `App_Resources/Android/src/main/AndroidManifest.xml`:
```xml title="App_Resources/Android/src/main/AndroidManifest.xml (add highlighted lines)"
<application
<!-- highlight-next-line -->
android:requestLegacyExternalStorage="true"
android:name="com.tns.NativeScriptApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true">
```
- Permissions must be explicitly requested.
`@nativescript-community/perms` is a community module for managing permissions:
```ts title="App script or component"
import { request } from '@nativescript-community/perms';
import { File } from '@nativescript/core/file-system';
```
Storage access must be requested before writing data:
```ts title="App script or component (before writing file)"
/* request permissions */
const res = await request('storage');
```
The external paths can be resolved using the low-level APIs:
```ts title="App script or component (writing to downloads folder)"
/* find Downloads folder */
const dl_dir = android.os.Environment.DIRECTORY_DOWNLOADS;
const dl = android.os.Environment.getExternalStoragePublicDirectory(dl_dir).getAbsolutePath();
/* write to file */
File.fromPath(dl + "/SheetJSNS.xls").writeSync(data);
```
</details>
### Reading Local Files
`getFileAccess().readBufferAsync` can read data into an `ArrayBuffer` object.
@ -265,7 +176,7 @@ import { read } from 'xlsx';
const temp: string = path.join(knownFolders.temp().path, "pres.xlsx");
/* download file */
const file = await getFile("https://docs.sheetjs.com/pres.xlsx", temp)
const file = await getFile("https://sheetjs.com/pres.xlsx", temp)
/* get data */
const ab: ArrayBuffer = await getFileAccess().readBufferAsync(file.path);
@ -281,58 +192,54 @@ const wb = read(ab);
0) Disable telemetry:
```bash
npx -y -p nativescript ns usage-reporting disable
npx -y -p nativescript ns error-reporting disable
npx -p nativescript ns usage-reporting disable
npx -p nativescript ns error-reporting disable
```
1) Follow the official Environment Setup instructions[^8].
:::caution pass
In previous test runs, NativeScript did not support the latest Android API.
The error message from `npx -y -p nativescript ns doctor android` clearly stated
supported versions:
When the demo was last tested, the latest version of the Android API was 34.
NativeScript did not support that API level. The exact error message from
`npx -p nativescript ns doctor ios` clearly stated supported versions:
<pre>
<span {...r}></span> No compatible version of the Android SDK Build-tools are installed on your system. You can install any version in the following range: '&gt;=23 &lt;=33'.
</pre>
(x is red, body text is yellow)
```
✖ No compatible version of the Android SDK Build-tools are installed on your system. You can install any version in the following range: '>=23 <=33'.
```
If NativeScript does not properly support the latest API level, an older API
version should be installed using Android Studio.
In a previous test run, the following packages were required:
The SDK Platform `Android 13.0 ("Tiramisu")` was compatible with NativeScript.
Until NativeScript properly supports API level 34, "Tiramisu" must be used.
This requires installing the following packages from Android Studio:
- `Android 13.0 ("Tiramisu")` API Level `33`
- `Android SDK Build-Tools` Version `33.0.2`
It is recommended to install the SDK Platform and corresponding Android SDK
Build-Tools for the latest supported API level.
:::
2) Test the local system configuration for Android development:
```bash
npx -y -p nativescript ns doctor android
npx -p nativescript ns doctor android
```
In the last macOS test, the following output was displayed:
<details open>
<summary><b>Expected output</b> (click to hide)</summary>
<details open><summary><b>Expected output</b> (click to hide)</summary>
<pre>
<span {...g}></span> Getting environment information
<b>No issues were detected.</b>
<span {...g}></span> Your ANDROID_HOME environment variable is set and points to correct directory.
<span {...g}></span> Your adb from the Android SDK is correctly installed.
<span {...g}></span> The Android SDK is installed.
<span {...g}></span> A compatible Android SDK for compilation is found.
<span {...g}></span> Javac is installed and is configured properly.
<span {...g}></span> The Java Development Kit (JDK) is installed and is configured properly.
<span {...g}></span> Getting NativeScript components versions information...
<span {...g}></span> Component nativescript has 8.9.2 version and is up to date.
<span {...g}></span> Getting environment information{'\n'}
{'\n'}
<b>No issues were detected.</b>{'\n'}
<span {...g}></span> Your ANDROID_HOME environment variable is set and points to correct directory.{'\n'}
<span {...g}></span> Your adb from the Android SDK is correctly installed.{'\n'}
<span {...g}></span> The Android SDK is installed.{'\n'}
<span {...g}></span> A compatible Android SDK for compilation is found.{'\n'}
<span {...g}></span> Javac is installed and is configured properly.{'\n'}
<span {...g}></span> The Java Development Kit (JDK) is installed and is configured properly.{'\n'}
<span {...g}></span> Getting NativeScript components versions information...{'\n'}
<span {...g}></span> Component nativescript has 8.6.1 version and is up to date.
</pre>
</details>
@ -340,28 +247,28 @@ In the last macOS test, the following output was displayed:
3) Test the local system configuration for iOS development (macOS only):
```bash
npx -y -p nativescript ns doctor ios
npx -p nativescript ns doctor ios
```
In the last macOS test, the following output was displayed:
<details open>
<summary><b>Expected output</b> (click to hide)</summary>
<details open><summary><b>Expected output</b> (click to hide)</summary>
<pre>
<span {...g}></span> Getting environment information
<b>No issues were detected.</b>
<span {...g}></span> Xcode is installed and is configured properly.
<span {...g}></span> xcodeproj is installed and is configured properly.
<span {...g}></span> CocoaPods are installed.
<span {...g}></span> CocoaPods update is not required.
<span {...g}></span> CocoaPods are configured properly.
<span {...g}></span> Your current CocoaPods version is newer than 1.0.0.
<span {...g}></span> Python installed and configured correctly.
<span {...g}></span> Xcode version 16.3.0 satisfies minimum required version 10.
<span {...g}></span> Getting NativeScript components versions information...
<span {...g}></span> Component nativescript has 8.9.2 version and is up to date.
<span {...g}></span> Getting environment information{'\n'}
{'\n'}
No issues were detected.{'\n'}
<span {...g}></span> Xcode is installed and is configured properly.{'\n'}
<span {...g}></span> xcodeproj is installed and is configured properly.{'\n'}
<span {...g}></span> CocoaPods are installed.{'\n'}
<span {...g}></span> CocoaPods update is not required.{'\n'}
<span {...g}></span> CocoaPods are configured properly.{'\n'}
<span {...g}></span> Your current CocoaPods version is newer than 1.0.0.{'\n'}
<span {...g}></span> Python installed and configured correctly.{'\n'}
<span {...g}></span> The Python 'six' package is found.{'\n'}
<span {...g}></span> Xcode version 15.0.1 satisfies minimum required version 10.{'\n'}
<span {...g}></span> Getting NativeScript components versions information...{'\n'}
<span {...g}></span> Component nativescript has 8.6.1 version and is up to date.
</pre>
</details>
@ -371,21 +278,20 @@ In the last macOS test, the following output was displayed:
4) Create a skeleton NativeScript + Angular app:
```bash
npx -y -p nativescript ns create SheetJSNS --ng
npx -p nativescript ns create SheetJSNS --ng
```
5) Launch the app in the Android simulator to verify the app:
5) Launch the app in the android simulator to verify the app:
```bash
cd SheetJSNS
npx -y -p nativescript ns run android
npx -p nativescript ns run android
```
(this may take a while)
Once the simulator launches and the test app is displayed, end the script by
selecting the terminal and pressing <kbd>CTRL</kbd>+<kbd>C</kbd>. On Windows, if
prompted to `Terminate batch job`, type <kbd>Y</kbd> and press Enter.
selecting the terminal and entering the key sequence `CTRL + C`
:::note pass
@ -397,22 +303,11 @@ Emulator start failed with: No emulator image available for device identifier 'u
:::
:::caution pass
6) From the project folder, install the library:
In the most recent test, the build failed with an exception:
```
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/sheetjs/.gradle/wrapper/dists/gradle-8.7-bin/bhs2wmbdwecv87pi65oeuq5iu/gradle-8.7/lib/native-platform-0.22-milestone-25.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
```
**The NativeScript Gradle version is incompatible with Java 24!**
It is strongly recommended to roll back to Java 21.
:::
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
### Add SheetJS
@ -422,34 +317,29 @@ The goal of this section is to display the SheetJS library version number.
:::
6) From the project folder, install the SheetJS NodeJS module:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
7) Edit `src/app/item/items.component.ts` so that the component imports the
SheetJS version string and adds it to a `version` variable in the component:
```ts title="src/app/item/items.component.ts (add highlighted lines)"
```ts title="src/app/item/items.component.ts"
// highlight-next-line
import { version } from 'xlsx';
import { Component, NO_ERRORS_SCHEMA, inject } from '@angular/core'
import { Component, OnInit } from '@angular/core'
// ...
export class ItemsComponent {
export class ItemsComponent implements OnInit {
items: Array<Item>
// highlight-next-line
version = `SheetJS - ${version}`;
itemService = inject(ItemService)
page = inject(Page)
constructor(private itemService: ItemService) {}
// ...
```
8) Edit the template `src/app/item/items.component.html` to reference `version`
in the title of the action bar:
```xml title="src/app/item/items.component.html (edit highlighted line)"
```xml title="src/app/item/items.component.html"
<!-- highlight-next-line -->
<ActionBar [title]="version"></ActionBar>
@ -457,10 +347,10 @@ in the title of the action bar:
<!-- ... -->
```
9) End the script and relaunch the app in the Android simulator:
9) Relaunch the app in the Android simulator:
```bash
npx -y -p nativescript ns run android
npx -p nativescript ns run android
```
The title bar should show the version.
@ -471,7 +361,7 @@ The title bar should show the version.
10) Add the Import and Export buttons to the template:
```xml title="src/app/item/items.component.html (add highlighted lines)"
```xml title="src/app/item/items.component.html"
<ActionBar [title]="version"></ActionBar>
<!-- highlight-start -->
@ -481,7 +371,7 @@ The title bar should show the version.
<Button text="Export File" (tap)="export()" style="padding: 10px"></Button>
</StackLayout>
<!-- highlight-end -->
<ListView [items]="itemService.items()">
<ListView [items]="items">
<!-- ... -->
</ListView>
<!-- highlight-next-line -->
@ -490,15 +380,15 @@ The title bar should show the version.
11) Add the `import` and `export` methods in the component script:
```ts title="src/app/item/items.component.ts (add highlighted lines)"
```ts title="src/app/item/items.component.ts"
// highlight-start
import { version, utils, read, write } from 'xlsx';
import { Dialogs, getFileAccess } from '@nativescript/core';
import { Folder, knownFolders, path } from '@nativescript/core/file-system';
// highlight-end
import { Component, NO_ERRORS_SCHEMA, inject } from '@angular/core'
import { NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular'
import { Page } from '@nativescript/core'
import { Component, OnInit } from '@angular/core'
import { Item } from './item'
import { ItemService } from './item.service'
// highlight-start
@ -508,12 +398,19 @@ function get_url_for_filename(filename: string): string {
}
// highlight-end
// ...
@Component({
selector: 'ns-items',
templateUrl: './items.component.html',
})
export class ItemsComponent implements OnInit {
items: Array<Item>
version: string = `SheetJS - ${version}`;
export class ItemsComponent {
version = `SheetJS - ${version}`;
itemService = inject(ItemService)
page = inject(Page)
constructor(private itemService: ItemService) {}
ngOnInit(): void {
this.items = this.itemService.getItems()
}
// highlight-start
/* Import button */
@ -524,23 +421,16 @@ export class ItemsComponent {
async export() {
}
// highlight-end
// ...
}
```
12) End the script and relaunch the app in the Android simulator:
```bash
npx -y -p nativescript ns run android
```
Two buttons should appear just below the header:
12) Restart the app process. Two buttons should show up at the top:
![NativeScript Step 5](pathname:///nativescript/step5.png)
13) Implement import and export by adding the highlighted lines:
```ts title="src/app/item/items.component.ts (add highlighted lines)"
```ts title="src/app/item/items.component.ts"
/* Import button */
async import() {
// highlight-start
@ -560,7 +450,7 @@ Two buttons should appear just below the header:
const ws = wb.Sheets[wsname];
/* update table */
this.itemService.items.set(utils.sheet_to_json(ws));
this.items = utils.sheet_to_json<Item>(ws);
} catch(e) { await Dialogs.alert(e.message); }
// highlight-end
}
@ -573,7 +463,7 @@ Two buttons should appear just below the header:
try {
/* create worksheet from data */
const ws = utils.json_to_sheet(this.itemService.items());
const ws = utils.json_to_sheet(this.items);
/* create workbook from worksheet */
const wb = utils.book_new();
@ -595,57 +485,36 @@ Two buttons should appear just below the header:
14) Launch the app in the Android Simulator:
```bash
npx -y -p nativescript ns run android
```
npx -p nativescript ns run android
````
If the app does not automatically launch, manually open the `SheetJSNS` app.
15) Tap "Export File". A dialog will print where the file was written. Typically
the URL is `/data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls`
16) Pull the file from the simulator. The following commands should be run in a
new terminal or PowerShell window:
16) Pull the file from the simulator:
```bash
adb root
adb pull /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls SheetJSNS.xls
```
If the emulator cannot be rooted, the following command works in macOS:
If the emulator cannot be rooted:
```bash
adb shell "run-as org.nativescript.SheetJSNS cat /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls" > SheetJSNS.xls
```
:::caution pass
In the most recent `win11-x64` test, the generated file was corrupt. This is a
known issue with Windows redirects. The solution is to generate a Base64-encoded
string and decode using PowerShell:
```bash
adb shell "run-as org.nativescript.SheetJSNS base64 /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls" > SheetJSNS.xls.b64
$b64 = Get-Content -Path .\SheetJSNS.xls.b64 -Raw
$bytes = [Convert]::FromBase64String($b64)
[System.IO.File]::WriteAllBytes("SheetJSNS.xls", $bytes)
```
:::
17) Open `SheetJSNS.xls` with a spreadsheet editor.
After the header row, insert a row and make the following assignments:
After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library:
- Set cell `A2` to `0`
- Set cell `B2` to `SheetJS` (type `'SheetJS` in the formula bar)
- Set cell `C2` to `Library` (type `'Library` in the formula bar)
After making the changes, the worksheet should look like the following:
```text
```
id | name | role
# highlight-next-line
0 | SheetJS | Library
1 | Ter Stegen | Goalkeeper
3 | Piqué | Defender
...
```
@ -655,65 +524,34 @@ id | name | role
adb push SheetJSNS.xls /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls
```
If the emulator cannot be rooted, the following command works in macOS:
If the emulator cannot be rooted:
```bash
dd if=SheetJSNS.xls | adb shell "run-as org.nativescript.SheetJSNS dd of=/data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls"
```
:::caution pass
In the most recent `win11-x64` test, neither workaround worked. The solution is
to generate a Base64-encoded string and decode in `adb`. After closing Excel and
saving the `SheetJSNS.xls` file, run the following commands:
```bash
$bytes = [IO.File]::ReadAllBytes(".\SheetJSNS.xls")
$b64 = [Convert]::ToBase64String($bytes)
echo $b64 | adb shell "run-as org.nativescript.SheetJSNS base64 -d | run-as org.nativescript.SheetJSNS dd of=/data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls"
```
:::
```
19) Tap "Import File". A dialog will print the path of the file that was read.
The first item in the list will change.
![NativeScript Step 6](pathname:///nativescript/step6.png)
### iOS
:::danger pass
**iOS testing can only be performed on Apple hardware running macOS!**
Xcode and iOS simulators are not available on Windows or Linux.
Scroll down to ["Fetching Files"](#android-device) for Android device testing.
:::
20) Launch the app in the iOS Simulator:
```bash
npx -y -p nativescript ns run ios
npx -p nativescript ns run ios
```
21) Tap "Export File". A dialog will print where the file was written.
22) Open the file with a spreadsheet editor.
After the header row, insert a row and make the following assignments:
After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library:
- Set cell `A2` to `0`
- Set cell `B2` to `SheetJS` (type `'SheetJS` in the formula bar)
- Set cell `C2` to `Library` (type `'Library` in the formula bar)
After making the changes, the worksheet should look like the following:
```text
```
id | name | role
# highlight-next-line
0 | SheetJS | Library
1 | Ter Stegen | Goalkeeper
3 | Piqué | Defender
...
```
@ -726,199 +564,86 @@ The first item in the list will change:
### Fetching Files
25) Replace `item.service.ts` with the following:
25) In `src/app/item/items.component.ts`, make `ngOnInit` asynchronous:
```ts title="src/app/item/items.component.ts"
async ngOnInit(): Promise<void> {
this.items = await this.itemService.getItems()
}
```
26) Replace `item.service.ts` with the following:
```ts title="src/app/item/item.service.ts"
import { read, utils } from 'xlsx';
import { Injectable, signal, effect } from '@angular/core'
import { knownFolders, path, getFileAccess } from '@nativescript/core';
import { getFile } from '@nativescript/core/http';
import { Item } from './item'
import { Injectable } from '@angular/core'
import { knownFolders, path, getFileAccess } from '@nativescript/core'
import { getFile } from '@nativescript/core/http';
import { read, utils } from 'xlsx';
import { Item } from './item'
interface IPresident { Name: string; Index: number };
@Injectable({ providedIn: 'root' })
export class ItemService {
items = signal<Item[]>([]);
constructor() { effect(() => { (async() => {
/* fetch https://docs.sheetjs.com/pres.xlsx */
private items: Array<Item>;
async getItems(): Promise<Array<Item>> {
/* fetch https://sheetjs.com/pres.xlsx */
const temp: string = path.join(knownFolders.temp().path, "pres.xlsx");
const ab = await getFile("https://docs.sheetjs.com/pres.xlsx", temp)
const ab = await getFile("https://sheetjs.com/pres.xlsx", temp)
/* read the temporary file */
const wb = read(await getFileAccess().readBufferAsync(ab.path));
/* translate the first worksheet to the required Item type */
const data = utils.sheet_to_json<IPresident>(wb.Sheets[wb.SheetNames[0]]);
/* update state */
this.items.set(data.map((pres, id) => ({id, name: pres.Name, role: ""+pres.Index} as Item)));
})(); }); }
return this.items = data.map((pres, id) => ({id, name: pres.Name, role: ""+pres.Index} as Item));
}
getItem(id: number): Item {
return this.items().find((item) => item.id === id)
return this.items.filter((item) => item.id === id)[0]
}
}
```
26) End the script and relaunch the app in the Android simulator:
27) Relaunch the app in the Android simulator:
```bash
npx -y -p nativescript ns run android
```
npx -p nativescript ns run android
````
The app should show Presidential data.
### Android Device
27) Connect an Android device using a USB cable.
28) Connect an Android device using a USB cable.
If the device asks to allow USB debugging, tap "Allow".
28) Close any Android / iOS emulators.
29) Close any Android / iOS emulators.
29) Enable "Legacy External Storage" in the Android app. The manifest is stored
at `App_Resources/Android/src/main/AndroidManifest.xml`:
```xml title="App_Resources/Android/src/main/AndroidManifest.xml (add highlighted line)"
<application
<!-- highlight-next-line -->
android:requestLegacyExternalStorage="true"
android:name="com.tns.NativeScriptApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true">
```
30) Install the `@nativescript-community/perms` dependency:
30) Build APK and run on device:
```bash
npm i --save @nativescript-community/perms
```
31) Add the highlighted lines to `items.component.ts`:
- Import `File` from NativeScript core and `request` from the new dependency:
```ts title="items.component.ts (add highlighted lines)"
import { Dialogs, getFileAccess, Utils } from '@nativescript/core';
// highlight-start
import { request } from '@nativescript-community/perms';
import { Folder, knownFolders, path, File } from '@nativescript/core/file-system';
// highlight-end
import { Component, OnInit } from '@angular/core'
// ...
```
- Add a new write operation to the `export` method:
```ts title="items.component.ts (add highlighted lines)"
/* attempt to save Uint8Array to file */
await getFileAccess().writeBufferAsync(url, global.isAndroid ? (Array.from(u8) as any) : u8);
await Dialogs.alert(`Wrote to SheetJSNS.xls at ${url}`);
/* highlight-start */
if(global.isAndroid) {
/* request permissions */
const res = await request('storage');
/* write to Downloads folder */
const dl = android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
File.fromPath(dl + "/SheetJSNS.xls").writeSync(Array.from(u8));
}
/* highlight-end */
} catch(e) { await Dialogs.alert(e.message); }
```
32) Build APK and run on device:
```bash
npx -y -p nativescript ns run android
npx -p nativescript ns run android
```
If the Android emulators are closed and an Android device is connected, the last
command will build an APK and install on the device.
<details open>
<summary><b>Android Device Testing</b> (click to hide)</summary>
When the app launches, if the SheetJS library is loaded and if the device is
connected to the Internet, a list of Presidents should be displayed.
Tap "Export File". The app will show an alert. Tap "OK".
Switch to the "Files" app and open the "Downloads" folder. There should be a new
file named `SheetJSNS.xls`.
</details>
### iOS Device
33) Connect an iOS device using a USB cable
31) Connect an iOS device using a USB cable
34) Close any Android / iOS emulators.
32) Close any Android / iOS emulators.
35) Enable developer code signing certificates:
33) Enable developer code signing certificates[^9]
Open `platforms/ios/SheetJSNS.xcodeproj/project.xcworkspace` in Xcode. Select
the "Project Navigator" and select `SheetJSNS`. In the main view, select the
`SheetJSNS` target. Click "Signing & Capabilities". Under "Signing", select a
team in the dropdown menu.
:::caution pass
When this demo was last tested, Xcode repeatedly crashed.
The issue was resolved by cleaning the project:
34) Run on device:
```bash
npx -y -p nativescript ns platform clean ios
npx -p nativescript ns run ios
```
:::
36) Add the following key/value pairs to `Info.plist`:
```xml title="App_Resources/iOS/Info.plist (add highlighted lines)"
<dict>
<!-- highlight-start -->
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<!-- highlight-end -->
```
37) Run on device:
```bash
npx -y -p nativescript ns run ios
```
:::info pass
If this is the first time testing an app on a device, the certificate must be
trusted on the device:
Under "Settings" > "General" > "VPN & Device Management", there should be a
"Apple Development" certificate in the "DEVELOPER APP" section. Select the
certificate and confirm that "SheetJSNS" is listed under "APPS". Tap "Trust ..."
and tap "Trust" in the popup.
:::
<details open>
<summary><b>iOS Device Testing</b> (click to hide)</summary>
When the app launches, if the SheetJS library is loaded and if the device is
connected to the Internet, a list of Presidents should be displayed.
Tap "Export File". The app will show an alert. Tap "OK".
Switch to the "Files" app and repeatedly tap "&lt;". In the "Browse" window, tap
"On My iPhone". There should be a new folder named "SheetJSNS". Tap the folder
and look for the file named `SheetJSNS.xls`.
</details>
[^1]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^2]: See ["Workbook Object"](/docs/csf/book)
[^3]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
@ -927,3 +652,4 @@ and look for the file named `SheetJSNS.xls`.
[^6]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`.
[^7]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^8]: See ["Local setup"](https://docs.nativescript.org/setup/#local-setup) in the NativeScript documentation. For Windows and Linux, follow the "Android" instructions. For macOS, follow both the iOS and Android instructions.
[^9]: The [Flutter documentation](https://docs.flutter.dev/get-started/install/macos?tab=ios15#enable-developer-code-signing-certificates) covers the instructions in more detail. The correct workspace is `platforms/ios/SheetJSNS.xcodeproj/project.xcworkspace`

@ -41,20 +41,10 @@ The ["Demo"](#demo) creates an app that looks like the screenshots below:
This demo was tested in the following environments:
**Real Devices**
| OS | Device | Quasar | Date |
|:-----------|:--------------------|:---------|:-----------|
| Android 34 | NVIDIA Shield | `2.18.1` | 2025-04-17 |
| iOS 15.1 | iPad Pro | `2.18.1` | 2025-04-17 |
**Simulators**
| OS | Device | Quasar | Dev Platform | Date |
|:-----------|:--------------------|:---------|:-------------|:-----------|
| Android 35 | Pixel 9 Pro XL | `2.18.1` | `darwin-arm` | 2025-04-17 |
| iOS 18.2 | iPhone 16 Pro Max | `2.18.1` | `darwin-arm` | 2025-04-17 |
| Android 36 | Pixel 9 Pro XL | `2.18.1` | `win11-x64` | 2025-04-17 |
| OS | Type | Device | Quasar | Date |
|:-----------|:-----|:--------------------|:---------|:-----------|
| Android 34 | Sim | Pixel 3a | `2.14.1` | 2023-12-04 |
| iOS 17.0.1 | Sim | iPhone SE (3rd gen) | `2.14.1` | 2023-12-04 |
:::
@ -77,7 +67,7 @@ cd ..
### Reading data
The `QFile`[^1] component presents an API reminiscent of File Input elements:
The QFile[^1] component presents an API reminiscent of File Input elements:
```html
<q-file label="Load File" filled label-color="orange" @input="updateFile"/>
@ -167,32 +157,13 @@ window.requestFileSystem(window.PERSISTENT, 0, function(fs) {
The demo draws from the ViteJS example. Familiarity with VueJS and TypeScript
is assumed.
### Platform Setup
0) Ensure all of the dependencies are installed. Install the CLI globally:
```bash
npm i -g @quasar/cli cordova
```
:::note pass
In some systems, the command must be run as the root user:
```bash
sudo npm i -g @quasar/cli cordova
```
:::
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
Quasar requires Java 17
</details>
### Base Project
(you may need to run `sudo npm i -g` if there are permission issues)
1) Create a new app:
@ -204,17 +175,19 @@ npm init quasar
When prompted:
- "What would you like to build?": `App with Quasar CLI, let's go!`
- "What would you like to build?": `App with Quasar CLI`
- "Project folder": `SheetJSQuasar`
- "Pick Quasar version": `Quasar v2 (Vue 3 | latest and greatest)`
- "Pick script type": `Typescript`
- "Pick Quasar App CLI variant": `Quasar App CLI with Vite`
- "Package name": (press <kbd>Enter</kbd>, it will use the default `sheetjsquasar`)
- "Package name": (just press enter, it will use the default `sheetjsquasar`
- "Project product name": `SheetJSQuasar`
- "Project description": `SheetJS + Quasar`
- "Pick a Vue component style": `Composition API with <script setup>`
- "Author": (just press enter, it will use your git config settings)
- "Pick a Vue component style": `Composition API`
- "Pick your CSS preprocessor": `None`
- "Check the features needed for your project": Deselect everything (scroll down to each selected item and press <kbd>Space</kbd>)
- "Install project dependencies": `Yes, use npm`
- "Check the features needed for your project": Deselect everything
- "Install project dependencies": `No`
2) Install dependencies:
@ -229,105 +202,18 @@ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
3) Set up Cordova:
```bash
npx cordova telemetry off
npx quasar mode add cordova
npx quasar mode remove capacitor
quasar mode add cordova
```
When prompted, enter the app id `org.sheetjs.quasar`.
4) Add the Dialog plugin to the `plugins` array in `quasar.config.ts`:
```js title="quasar.config.ts (edit highlighted line)"
// Quasar plugins
// highlight-next-line
plugins: ['Dialog']
```
5) Download [`IndexPage.vue`](pathname:///quasar/IndexPage.vue) and replace the
existing page script `src/pages/IndexPage.vue`:
```bash
curl -L -o src/pages/IndexPage.vue https://docs.sheetjs.com/quasar/IndexPage.vue
```
### Android
6) Create the Android project:
It will create a new `src-cordova` folder. Continue in that folder:
```bash
cd src-cordova
npx cordova platform add android
npx cordova plugin add cordova-plugin-wkwebview-engine
npx cordova plugin add cordova-plugin-file
cd ..
```
7) Start the simulator:
```bash
npx quasar dev -m cordova -T android
```
If prompted to select an external IP, press <kbd>Enter</kbd>.
:::caution pass
If the app is blank or not refreshing, delete the app and close the simulator,
then restart the development process.
:::
:::info pass
In some test runs, the command failed with a Gradle error
```
Could not find an installed version of Gradle either in Android Studio,
or on your system to install the gradle wrapper. Please include gradle
in your path, or install Android Studio
```
[Gradle](https://gradle.org/) (the complete version) must be extracted and the
`bin` folder must be added to the user PATH variable. After adding to PATH,
launch a new PowerShell or Command Prompt window and run the command.
:::
To test that reading works:
- Download https://docs.sheetjs.com/pres.numbers
- Open the Downloads folder in Finder or Explorer
- Click and drag `pres.numbers` from the window into the simulator.
- Tap "Load File", tap the `≡` icon, tap "Downloads" and select `pres.numbers`.
To test that writing works:
- Tap "Save File". You will see a popup with a location.
- Pull the file from the simulator and verify the contents:
```bash
adb exec-out run-as org.sheetjs.quasar cat files/files/SheetJSQuasar.xlsx > /tmp/SheetJSQuasar.xlsx
npx xlsx-cli /tmp/SheetJSQuasar.xlsx
```
:::caution pass
PowerShell file redirects will corrupt binary data. In Windows, commands must be
run from a Command Prompt session.
:::
### iOS
8) Create the iOS project:
```bash
cd src-cordova
npx cordova platform add ios
npx cordova plugin add cordova-plugin-wkwebview-engine
npx cordova plugin add cordova-plugin-file
cd ..
cordova platform add ios
cordova plugin add cordova-plugin-wkwebview-engine
cordova plugin add cordova-plugin-file
```
:::note pass
@ -335,14 +221,20 @@ cd ..
If there is an error `Could not load API for iOS project`, it needs to be reset:
```bash
npx cordova platform rm ios
npx cordova platform add ios
npx cordova plugin add cordova-plugin-file
cordova platform rm ios
cordova platform add ios
cordova plugin add cordova-plugin-file
```
:::
9) Enable file sharing and make the documents folder visible in the iOS app.
Return to the project directory:
```bash
cd ..
```
11) Enable file sharing and make the documents folder visible in the iOS app.
The following lines must be added to `src-cordova/platforms/ios/SheetJSQuasar/SheetJSQuasar-Info.plist`:
```xml title="src-cordova/platforms/ios/SheetJSQuasar/SheetJSQuasar-Info.plist (add to file)"
@ -360,14 +252,12 @@ The following lines must be added to `src-cordova/platforms/ios/SheetJSQuasar/Sh
(The root element of the document is `plist` and it contains one `dict` child)
10) Start the development server:
5) Start the development server:
```bash
npx quasar dev -m cordova -T ios
quasar dev -m ios
```
If prompted to select an external IP, press <kbd>Enter</kbd>.
:::caution pass
If the app is blank or not refreshing, delete the app and close the simulator,
@ -375,6 +265,52 @@ then restart the development process.
:::
6) Add the Dialog plugin to `quasar.config.js`:
```js title="quasar.config.js"
framework: {
config: {},
// ...
// Quasar plugins
// highlight-next-line
plugins: ['Dialog']
},
```
7) In the template section of `src/pages/IndexPage.vue`, replace the example
with a Table, Save button and Load file picker component:
```html title="src/pages/IndexPage.vue"
<template>
<q-page class="row items-center justify-evenly">
<!-- highlight-start -->
<q-table :rows="todos" />
<q-btn-group>
<q-file label="Load File" filled label-color="orange" @input="updateFile"/>
<q-btn label="Save File" @click="saveFile" />
</q-btn-group>
<!-- highlight-end -->
</q-page>
</template>
```
This uses two functions that should be added to the component script:
```ts title="src/pages/IndexPage.vue"
const meta = ref<Meta>({
totalCount: 1200
});
// highlight-start
function saveFile() {
}
async function updateFile(v: Event) {
}
return { todos, meta, saveFile, updateFile };
// highlight-end
}
});
```
The app should now show two buttons at the bottom:
![Quasar Step 6](pathname:///mobile/quasar6.png)
@ -386,9 +322,41 @@ then restart the development process.
:::
8) Wire up the `updateFile` function:
```ts title="src/pages/IndexPage.vue"
import { defineComponent, ref } from 'vue';
// highlight-start
import { read, write, utils } from 'xlsx';
import { useQuasar } from 'quasar';
// highlight-end
export default defineComponent({
// ...
// highlight-start
const $q = useQuasar();
function dialogerr(e: Error) { $q.dialog({title: "Error!", message: e.message || String(e)}); }
// highlight-end
function saveFile() {
}
async function updateFile(v: Event) {
// highlight-start
try {
const files = (v.target as HTMLInputElement).files;
if(!files || files.length == 0) return;
const wb = read(await files[0].arrayBuffer());
const data = utils.sheet_to_json<any>(wb.Sheets[wb.SheetNames[0]]);
todos.value = data.map(row => ({id: row.Index, content: row.Name}));
} catch(e) { dialogerr(e); }
// highlight-end
}
```
To test that reading works:
- Download https://docs.sheetjs.com/pres.numbers
- Download <https://sheetjs.com/pres.numbers>
- In the simulator, click the Home icon to return to the home screen
- Click on the "Files" icon
- Click and drag `pres.numbers` from a Finder window into the simulator.
@ -403,11 +371,46 @@ To test that reading works:
Once selected, the screen should refresh with new contents.
9) Wire up the `saveFile` function:
```ts title="src/pages/IndexPage.vue"
function saveFile() {
// highlight-start
/* generate workbook from state */
const ws = utils.json_to_sheet(todos.value);
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, "SheetJSQuasar");
const u8: Uint8Array = write(wb, {bookType: "xlsx", type: "buffer"});
const dir: string = $q.cordova.file.documentsDirectory || $q.cordova.file.externalApplicationStorageDirectory;
/* save to file */
window.requestFileSystem(window.PERSISTENT, 0, function(fs) {
try {
fs.root.getFile("SheetJSQuasar.xlsx", {create: true}, entry => {
const msg = `File stored at ${dir} ${entry.fullPath}`;
entry.createWriter(writer => {
try {
const data = new Blob([u8], {type: "application/vnd.ms-excel"});
writer.onwriteend = () => {
try {
$q.dialog({title: "Success!", message: msg});
} catch(e) { dialogerr(e); }
};
writer.onerror = dialogerr;
writer.write(data);
} catch(e) { dialogerr(e); }
}, dialogerr);
}, dialogerr);
} catch(e) { dialogerr(e) }
}, dialogerr);
// highlight-end
}
```
The page should revert to the old contents.
To test that writing works:
- Close the app in the simulator and re-launch the app.
- Click "Save File". You will see a popup with a location:
![Quasar Step 8](pathname:///mobile/quasar8.png)
@ -452,81 +455,38 @@ id,content
46,Joseph Biden
```
### Android Device
**Android**
11) Close all open emulators and simulators.
12) Disconnect any iOS or Android devices connected to the computer.
13) Connect the Android device to the computer.
14) Start the dev process:
10) Create the Android project:
```bash
npx quasar dev -m cordova -T android
cd src-cordova
cordova platform add android
cd ..
```
If prompted to select an external IP, press <kbd>Enter</kbd>.
15) Test the application:
- Press the Home button (or swipe up with one finger) and switch to Browser.
- Download https://docs.sheetjs.com/pres.numbers
- Press the Home button (or swipe up with one finger) and select the `SheetJSQuasar` app
- Tap the "Load" button, then select "Choose File" and select the downloaded `pres.numbers`
The table will update with new data.
:::warning pass
The "Save File" process will write files. However, Android 30+ requires special
methods ("Storage Access Framework") that are not implemented in Quasar.
:::
### iOS Device
16) Close all open emulators and simulators.
17) Disconnect any iOS or Android devices connected to the computer.
18) Connect the iOS device to the computer.
19) Open the Xcode project:
11) Start the simulator:
```bash
open src-cordova/platforms/ios/SheetJSQuasar.xcodeproj
quasar dev -m android
```
Select "SheetJSQuasar" in the Navigator. In the main pane, select "Signing &amp;
Capabilities" and ensure a Team is selected. Save and close the project.
To test that reading works:
20) Start the dev process:
- Click and drag `pres.numbers` from a Finder window into the simulator.
- Tap "Load", tap the `≡` icon, tap "Downloads" and select `pres.numbers`.
To test that writing works:
- Tap "Save File". You will see a popup with a location.
- Pull the file from the simulator and verify the contents:
```bash
npx quasar dev -m cordova -T ios
adb exec-out run-as org.sheetjs.quasar cat files/files/SheetJSQuasar.xlsx > /tmp/SheetJSQuasar.xlsx
npx xlsx-cli /tmp/SheetJSQuasar.xlsx
```
If prompted to select an external IP, press <kbd>Enter</kbd>.
11) Test the application:
- Press the Home button (or swipe up with one finger) and switch to Safari.
- Download https://docs.sheetjs.com/pres.numbers
- Press the Home button (or swipe up with one finger) and select the `SheetJSQuasar` app
- Tap the "Load" button, then select "Choose File" and select the downloaded `pres.numbers`
The table will update with new data.
- Tap "Save File"
- Press the Home button (or swipe up with one finger) and switch to Files.
- Tap `<` until the main "Browse" window is displayed, then select "On My iPhone"
- Look for the "SheetJSQuasar" folder and tap `SheetJSQuasar.xlsx`.
If Numbers is installed on the device, it will display the contents of the new file.
[^1]: See ["File Picker"](https://quasar.dev/vue-components/file-picker) in the Quasar documentation.
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^3]: See ["SheetJS Data Model"](/docs/csf/) for more details on workbooks, worksheets, and other concepts.

@ -49,54 +49,42 @@ The [CapacitorJS demo](/docs/demos/mobile/capacitor) covers CapacitorJS apps.
This demo was tested in the following environments:
**Real Devices**
| OS | Type | Device | Date |
|:-----------|:-----|:--------------------|:-----------|
| Android 34 | Sim | Pixel 3a | 2023-12-04 |
| iOS 17.0.1 | Sim | iPhone SE (3rd gen) | 2023-12-04 |
| OS | Device | Config | Date |
|:-----------|:--------------------|:-------|:-----------|
| Android 34 | NVIDIA Shield | B | 2025-03-30 |
| iOS 15.6 | iPhone 13 Pro Max | B | 2025-03-30 |
`ionic info` showed:
**Simulators**
- Ionic: `@ionic/angular 7.5.7`, `@ionic/angular-toolkit 9.0.0`
- Cordova: `cordova-lib@12.0.1`, `android 12.0.1, ios 7.0.1`
| OS | Device | Config | Dev Platform | Date |
|:-----------|:--------------------|:-------|:-------------|:-----------|
| Android 34 | Pixel 3a | B | `darwin-arm` | 2025-03-30 |
| iOS 18.2 | iPhone SE (3rd gen) | B | `darwin-arm` | 2025-03-30 |
| Android 36 | Pixel 9 Pro XL | A | `win11-x64` | 2025-04-17 |
<details>
<summary><b>Configurations</b> (click to show)</summary>
Configuration A:
- Ionic: `@ionic/angular 8.5.2`, `@ionic/angular-toolkit 12.1.1`
- Cordova: `cordova-lib@12.0.2`, `android 14.0.0`
- File Integration: `@awesome-cordova-plugins/file` version `6.16.0`
Configuration B:
- Ionic: `@ionic/angular 8.5.2`, `@ionic/angular-toolkit 12.1.1`
- Cordova: `cordova-lib@12.0.2`, `android 14.0.0, ios 7.1.1`
- File Integration: `@awesome-cordova-plugins/file` version `6.16.0`
</details>
The file integration uses `@awesome-cordova-plugins/file` version `6.4.0`.
:::
:::danger Telemetry
:::warning Telemetry
Before starting this demo, manually disable telemetry:
Before starting this demo, manually disable telemetry. On Linux and MacOS:
```bash
npx -y @ionic/cli config set -g telemetry false
npx -y @capacitor/cli telemetry off
rm -rf ~/.ionic/
mkdir ~/.ionic
cat <<EOF > ~/.ionic/config.json
{
"version": "6.20.1",
"telemetry": false,
"npmClient": "npm"
}
EOF
npx @capacitor/cli telemetry off
```
To verify telemetry was disabled:
```bash
npx -y @ionic/cli config get -g telemetry
npx -y @capacitor/cli telemetry
npx @ionic/cli config get -g telemetry
npx @capacitor/cli telemetry
```
:::
@ -144,18 +132,7 @@ simplifies iteration over the array of arrays:
### File Operations
The `cordova-plugin-file` plugin reads and writes files on devices.
:::caution pass
For Android 30+, due to scoped storage rules, the standard file module writes
private files that cannot be accessed from the Files app.
A Storage Access Framework plugin must be used to write external files.
:::
`@awesome-cordova-plugins/file` is a wrapper designed for Ionic + Angular apps.
`@awesome-cordova-plugins/file` reads and writes files on devices.
:::info pass
@ -173,7 +150,7 @@ These objects can be parsed with the SheetJS `read` method[^4]. The SheetJS
`sheet_to_json` method[^5] with the option `header: 1` generates an array of
arrays which can be assigned to the page state:
```ts title="Read file from device and update state"
```ts
/* read a workbook file */
const ab: ArrayBuffer = await this.file.readAsArrayBuffer(url, filename);
/* parse */
@ -194,7 +171,7 @@ worksheet object. The `book_new` and `book_append_sheet` helpers[^7] generate a
workbook object. The SheetJS `write` method[^8] with the option `type: "array"`
will generate an `ArrayBuffer`, from which a `Blob` can be created:
```ts title="Export state data to XLSX workbook and write to device"
```ts
/* generate worksheet */
const ws: XLSX.WorkSheet = XLSX.utils.aoa_to_sheet(this.data);
@ -216,7 +193,7 @@ this.file.writeFile(url, filename, blob, {replace: true});
The app in this demo will display data in a table.
On load, a [test file](https://docs.sheetjs.com/pres.numbers) will be processed.
On load, a [test file](https://sheetjs.com/pres.numbers) will be processed.
When a document is selected with the file picker, it will be processed and the
table will refresh to show the contents.
@ -230,38 +207,16 @@ known location. After writing, an alert will display the location of the file.
### Platform Setup
0) Disable telemetry:
```bash
npx -y @ionic/cli config set -g telemetry false
npx -y @capacitor/cli telemetry off
```
0) Disable telemetry as noted in the warning.
1) Follow the official instructions for iOS and Android development[^9].
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
Ionic requires Java 17.
</details>
2) Install required global dependencies:
```bash
npm i -g cordova cordova-res @angular/cli native-run @ionic/cli
```
:::note pass
In some systems, the command must be run as the root user:
```bash
sudo npm i -g cordova cordova-res @angular/cli native-run @ionic/cli
```
:::
### Base Project
3) Create a new project:
@ -272,15 +227,15 @@ ionic start SheetJSIonic blank --type angular --cordova --quiet --no-git --no-li
When asked to select `NgModules` or `Standalone Components`, select `NgModules`
If a prompt asks to confirm Cordova use, enter <kbd>Y</kbd> to continue.
If a prompt asks to confirm Cordova use, enter `Yes` to continue.
If a prompt asks to create an Ionic account, enter <kbd>N</kbd> to opt out.
If a prompt asks about creating an Ionic account, enter `N` to opt out.
:::caution pass
Due to conflicts in the dependency tree, the command failed in some test runs.
Due to conflicts in the dependency tree, the command failed in the last test.
If the package installation fails, forcefully install all modules:
The fix is to force install all modules:
```bash
cd SheetJSIonic
@ -296,9 +251,28 @@ cd ..
```bash
cd SheetJSIonic
ionic cordova plugin add cordova-plugin-file
ionic cordova platform add ios --confirm
ionic cordova platform add android --confirm
npm i --save @awesome-cordova-plugins/core @awesome-cordova-plugins/file @ionic/cordova-builders
```
:::note pass
If `cordova-plugin-file` is added before the platforms, installation may fail:
```
CordovaError: Could not load API for ios project
```
This can be resolved by removing and reinstalling the `ios` platform:
```bash
ionic cordova platform rm ios
ionic cordova platform add ios --confirm
```
:::
:::caution pass
If the `npm i` step fails due to `rxjs` resolution, add the highlighted lines
@ -326,9 +300,9 @@ After adding the lines, the `npm i` command will succeed.
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
6) Add the `@awesome-cordova-plugins/file` plugin to `src/app/app.module.ts`:
6) Add `@awesome-cordova-plugins/file` to the module. Differences highlighted below:
```ts title="src/app/app.module.ts (add highlighted lines)"
```ts title="src/app/app.module.ts"
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
@ -352,167 +326,9 @@ export class AppModule {}
curl -o src/app/home/home.page.ts -L https://docs.sheetjs.com/ionic/home.page.ts
```
:::note pass
In PowerShell, the command may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'L'.
```
`curl.exe` must be invoked directly:
```bash
curl.exe -o src/app/home/home.page.ts -L https://docs.sheetjs.com/ionic/home.page.ts
```
:::
### Android
8) Add the Android platform to the project:
```bash
ionic cordova platform add android --confirm
npm i --save cordova-android
```
9) Enable file reading and writing in the Android app.
Edit `platforms/android/app/src/main/AndroidManifest.xml` and add the following
two lines before the `application` tag:
```xml title="platforms/android/app/src/main/AndroidManifest.xml (add to file)"
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```
In the `application` tag, add the attribute `android:requestLegacyExternalStorage="true"`.
10) Build the app and start the emulator:
```bash
ionic cordova emulate android
```
If prompted to share pseudonymous usage data with Google, type <kbd>N</kbd> and
press <kbd>Enter</kbd> to opt out.
If prompted to share anonymous usage data with Cordova, type <kbd>N</kbd>.
When the app is loaded, a list of Presidents should be displayed. This list is
dynamically generated by fetching and parsing a test file.
:::caution pass
In some test runs, `cordova build android --emulator` step failed with error:
```
Could not find or parse valid build output file
```
This was resolved by forcefully installing `cordova-android`:
```bash
npm i --save cordova-android
```
:::
:::caution pass
In some test runs, Ionic could not find the emulator:
```
ERR_NO_TARGET: No target devices/emulators available.
```
The target emulator can be found by running
```bash
avdmanager list avd
```
In a test run, the output showed a Pixel 3a with the following details:
```text
// highlight-next-line
Name: Pixel_3a_API_34
Device: pixel_3a (Google)
Path: /Users/SheetJS/.android/avd/Pixel_3a_API_34.avd
```
The Ionic command accepts a `--target` flag. Pass the emulator name:
```bash
ionic cordova emulate android --target=Pixel_3a_API_34
```
:::
:::caution pass
In some tests, the build failed with a Gradle error:
```
Could not find an installed version of Gradle either in Android Studio,
or on your system to install the gradle wrapper. Please include gradle
in your path or install Android Studio
```
On macOS, this issue was resolved by installing Gradle with Homebrew manager:
```bash
brew install gradle
```
In Windows, Gradle must be installed manually[^11]
:::
:::danger pass
When the demo was last tested on Android, reading files worked as expected.
However, the generated files were not externally visible from the Files app.
**This is a known bug with Android SDK 33 and the underlying file plugins!**
:::
### iOS
:::danger pass
**iOS testing can only be performed on Apple hardware running macOS!**
Xcode and iOS simulators are not available on Windows or Linux.
:::
11) Add the iOS platform to the project:
```bash
ionic cordova platform add ios --confirm
```
:::note pass
If `cordova-plugin-file` is added before the platforms, installation may fail:
```
CordovaError: Could not load API for ios project
```
This can be resolved by removing and reinstalling the `ios` platform:
```bash
ionic cordova platform rm ios
ionic cordova platform add ios --confirm
```
:::
12) Enable file sharing and make the documents folder visible in the iOS app.
8) Enable file sharing and make the documents folder visible in the iOS app.
Add the following lines to `platforms/ios/SheetJSIonic/SheetJSIonic-Info.plist`:
```xml title="platforms/ios/SheetJSIonic/SheetJSIonic-Info.plist (add to file)"
@ -529,7 +345,7 @@ Add the following lines to `platforms/ios/SheetJSIonic/SheetJSIonic-Info.plist`:
(The root element of the document is `plist` and it contains one `dict` child)
13) Build the app and start the simulator
9) Build the app and start the simulator
```bash
ionic cordova emulate ios
@ -598,86 +414,71 @@ ng add @ionic/cordova-builders
:::
### iOS Device
### Android
14) Connect an iOS device to the computer and "Trust" the device if prompted.
10) Enable file reading and writing in the Android app.
15) Enable code signing for the project:
Edit `platforms/android/app/src/main/AndroidManifest.xml` and add the following
two lines before the `application` tag:
Open the `SheetJSIonic.xcodeproj` project in Xcode:
```xml title="platforms/android/app/src/main/AndroidManifest.xml (add to file)"
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```
In the `application` tag, add the attribute `android:requestLegacyExternalStorage="true"`.
11) Build the app and start the emulator
```bash
open platforms/ios/SheetJSIonic.xcodeproj
ionic cordova emulate android
```
Select the "SheetJSIonic" project in the "Project navigator" side panel.
When the app is loaded, a list of Presidents should be displayed. This list is
dynamically generated by fetching and parsing a test file.
In the main panel, select "Signing & Capabilities".
:::caution pass
In the "Team" dropdown, select a certificate.
In some test runs, `cordova build android --emulator` step failed with error:
In the "Bundle Identifier" text box, enter `com.sheetjs.SheetJSIonic`
```
Could not find or parse valid build output file
```
16) Launch the app on the device:
This was resolved by forcefully installing `cordova-android`:
```bash
ionic cordova run ios --device --verbose
```
:::info pass
In the most recent test, the `native-run ios` command failed with
```
[native-run] ERR_UNKNOWN: Path 'platforms/ios/build/device/SheetJSIonic.ipa' not found
```
Inspecting `platforms/ios/build/`, the actual folder name was:
```bash
% ls platforms/ios/build
#highlight-next-line
Debug-iphoneos
```
To force `native-run` to use the device, the name must be found by inspecting
the output of `native-run ios --list`:
```bash
% native-run ios --list
Connected Devices:
Name API Target ID
---------------------------------------------
SheetJS iOS 15.6 12345678-90ABCDEF12345678
```
`native-run` accepts a `--device` flag. Pass the device name:
```bash
native-run ios --app platforms/ios/build/Debug-iphoneos/SheetJSIonic.ipa --device SheetJS
npm i --save cordova-android
```
:::
17) Test the app.
:::caution pass
The app will fetch a file and display the contents in a table.
In some tests, the build failed with a Gradle error:
Tap "Export Data" to create a file. To find the file, switch to the "Files" app
and browse "On My iPhone" > "SheetJSIonic". There should be a new spreadsheet
named "SheetJSIonic".
```
Could not find an installed version of Gradle either in Android Studio,
or on your system to install the gradle wrapper. Please include gradle
in your path or install Android Studio
```
Switch to the "Numbers" app and open that file. Tap "EDIT" to make changes.
Change cell A7 to "SheetJS Dev" and cell B7 to 47. Tap "Done" and close the app.
On macOS, this issue was resolved by installing gradle with Homebrew manager:
Switch back to "SheetJSIonic" and tap "Import Data". Tap "Choose Files" in the
popup. Tap "Browse" in the bottom of the sheet. Navigate to "On My iPhone" >
"SheetJSIonic" and tap the new "SheetJSIonic" spreadsheet. The screen will show
the file with the new line.
```bash
brew install gradle
```
:::
:::warning pass
When the demo was last tested on Android, reading files worked as expected.
However, the generated files were not externally visible from the Files app.
**This is a known bug with Android SDK 33 and the underlying file plugins!**
:::
[^1]: See ["Array of Arrays" in the API reference](/docs/api/utilities/array#array-of-arrays)
[^2]: See [`ion-grid`](https://ionicframework.com/docs/api/grid) in the Ionic documentation.
@ -687,6 +488,4 @@ the file with the new line.
[^6]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input)
[^7]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`.
[^8]: See [`write` in "Writing Files"](/docs/api/write-options)
[^9]: See ["Developing for iOS"](https://ionic-docs-o31kiyk8l-ionic1.vercel.app/docs/v6/developing/ios) and ["Developing for Android"](https://ionic-docs-o31kiyk8l-ionic1.vercel.app/docs/v6/developing/android). The Ionic team removed these pages from the official docs site and recommend the `vercel.app` docs site.
[^10]: See the [JDK Archive](https://jdk.java.net/archive/) for Java 17 JDK download links.
[^11]: See ["Installing manually"](https://gradle.org/install/#manually) in the Gradle documentation.
[^9]: See ["Developing for iOS"](https://ionicframework.com/docs/v6/developing/ios) and ["Developing for Android"](https://ionicframework.com/docs/v6/developing/android) in the v6 Ionic framework documentation.

@ -1,6 +1,5 @@
---
title: Storing Sheets with CapacitorJS
sidebar_label: CapacitorJS
title: CapacitorJS
pagination_prev: demos/static/index
pagination_next: demos/desktop/index
sidebar_position: 5
@ -11,17 +10,10 @@ sidebar_custom_props:
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
[CapacitorJS](https://capacitorjs.com/) is a mobile app runtime for building iOS
and Android apps.
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
imported from any component or script in the app.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses CapacitorJS and SheetJS to process data and export spreadsheets.
We'll explore how to load SheetJS in an CapacitorJS app and use APIs and plugins
to extract data from, and write data to, spreadsheet files on the device.
The ["Demo"](#demo) creates an app that looks like the screenshots below:
The "Complete Example" creates an app that looks like the screenshots below:
<table><thead><tr>
<th><a href="#demo">iOS</a></th>
@ -40,62 +32,40 @@ The ["Demo"](#demo) creates an app that looks like the screenshots below:
This demo was tested in the following environments:
**Real Devices**
| OS | Device | CapacitorJS + FS | Date |
|:-----------|:--------------------|:------------------|:-----------|
| Android 30 | NVIDIA Shield | `6.2.0` / `6.0.3` | 2025-01-19 |
| iOS 15.1 | iPad Pro | `6.2.0` / `6.0.3` | 2025-01-19 |
**Simulators**
| OS | Device | CapacitorJS + FS | Dev Platform | Date |
|:-----------|:--------------------|:------------------|:-------------|:-----------|
| Android 34 | Pixel 3a | `7.1.0` / `7.0.0` | `darwin-arm` | 2025-03-30 |
| iOS 18.2 | iPhone 16 Pro Max | `7.1.0` / `7.0.0` | `darwin-arm` | 2025-03-30 |
| Android 35 | Pixel 9 Pro XL | `7.3.0` / `7.1.1` | `win11-x64` | 2025-06-08 |
| Android 35 | Pixel 9 | `6.2.0` / `6.0.2` | `linux-x64` | 2025-01-02 |
| OS | Type | Device | CapacitorJS + FS | Date |
|:-----------|:-----|:--------------------|:------------------|:-----------|
| Android 34 | Sim | Pixel 3a | `5.5.1` / `5.1.4` | 2023-12-04 |
| iOS 17.0.1 | Sim | iPhone 15 Pro Max | `5.5.1` / `5.1.4` | 2023-12-04 |
| Android 29 | Real | NVIDIA Shield | `5.5.1` / `5.1.4` | 2023-12-04 |
| iOS 15.1 | Real | iPad Pro | `5.5.1` / `5.1.4` | 2023-12-04 |
:::
:::danger Telemetry
:::warning Telemetry
Before starting this demo, manually disable telemetry. On Linux and MacOS:
```bash
npx -y @capacitor/cli telemetry off
npx @capacitor/cli telemetry off
```
To verify telemetry was disabled:
```bash
npx -y @capacitor/cli telemetry
npx @capacitor/cli telemetry
```
:::
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
imported from any component or script in the app.
This demo uses [SvelteJS](/docs/demos/frontend/svelte), but the same principles
apply to other frameworks.
This example uses Svelte, but the same principles apply to other frameworks.
#### Reading data
The standard [HTML5 File Input](/docs/demos/local/file#file-api) API works as
expected in CapacitorJS.
The standard HTML5 File Input element logic works in CapacitorJS:
Apps will typically include an `input type="file"` element. When the element is
activated, CapacitorJS will show a file picker. After the user selects a file,
the element will receive a `change` event.
The following example parses the selected file using the SheetJS `read`[^1]
method, generates an HTML table from the first sheet using `sheet_to_html`[^2],
and displays the table by setting the `innerHTML` attribute of a `div` element:
```html title="Sample component for data import"
```html
<script>
import { read, utils } from 'xlsx';
@ -121,39 +91,28 @@ async function importFile(evt) {
#### Writing data
Starting from a SheetJS workbook object[^3], the `write` method with the option
`type: "base64"`[^4] will generate Base64-encoded files.
`@capacitor/filesystem` can write Base64 strings:
The `@capacitor/filesystem` plugin can write Base64 strings to the device.
The following example uses the SheetJS `table_to_book` method[^5] to create a
workbook object from an HTML table. The workbook object is exported to the XLSX
format and written to the device.
```html title="Sample component for data export"
```html
<script>
import { Filesystem, Directory } from '@capacitor/filesystem';
import { utils, write } from 'xlsx';
import { utils, writeXLSX } from 'xlsx';
let html = "";
let tbl;
/* get state data and export to XLSX */
async function exportFile() {
/* generate workbook object from HTML table */
const elt = tbl.getElementsByTagName("TABLE")[0];
const wb = utils.table_to_book(elt);
/* generate Base64 string for Capacitor */
// highlight-start
/* export to XLSX encoded in a Base64 string */
const data = write(wb, { bookType: "xlsx", type: "base64" });
/* attempt to write to the device */
const data = writeXLSX(wb, { type: "base64" });
await Filesystem.writeFile({
data,
path: "SheetJSCap.xlsx",
directory: Directory.Documents
});
}); // write file
// highlight-end
}
@ -165,45 +124,11 @@ async function exportFile() {
</main>
```
:::caution pass
`Filesystem.writeFile` cannot overwrite existing files. Production apps should
attempt to delete the file before writing:
```js
/* attempt to delete file first */
try {
await Filesystem.deleteFile({
path: "SheetJSCap.xlsx",
directory: Directory.Documents
});
} catch(e) {}
/* attempt to write to the device */
await Filesystem.writeFile({
data,
path: "SheetJSCap.xlsx",
directory: Directory.Documents
});
```
:::
## Demo
The app in this demo will display data in a table.
When the app is launched, a [test file](https://docs.sheetjs.com/pres.numbers)
will be fetched and processed.
When a document is selected with the file picker, it will be processed and the
table will refresh to show the contents.
"Export XLSX" will attempt to export the table data to `SheetJSCap.xlsx` in the
app Documents folder. An alert will display the location of the file.
### Base Project
0) Follow the official "Environment Setup"[^6] instructions to set up Android
0) Follow the official "Environment Setup"[^1] instructions to set up Android
and iOS targets
:::caution pass
@ -212,25 +137,22 @@ iOS development is only supported on macOS.
:::
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
<details><summary><b>Installation Notes</b> (click to show)</summary>
For Android development, CapacitorJS requires a Java version compatible with the
expected Gradle version. When this demo was tested against CapacitorJS `6.2.0`,
Java 20 was required to support Gradle `8.2.1`.
CapacitorJS requires Java 17.
</details>
1) Disable telemetry.
```bash
npx -y @capacitor/cli telemetry off
npx @capacitor/cli telemetry off
```
Verify that telemetry is disabled by running
```bash
npx -y @capacitor/cli telemetry
npx @capacitor/cli telemetry
```
(it should print `Telemetry is off`)
@ -258,14 +180,14 @@ npm run build
:::note pass
If prompted to create an Ionic account, type `N` and press <kbd>Enter</kbd>.
If prompted to create an Ionic account, type `N` and press Enter.
:::
5) Download [`src/App.svelte`](pathname:///cap/App.svelte) and replace:
```bash
curl -o src/App.svelte https://docs.sheetjs.com/cap/App.svelte
curl -o src/App.svelte -L https://docs.sheetjs.com/cap/App.svelte
```
### Android
@ -277,20 +199,6 @@ npm i --save @capacitor/android
npx cap add android
```
:::caution pass
If the wrong Java version is installed, the last command will fail with a
message that references a "class file major version"
```
> BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 67
```
The correct Java version must be installed. When this demo was last tested, Java
20 was compatible with CapacitorJS Android projects.
:::
7) Enable file reading and writing in the Android app.
Add the highlighted lines to `android/app/src/main/AndroidManifest.xml` after
@ -307,9 +215,7 @@ the `Permissions` comment:
<uses-permission android:name="android.permission.INTERNET" />
```
8) Start the Android simulator through Android Studio.
9) Run the app in the simulator:
8) Run the app in the simulator
```bash
npm run build
@ -317,66 +223,27 @@ npx cap sync
npx cap run android
```
The app should look like the screenshot at the top of the page.
9) Test the app
10) Test the export functionality.
Open the app and observe that presidents are listed in the table.
Touch "Export XLSX". If the emulator asks for permission, tap "Allow". A popup
will show the exported path.
Touch "Export XLSX" and the emulator will ask for permission:
![Export Confirmation Popup](pathname:///cap/and-export-popup.png)
Tap "Allow" and a popup will be displayed with a path.
Open the "Files" app in the simulator, tap the `≡` icon and tap "Documents". Tap
the "Documents" folder to find `SheetJSCap.xlsx`.
<details open>
<summary><b>Downloading the generated file</b> (click to hide)</summary>
The file location can be found by searching for `SheetJSCap.xlsx`:
```bash
adb exec-out find / -name SheetJSCap.xlsx
```
There may be a number of error messages that start with `find:`. There will be
at least one line starting with `/`:
```text
/data/media/0/Documents/SheetJSCap.xlsx
/storage/emulated/0/Documents/SheetJSCap.xlsx
```
The `/storage` path can be pulled using `adb pull`:
```bash
adb pull "/storage/emulated/0/Documents/SheetJSCap.xlsx" SheetJSCap.xlsx
```
`SheetJSCap.xlsx` can be opened with a spreadsheet editor such as Excel.
</details>
11) Test the import functionality.
Edit `SheetJSCap.xlsx`, setting cell `A7` to `SheetJS Dev` and setting cell `B7`
to `47`. Save the file.
Click and drag the file into the Android emulator window. The file will be
uploaded to a Downloads folder in the emulator.
Switch back to the app and tap "Choose File". In the selector, tap `≡`, select
"Downloads" and tap `SheetJSCap.xlsx`. The table will refresh with the new row.
To see the generated file, switch to the "Files" app in the simulator, tap the
`≡` icon and tap "Documents". Tap "Documents" folder to find `SheetJSCap.xlsx`.
### iOS
12) Create iOS app.
10) Create iOS app
```bash
npm i --save @capacitor/ios
npx cap add ios
```
13) Enable file sharing and make the documents folder visible in the iOS app.
11) Enable file sharing and make the documents folder visible in the iOS app.
The following lines must be added to `ios/App/App/Info.plist`:
```xml title="ios/App/App/Info.plist (add to file)"
@ -393,7 +260,7 @@ The following lines must be added to `ios/App/App/Info.plist`:
(The root element of the document is `plist` and it contains one `dict` child)
14) Run the app in the simulator.
12) Run the app in the simulator
```bash
npm run build
@ -401,66 +268,26 @@ npx cap sync
npx cap run ios
```
If prompted to select a target device, select "iPhone 16 Pro Max (simulator)".
If prompted to select a target device, select "iPhone 15 Pro Max (simulator)".
The app should look like the screenshot at the top of the page.
13) Test the app
15) Test the export functionality.
Open the app and observe that presidents are listed in the table.
Touch "Export XLSX" and a popup will be displayed.
![Export Confirmation Popup](pathname:///cap/ios-export-popup.png)
To see the generated file, switch to the "Files" app in the simulator and look
for `SheetJSCap.xlsx` in "On My iPhone" > "`sheetjs-cap`"
<details open>
<summary><b>Downloading the generated file</b> (click to hide)</summary>
The app files are available in the filesystem in `~/Library/Developer`. Open a
terminal and run the following command to find the file:
```bash
find ~/Library/Developer -name SheetJSCap.xlsx
```
</details>
16) Test the import functionality.
Edit `SheetJSCap.xlsx`, setting cell `A7` to `SheetJS Dev` and setting cell `B7`
to `47`. Save the file.
Click and drag the file into the iOS simulator window. The simulator will show a
picker for saving the file. Select the `sheetjs-cap` folder and tap "Save". If
prompted to "Replace Existing Items?", tap "Replace".
Switch back to the app and tap "Choose File". Tap "Choose File" in the popup.
In the picker, tap "Recents" and select the newest `SheetJSCap` file. The table
will refresh with the new data.
### Android Device
17) Connect an Android device using a USB cable.
14) Connect an Android device using a USB cable.
If the device asks to allow USB debugging, tap "Allow".
18) Confirm the device is detected by `adb`.
15) Close any Android / iOS emulators.
```bash
adb devices
```
If the device is detected, the command will list the device:
```text title="Expected output"
List of devices attached
1234567890 device
```
19) Close any Android / iOS emulators.
20) Build APK and run on device:
16) Build APK and run on device:
```bash
npm run build
@ -471,13 +298,6 @@ npx cap run android
If the Android emulators are closed and an Android device is connected, the last
command will build an APK and install on the device.
:::note pass
In some tests, the last command asked for a target device. Select the Android
device in the list and press <kbd>Enter</kbd>
:::
:::caution pass
For real devices running API level 29 or below, the following line must be added
@ -499,19 +319,13 @@ to the `application` open tag in `android/app/src/main/AndroidManifest.xml`:
### iOS Device
21) Connect an iOS device using a USB cable
17) Connect an iOS device using a USB cable
If prompted to "Trust This Computer", tap "Trust" and enter the device passcode.
18) Close any Android / iOS emulators.
22) Close any Android / iOS emulators.
19) Enable developer code signing certificates[^2]
23) Enable developer code signing certificates.
Open `ios/App/App.xcworkspace` in Xcode. Select the "Project Navigator" and
select the "App" project. In the main view, select "Signing & Capabilities".
Under "Signing", select a team in the dropdown menu.
24) Run on device:
19) Run on device:
```bash
npm run build
@ -521,38 +335,5 @@ npx cap run ios
When prompted to select a target device, select the real device in the list.
:::info pass
In some test runs, the build failed with a provisioning error:
```
error: Provisioning profile "iOS Team Provisioning Profile: com.sheetjs.cap" doesn't include the currently selected device "SheetJS Test Device" (identifier 12345678-9ABCDEF012345678). (in target 'App' from project 'App')
```
This error was resolved by manually selecting the device as the primary target
in the Xcode workspace.
:::
:::caution pass
In some tests, the app failed to launch with a "Untrusted Developer" error.
Switch to the Settings app and select General > VPN & Device Management. There
will be a new item in the "DEVELOPER APP" section. Tap the line and verify that
`sheetjs-cap` is listed in the screen. Tap "Trust" and tap "Trust" in the popup.
After trusting the certificate, re-run the app:
```bash
npx cap run ios
```
:::
[^1]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^2]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output)
[^3]: See ["Workbook Object"](/docs/csf/book)
[^4]: See [the "base64" type in "Writing Files"](/docs/api/write-options#output-type)
[^5]: See [`table_to_book` in "HTML" Utilities](/docs/api/utilities/html#create-new-sheet)
[^6]: See ["Environment Setup"](https://capacitorjs.com/docs/getting-started/environment-setup) in the CapacitorJS documentation.
[^1]: See ["Environment Setup"](https://capacitorjs.com/docs/getting-started/environment-setup) in the CapacitorJS documentation.
[^2]: The [Flutter documentation](https://docs.flutter.dev/get-started/install/macos?tab=ios15#enable-developer-code-signing-certificates) covers the instructions in more detail. The correct workspace is `ios/App/App.xcworkspace`

@ -10,13 +10,8 @@ sidebar_custom_props:
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
export const r = {style: {color:"red"}};
export const g = {style: {color:"green"}};
Dart[^1] + Flutter[^2] is a popular cross-platform app framework. JavaScript
code can be run through [embedded engines](/docs/demos/engines).
@ -46,24 +41,16 @@ The "Demo" creates an app that looks like the screenshots below:
This demo was tested in the following environments:
**Real Devices**
| OS | Device | Dart | Flutter | Date |
|:-----------|:------------------|:--------|:---------|:-----------|
| Android 34 | NVIDIA Shield | `3.7.2` | `3.29.2` | 2025-03-31 |
| iOS 15.6 | iPhone 13 Pro Max | `3.7.2` | `3.29.2` | 2025-03-31 |
**Simulators**
| OS | Device | Dart | Flutter | Dev Platform | Date |
|:-----------|:------------------|:--------|:---------|:-------------|:-----------|
| Android 35 | Pixel 9 Pro XL | `3.7.2` | `3.29.2` | `darwin-x64` | 2025-03-31 |
| iOS 18.3 | iPhone 16 Pro Max | `3.7.2` | `3.29.2` | `darwin-x64` | 2025-03-31 |
| Android 36 | Pixel 9 Pro XL | `3.7.2` | `3.29.3` | `win11-x64` | 2054-04-28 |
| OS | Type | Device | Dart | Flutter | Date |
|:-----------|:-----|:------------------|:--------|:---------|:-----------|
| Android 34 | Sim | Pixel 3a | `3.2.2` | `3.16.2` | 2023-12-04 |
| iOS 17.0.1 | Sim | iPhone 15 Pro Max | `3.2.2` | `3.16.2` | 2023-12-04 |
| Android 29 | Real | NVIDIA Shield | `3.2.2` | `3.16.2` | 2023-12-04 |
| iOS 15.1 | Real | iPad Pro | `3.2.2` | `3.16.2` | 2023-12-04 |
:::
:::danger Telemetry
:::warning Telemetry
Before starting this demo, manually disable telemetry. On MacOS:
@ -225,42 +212,22 @@ class SheetJSFlutterState extends State<SheetJSFlutter> {
Run `flutter doctor` and confirm the following items are checked:
<Tabs groupId="os">
<TabItem value="linux" label="Linux">
<pre>
<span {...g}>[✓]</span> Android toolchain - develop for Android devices (Android SDK version 34.0.0)
</pre>
</TabItem>
<TabItem value="macos" label="macOS" default>
<pre>
<span {...g}>[✓]</span> Android toolchain - develop for Android devices (Android SDK version 36.0.0)
<span {...g}>[✓]</span> Xcode - develop for iOS and macOS (Xcode 16.2)
</pre>
</TabItem>
<TabItem value="win" label="Windows">
<pre>
<span {...g}>[✓]</span> Android toolchain - develop for Android devices (Android SDK version 35.0.1)
</pre>
</TabItem>
</Tabs>
```
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.0.1)
[✓] Android Studio (version 2022.3)
```
(the actual version numbers may differ)
<details open>
<summary><b>Installation Notes</b> (click to hide)</summary>
<details open><summary><b>Installation Notes</b> (click to hide)</summary>
:::note pass
On first run, there may be a warning with "Android toolchain":
```
[!] Android toolchain - develop for Android devices (Android SDK version 35.0.1)
[!] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
! Some Android licenses not accepted. To resolve this, run: flutter doctor
--android-licenses
```
@ -294,11 +261,6 @@ In local testing, there were issues with the Android toolchain:
error: Android sdkmanager not found. Update to the latest Android SDK and ensure that the cmdline-tools are installed to resolve this.
```
Android Studio does not install `Android SDK Command-Line Tools` by default. It
must be installed manually.
Assuming the command-line tools are installed
This was fixed by switching to Java 20, installing `Android SDK 33`, and rolling
back to `Android SDK Command-Line Tools (revision: 10.0)`
@ -316,90 +278,28 @@ If Google Chrome is not installed, `flutter doctor` will show an issue:
If Chromium is installed, the environment variable should be manually assigned:
<Tabs groupId="os">
<TabItem value="linux" label="Linux">
The `CHROME_EXECUTABLE` environment variable should be set to the path to the
`chrome` binary. This path differs between distributions and package managers.
</TabItem>
<TabItem value="macos" label="macOS">
```bash
export CHROME_EXECUTABLE=/Applications/Chromium.app/Contents/MacOS/Chromium
```
</TabItem>
<TabItem value="win" label="Windows">
Type `env` in the search bar and select "Edit the system environment variables".
In the new window, click the "Environment Variables..." button.
In the new window, look for the "System variables" section and click "New..."
Set the "Variable name" to `CHROME_EXECUTABLE` and the value to the path to the
program. When this demo was last tested, Chromium was installed for the local
user at `C:\Users\USERNAME\AppData\Local\Chromium\Application\chrome.exe` .
Click "OK" in each window (3 windows) and restart your computer.
</TabItem>
</Tabs>
:::
</details>
List all available emulators:
```bash
flutter emulators
```
<Tabs groupId="os">
<TabItem value="linux" label="Linux">
There should be at least one `android` emulator:
Run `flutter emulators` and check for both `ios` and `android` emulators:
```
Id • Name • Manufacturer • Platform
Pixel_3a_API_35 • Pixel 3a API 35 • Google • android
apple_ios_simulator • iOS Simulator • Apple • ios
Pixel_3a_API_34 • Pixel 3a API 34 • Google • android
```
</TabItem>
<TabItem value="macos" label="macOS">
There should be at least one `android` emulator and one `ios` simulator:
```
Id • Name • Manufacturer • Platform
apple_ios_simulator • iOS Simulator • Apple • ios
Pixel_9_Pro_XL_API_35 • Pixel 9 Pro XL API 35 • Google • android
```
</TabItem>
<TabItem value="win" label="Windows">
There should be at least one `android` emulator:
```
Id • Name • Manufacturer • Platform
Pixel_9_Pro_XL • Pixel 9 Pro XL • Google • android
```
</TabItem>
</Tabs>
1) Disable telemetry. The following commands were confirmed to work:
1) Disable telemetry.
```bash
dart --disable-telemetry
dart --disable-analytics
flutter config --no-analytics
flutter config --disable-telemetry
```
### Base Project
@ -413,8 +313,7 @@ cd sheetjs_flutter
3) Start the Android emulator.
<details open>
<summary><b>Details</b> (click to hide)</summary>
<details open><summary><b>Details</b> (click to hide)</summary>
**Android Studio**
@ -429,59 +328,28 @@ List the available emulators with `flutter emulators`:
% flutter emulators
2 available emulators:
Id • Name • Manufacturer • Platform
apple_ios_simulator • iOS Simulator • Apple • ios
Pixel_9_Pro_XL_API_35 • Pixel 9 Pro XL API 35 • Google • android
^^^^^^^^^^^^^^^^^^^^^--- the first column is the name for `emulator avd`
apple_ios_simulator • iOS Simulator • Apple • ios
Pixel_3a_API_34 • Pixel 3a API 34 • Google • android
^^^^^^^^^^^^^^^--- the first column is the name
```
The first column shows the name that should be passed to `emulator -avd`. In a
previous test, the name was `Pixel_9_Pro_XL_API_35` and the launch command was:
previous test, the name was `Pixel_3a_API_34` and the launch command was:
```bash
emulator -avd Pixel_9_Pro_XL_API_35
emulator -avd Pixel_3a_API_34
```
:::note pass
If `emulator` cannot be found, the folder must be added to the system path.
<Tabs groupId="os">
<TabItem value="macos" label="macOS">
On macOS, `~/Library/Android/sdk/emulator/` is the typical location for the
`emulator` binary:
`emulator` binary. If it cannot be found, add the folder to `PATH`:
```bash
export PATH="$PATH":~/Library/Android/sdk/emulator
emulator -avd Pixel_9_Pro_XL_API_35
emulator -avd Pixel_3a_API_34
```
</TabItem>
<TabItem value="win" label="Windows">
The Android SDK folder can be found in the SDK manager in Android Studio. It is
typically `%LOCALAPPDATA%\Android\Sdk`.
If it is not assigned, create a User environment variable named `ANDROID_HOME`
with the value set to the Android SDK folder.
---
There are three folders within the Android SDK folder that should be added to
the User `PATH` environment variable. Each folder holds a different tool:
| Folder | Command-line Tool |
|:------------------------------------------|:------------------|
| `%ANDROID_HOME%\emulator` | `emulator` |
| `%ANDROID_HOME%\cmdline-tools\latest\bin` | `avdmanager` |
| `%ANDROID_HOME%\platform-tools` | `adb` |
</TabItem>
</Tabs>
:::
</details>
@ -492,8 +360,7 @@ the User `PATH` environment variable. Each folder holds a different tool:
flutter run
```
<details>
<summary><b>If emulator is not detected</b> (click to show)</summary>
<details><summary><b>If emulator is not detected</b> (click to show)</summary>
In some test runs, `flutter run` did not automatically detect the emulator.
@ -534,32 +401,6 @@ Once the app loads, stop the terminal process and close the simulator.
flutter pub add http csv flutter_js
```
:::info pass
The command may fail in Windows with the following message:
<pre {...r}>
Building with plugins requires symlink support.
Please enable Developer Mode in your system settings. Run
{` `}start ms-settings:developers
to open settings.
</pre>
As stated, "Developer Mode" must be enabled:
1) Run `start ms-settings:developers`
2) In the panel, enable "Developer Mode" and click "Yes" in the popup.
3) Reinstall dependencies:
```bash
flutter pub add http csv flutter_js
```
:::
6) Open `pubspec.yaml` with a text editor. Search for the line that starts with
`flutter:` (no whitespace) and add the highlighted lines:
@ -583,50 +424,12 @@ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js
cd ..`}
</CodeBlock>
:::caution pass
PowerShell `curl` is incompatible with the official `curl` program. The command
may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'LO'.
```
`curl.exe` must be used instead:
<CodeBlock language="bash">{`\
mkdir -p scripts
cd scripts
curl.exe -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl.exe -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js
cd ..`}
</CodeBlock>
:::
8) Download [`main.dart`](pathname:///flutter/main.dart) to `lib/main.dart`:
```bash
curl -L -o lib/main.dart https://docs.sheetjs.com/flutter/main.dart
```
:::caution pass
PowerShell `curl` is incompatible with the official `curl` program. The command
may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'L'.
```
`curl.exe` must be used instead:
```bash
curl.exe -L -o lib/main.dart https://docs.sheetjs.com/flutter/main.dart
```
:::
### Android
9) Start the Android emulator using the same instructions as Step 3.
@ -637,53 +440,12 @@ curl.exe -L -o lib/main.dart https://docs.sheetjs.com/flutter/main.dart
flutter run
```
The app fetches https://docs.sheetjs.com/pres.numbers, parses, converts data to
an array of arrays, and presents the data in a Flutter `Table` widget.
<details>
<summary><b>If emulator is not detected</b> (click to show)</summary>
In some test runs, `flutter run` did not automatically detect the emulator.
Run `flutter -v -d sheetjs run` and the command will fail. Inspect the output:
```text title="Command output"
// highlight-next-line
[ +6 ms] No supported devices found with name or id matching 'sheetjs'.
[ ] The following devices were found:
...
// highlight-next-line
[ +26 ms] sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 13 (API 33) (emulator)
[ ] macOS (desktop) • macos • darwin-arm64 • macOS 13.5.1 22G90 darwin-arm64
...
```
Search the output for `sheetjs`. After that line, search for the emulator list.
One of the lines will correspond to the running emulator:
```
[ +26 ms] sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 13 (API 33) (emulator)
^^^^^^^^^^^^^--- the second column is the name
```
The second column is the device name. Assuming the name is `emulator-5554`, run:
```bash
flutter -v -d emulator-5554 run
```
</details>
The app fetches <https://sheetjs.com/pres.numbers>, parses, converts data to an
array of arrays, and presents the data in a Flutter `Table` widget.
:::caution pass
In some test runs on low-power devices, it took 20 seconds for the app to fetch
and display data!
:::
:::info Troubleshooting
In some demo runs, the build failed with an Android SDK error:
When the demo was last run, there was a build error:
```
│ The plugin flutter_js requires a higher Android SDK version. │
@ -709,57 +471,6 @@ Searching for `minSdkVersion` should reveal the following line:
minSdkVersion 21
```
---
In some demo runs, the build failed with an Android NDK error:
```
Your project is configured with Android NDK 26.3.11579264, but the following plugin(s) depend on a different Android NDK version:
- flutter_js requires Android NDK 27.0.12077973
Fix this issue by using the highest Android NDK version (they are backward compatible).
Add the following to /.../android/app/build.gradle.kts:
android {
ndkVersion = "27.0.12077973"
...
}
```
This was fixed by editing `android/app/build.gradle.kts`.
Searching for `ndkVersion` should reveal the following line:
```text title="android\app\build.gradle.kts"
ndkVersion = flutter.ndkVersion
```
`flutter.ndkVersion` should be replaced with `27.0.12077973`:
```text title="android\app\build.gradle.kts"
ndkVersion = "27.0.12077973"
```
---
In some demo runs, the build failed with an Android namespace error:
```
A problem occurred configuring project ':flutter_js'.
> Could not create an instance of type com.android.build.api.variant.impl.LibraryVariantBuilderImpl.
> Namespace not specified. Specify a namespace in the module's build file: /Users/sheetjs/.pub-cache/hosted/pub.dev/flutter_js-0.8.2/android/build.gradle. See https://d.android.com/r/tools/upgrade-assistant/set-namespace for information about setting the namespace.
```
This affects `flutter_js` version `0.8.2`.
The file (`flutter_js-0.8.2/android/build.gradle`) should be manually edited. In
the `android` block, add a `namespace` field:
```text title="cached flutter_js android/build.gradle (add highlighted line)"
android {
// highlight-next-line
namespace "io.abner.flutter_js"
```
:::
11) Close the Android emulator.
@ -774,8 +485,8 @@ android {
flutter run
```
The app fetches https://docs.sheetjs.com/pres.numbers, parses, converts data to
an array of arrays, and presents the data in a Flutter `Table` widget.
The app fetches <https://sheetjs.com/pres.numbers>, parses, converts data to an
array of arrays, and presents the data in a Flutter `Table` widget.
### Android Device
@ -792,7 +503,7 @@ flutter devices
The list should include the device:
```
SheetJS (mobile) • 726272627262726272 • android-arm64 • Android 11 (API 30)
SheetJS (mobile) • 1234567890 • android-arm64 • Android 10 (API 29)
^^^^^^^--- the first column is the name
```
@ -802,37 +513,13 @@ The list should include the device:
flutter build apk --release
```
:::info Troubleshooting
In some demo runs, the build failed with an Android resource error:
```
Execution failed for task ':flutter_js:verifyReleaseResources'.
> A failure occurred while executing com.android.build.gradle.tasks.VerifyLibraryResourcesTask$Action
> Android resource linking failed
ERROR: /private/tmp/sheetjs_flutter/build/flutter_js/intermediates/merged_res/release/mergeReleaseResources/values/values.xml:194: AAPT: error: resource android:attr/lStar not found.
```
The file (`flutter_js-0.8.2/android/build.gradle`) should be manually edited. In
the `android` block, force the `compileSdkVersion` to be `31`:
```text title="cached flutter_js android/build.gradle (add highlighted line)"
android {
// highlight-next-line
compileSdkVersion 31
```
:::
17) Install on the Android device:
```bash
flutter install
```
:::note pass
The script may ask for a device:
The script will ask for a device:
```
[1]: SheetJS (1234567890)
@ -844,9 +531,7 @@ Please choose one (or "q" to quit):
Select the number corresponding to the device.
:::
18) Launch the installed `sheetjs_flutter` app on the device.
18) Launch the installed `sheetjs_flutter` app on the device
:::caution pass
@ -869,7 +554,7 @@ flutter devices
The list should include the device:
```
SheetPad (mobile) • 00000000-0000000000000000 • ios • iOS 15.1 19B74
SheetPad (mobile) • 00000000-0000000000000000 ios • iOS 15.1 19B74
^^^^^^^^--- the first column is the name
```
@ -879,67 +564,8 @@ The list should include the device:
flutter run -d SheetPad
```
In debug mode, "Flutter tools" will attempt to connect to the running app. The
device will ask for permission:
> "Sheetjs Flutter" would like to find and connect to devices on your local network.
Tap "OK" to continue.
:::info pass
In some test runs, the app requested for local network access:
> "Sheetjs Flutter" would like to find and connect to devices on your local network.
Local network access is not required for the demo. Select "Don't Allow".
:::
:::caution pass
When this demo was last tested, the build failed with an error:
```text
Could not build the precompiled application for the device.
Error (Xcode): No profiles for 'com.example.sheetjsFlutter' were found: Xcode couldn't find any iOS App Development provisioning profiles matching 'com.example.sheetjsFlutter'. Automatic signing is disabled and unable to generate a profile. To enable automatic signing, pass -allowProvisioningUpdates to xcodebuild.
```
The message includes a hint:
```
Verify that the Bundle Identifier in your project is your signing id in Xcode
open ios/Runner.xcworkspace
```
Open the workspace and select the "Runner" project in the Navigator. In the main
pane, select "Signing &amp; Capabilities" and ensure a Team is selected. From
the menu bar, select "Product" > "Run" to run the app.
:::
:::info pass
If there is an "Untrusted Developer" error, the certificate must be trusted on
the device. The following steps were verified in iOS 15.1:
1) Open the "Settings" app on the device
In the "APPS FROM DEVELOPER" section of the new screen, "Sheetjs Flutter" should
be displayed. If it is missing, tap the "&lt;" button near the top of the screen
and select a different certificate from the list.
2) Select "General" > "VPN &amp; Device Management".
3) In the "DEVELOPER APP" section, tap the certificate that is "Not Trusted".
4) After confirming "Sheetjs Flutter" is in the list, tap the "Trust" button and
tap "Trust" in the popup.
:::
[^1]: https://dart.dev/ is the official site for the Dart Programming Language.
[^2]: https://flutter.dev/ is the official site for the Flutter Framework.
[^1]: <https://dart.dev/> is the official site for the Dart Programming Language.
[^2]: <https://flutter.dev/> is the official site for the Flutter Framework.
[^3]: [The `flutter_js` package](https://pub.dev/packages/flutter_js) is hosted on the Dart package repository.
[^4]: See [the dedicated "Swift + JavaScriptCore" demo](/docs/demos/engines/jsc) for more details.
[^5]: See [the dedicated "C + QuickJS" demo](/docs/demos/engines/quickjs) for more details.

@ -1,402 +0,0 @@
---
title: Sheets at Native Speed with Lynx
sidebar_label: Lynx
description: Build data-intensive mobile apps with Lynx. Seamlessly integrate spreadsheets into your app using SheetJS. Securely process and generate Excel files in the field.
pagination_prev: demos/static/index
pagination_next: demos/desktop/index
sidebar_position: 7
sidebar_custom_props:
summary: React + Native Rendering
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
export const r = {style: {color:"red"}};
export const g = {style: {color:"green"}};
export const y = {style: {color:"gold"}};
export const gr = {style: {color:"gray"}};
[Lynx](https://lynxjs.org/) is a modern cross-platform framework. It builds iOS,
Android and Web apps that use JavaScript for describing layouts and events.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
:::caution Lynx support is considered experimental.
Lynx is relatively new and does not currently have a deep community.
Any issues should be reported to the Lynx project for further diagnosis.
:::
This demo uses React (using [ReactLynx](https://lynxjs.org/react)) and SheetJS
to process and generate spreadsheets. We'll explore how to load SheetJS in Lynx
apps in the following scenarios:
- ["Fetching Remote Data"](#fetching-remote-data) uses the built-in `fetch` to download
and parse remote workbook files.
The "Fetching Remote Data" example creates an app that looks like the screenshots below:
<table><thead><tr>
<th><a href="#demo">iOS</a></th>
<th><a href="#demo">Android</a></th>
</tr></thead><tbody><tr><td>
![iOS screenshot](pathname:///lynx/react_lynx_fetch_demo_ios_1.jpeg)
</td><td>
![Android screenshot](pathname:///lynx/react_lynx_fetch_demo_android_1.png)
</td></tr></tbody></table>
:::caution pass
**Before testing this demo, follow the official React Lynx Guide!**[^1]
Follow the instructions for iOS (requires macOS) and for Android. They will
cover installation and system configuration. You should be able to build and run
a sample app in the Android and the iOS (if applicable) simulators.
:::
:::danger pass
**Lynx development requires an Apple Silicon-powered Macintosh!**
[X64 is currently unsupported.](https://github.com/lynx-family/lynx/issues/219)
:::
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
imported from any component or script in the app.
### Internal State
For simplicity, this demo uses an "Array of Arrays"[^2] as the internal state.
<table>
<thead><tr><th>Spreadsheet</th><th>Array of Arrays</th></tr></thead>
<tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
</td><td>
```js
[
["Name", "Index"],
["Bill Clinton", 42],
["GeorgeW Bush", 43],
["Barack Obama", 44],
["Donald Trump", 45],
["Joseph Biden", 46]
]
```
</td></tr></tbody></table>
Each array represents a row in the table.
This demo also keeps track of the column widths as a single array of numbers.
The widths are used by the display component.
```ts title="State variables"
const [data, setData] = useState<any[]>([
"SheetJS".split(""),
[5, 4, 3, 3, 7, 9, 5],
[8, 6, 7, 5, 3, 0, 9]
]);
const [widths, setWidths] = useState<number[]>(Array.from({ length: 7 }, () => 20));
```
#### Updating State
Starting from a SheetJS worksheet object, `sheet_to_json`[^3] with the `header`
option can generate an array of arrays:
```js title="Updating state from a workbook"
/* assuming `wb` is a SheetJS workbook */
function update_state(wb) {
/* convert first worksheet to AOA */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = utils.sheet_to_json(ws, {header:1});
/* update state */
setData(data);
/* update column widths */
setWidths(make_width(data));
}
```
_Calculating Column Widths_
Column widths can be calculated by walking each column and calculating the max
data width. Using the array of arrays:
```js title="Calculating column widths"
/* this function takes an array of arrays and generates widths */
function make_width(aoa) {
/* walk each row */
aoa.forEach((r) => {
/* walk each column */
r.forEach((c, C) => {
/* update column width based on the length of the cell contents */
res[C] = Math.max(res[C]||60, String(c).length * 10);
});
});
/* use a default value for columns with no data */
for(let C = 0; C < res.length; ++C) if(!res[C]) res[C] = 60;
return res;
}
```
### Displaying Data
Lynx does not ship with a component for displaying tabular data.
The demo uses Lynx `<view/>` and `<text/>` elements to display tabular data:
```tsx title="Example JSX for displaying data"
{/* Table container */}
<view className='Table'>
{/* Map through each row in the data array */}
{data.map((row, rowIndex) => (
<view key={`row-${rowIndex}`} className="Row">
{/* Map through each cell in the current row */}
{Array.isArray(row) && row.map((cell, cellIndex) => (
{/* Cell with dynamic width based on content */}
<view
key={`cell-${rowIndex}-${cellIndex}`} className="Cell"
style={{ width: `${widths[cellIndex]}px` }}>
{/* Display cell content as text */}
<text>{String(cell)}</text>
</view>
))}
</view>
))}
</view>
```
## Fetching Remote Data
This snippet downloads and parses https://docs.sheetjs.com/pres.xlsx:
```js
/* fetch data into an ArrayBuffer */
const ab = await (await fetch("https://docs.sheetjs.com/pres.xlsx")).arrayBuffer();
/* parse data */
const wb = XLSX.read(ab);
```
### Fetch Demo
:::note Tested Deployments
This demo was tested in the following environments:
**Simulators**
| OS | Device | Lynx | LynxExplorer | Dev Platform | Date |
|:-----------|:--------------------|:---------|:-------------|:-------------|:-----------|
| Android 35 | Pixel 3a | `0.8.6` | `3.2.0-rc.1` | `darwin-arm` | 2025-03-26 |
| iOS 18.3 | iPhone 16 Pro | `0.8.6` | `3.2.0-rc.1` | `darwin-arm` | 2025-03-26 |
:::
:::danger Real Devices
When this demo was last tested, there was no simple standalone guide for running
Lynx apps on real devices.
:::
:::caution pass
First install Lynx by following the Guide![^1].
Make sure you can run a basic test app on your simulator before continuing!
:::
0) Install Lynx dependencies
1) Create project:
```bash
npm create rspeedy@0.8.6 -- -d SheetJSLynxFetch -t react-ts --tools biome
```
2) Install shared dependencies:
<CodeBlock language="bash">{`\
cd SheetJSLynxFetch
curl -o ./src/assets/SheetJS-logo.png https://docs.sheetjs.com/logo.png
npm i
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
3) Download [`App.tsx`](pathname:///lynx/App.tsx) into the `src` folder:
```bash
curl -o ./src/App.tsx https://docs.sheetjs.com/lynx/App.tsx
```
4) Download [`App.css`](pathname:///lynx/App.css) into the `src` folder:
```bash
curl -o ./src/App.css https://docs.sheetjs.com/lynx/App.css
```
<a name="step5"></a>
5) Start the development server:
```bash
npm run dev
```
Keep the window open.
#### Android
6) Start the Android emulator:
<Tabs>
<TabItem name="Android Studio" value="Android Studio">
In Android Studio, click "More actions" > "Virtual Device Manager". Look for the
emulated device in the list and click the ▶ button to play.
</TabItem>
<TabItem name="Android Studio" value="Command Line">
List the available emulators with `emulator -list-avds`:
```
shjs@sheetjs SheetJSLynxFetch % emulator -list-avds
Medium_Phone_API_35
^^^^^^^^^^^^^^^^^^^--- emulator name
```
The emulator name should be passed to `emulator -avd`. In a previous test, the
name was `Medium_Phone_API_35` and the launch command was:
```bash
emulator -avd Medium_Phone_API_35
```
:::note pass
On macOS, `~/Library/Android/sdk/emulator/` is the typical location
for the `emulator` binary. If it cannot be found, add the folder to `PATH`:
```bash
export PATH="$PATH":~/Library/Android/sdk/emulator
emulator -avd Medium_Phone_API_35
```
:::
</TabItem>
</Tabs>
7) Download the LynxExplorer[^4] APK.
The latest test used [`LynxExplorer-noasan-release.apk` for version `3.2.0-rc.1`](https://github.com/lynx-family/lynx/releases/download/3.2.0-rc.1/LynxExplorer-noasan-release.apk).
8) Drag and drop the APK into the Android emulator window.
The emulator will install LynxExplorer.
9) In the terminal window from [step 5](#step5), copy the HTTP link. It will be
printed below the QR code, as shown in the following screenshot:
![lynx live server link](pathname:///lynx/lynx_live_server_link.png)
10) In the emulator, open the "LynxExplorer" app.
11) In the **Enter Card URL** input field, paste the link. Tap **Go**.
The view will refresh. The app should look like the "Before" screenshot:
<table><thead><tr>
<th>Before</th>
<th>After</th>
</tr></thead><tbody><tr><td>
![before screenshot](pathname:///lynx/react_lynx_fetch_demo_android_1.png)
</td><td>
![after screenshot](pathname:///lynx/react_lynx_fetch_demo_android_2.png)
</td></tr></tbody></table>
12) Tap "Import data from a spreadsheet" and verify that the app shows new data.
The app should look like the "After" screenshot.
**iOS Testing**
:::danger pass
**iOS testing can only be performed on Apple hardware running macOS!**
Xcode and iOS simulators are not available on Windows or Linux.
:::
13) Download the LynxExplorer[^4] app tarball.
The latest test used [`LynxExplorer-arm64.app.tar.gz` for version `3.2.0-rc.1`](https://github.com/lynx-family/lynx/releases/download/3.2.0-rc.1/LynxExplorer-arm64.app.tar.gz).
14) Open `LynxExplorer-arm64.app.tar.gz` using Finder.
The tarball contains an app named `LynxExplorer-arm64` .
15) Launch the iOS Simulator.
16) Click and drag `LynxExplorer-arm64` into the Simulator window.
The simulator will install the "LynxExplorer" app.
17) Copy the HTTP link from the terminal window in [step 5](#step5).
![lynx live server link](pathname:///lynx/lynx_live_server_link.png)
18) Tap the "LynxExplorer" icon in the simulator to launch the app.
19) Tap the **Enter Card URL** input field and paste the link. Tap **Go**.
The view will refresh. The app should look like the "Before" screenshot:
<table><thead><tr>
<th>Before</th>
<th>After</th>
</tr></thead><tbody><tr><td>
![before screenshot](pathname:///lynx/react_lynx_fetch_demo_ios_1.jpeg)
</td><td>
![after screenshot](pathname:///lynx/react_lynx_fetch_demo_ios_2.jpeg)
</td></tr></tbody></table>
20) Tap "Import data from a spreadsheet" and verify that the app shows new data. The app should look like the "After" screenshot.
[^1]: Follow ["Quick Start"](https://lynxjs.org/guide/start/quick-start.html) in
the Lynx documentation and select the appropriate "Lynx Explorer sandbox"
[^2]: See ["Array of Arrays" in the API reference](/docs/api/utilities/array#array-of-arrays)
[^3]: See ["Array Output" in "Utility Functions"](/docs/api/utilities/array#array-output)
[^4]: See ["LynxExplorer sandbox"](https://github.com/lynx-family/lynx/releases/latest/)

@ -1,28 +1,18 @@
---
title: Tables on Tablets and Mobile Devices
sidebar_label: iOS and Android Apps
title: iOS and Android Apps
pagination_prev: demos/static/index
pagination_next: demos/desktop/index
hide_table_of_contents: true
---
import EngineData from '/data/mobile.js'
import DocCardList from '@theme/DocCardList';
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
Many mobile app frameworks mix JavaScript / CSS / HTML5 concepts with native
extensions and libraries to create a hybrid development experience. Developers
well-versed in web technologies can now build actual mobile applications that
run on iOS and Android!
The demos in this section showcase a number of mobile frameworks. In each case,
we will build a sample app that loads SheetJS library scripts and processes
on-device and remote spreadsheet files.
:::danger pass
:::warning pass
**The ecosystem has broken backwards-compatibility many times!**
@ -34,6 +24,10 @@ MacOS is required for the iOS demos. The Android demos were tested on MacOS.
:::
The ["JavaScript Engines"](/docs/demos/engines) section includes samples for JS
engines used in the mobile app frameworks. SheetJS libraries have been tested
in the relevant engines and should "just work" with some caveats.
Demos for common tools are included in separate pages. Each demo section will
mention test dates and platform versions.
@ -46,14 +40,6 @@ mention test dates and platform versions.
</li>);
})}</ul>
:::info pass
The ["JavaScript Engines"](/docs/demos/engines) section includes samples for JS
engines used in the mobile app frameworks. SheetJS libraries have been tested
in the relevant engines.
:::
:::note Recommendation
React Native is extremely popular and is the recommended choice for greenfield
@ -61,9 +47,9 @@ projects that can use community modules. However, its "lean core" approach
forces developers to learn iOS/Android programming or use community modules to
provide basic app features.
The original Web View framework was PhoneGap/Cordova. The modern frameworks are
built atop Cordova. Cordova is waning in popularity but it has a deep library of
community modules to solve many problems.
The original Web View framework was PhoneGap/Cordova. The modern frameworks
are built atop Cordova. Cordova is waning in popularity but it has a deep
library of community modules to solve many problems.
Before creating a new app, it is important to identify what features the app
should support and investigate community modules. If there are popular modules
@ -71,16 +57,3 @@ for features that must be included, or for teams that are comfortable with
native app development, React Native is the obvious choice.
:::
### Platforms
The following frameworks have been tested:
<EngineData/>
:::info pass
When this table was last updated, it was not possible to build an iOS app from
Linux or Windows. Android tooling runs on MacOS, Linux and Windows.
:::

@ -1,6 +1,5 @@
---
title: Electrified Sheets with Electron
sidebar_label: Electron
title: Electron
pagination_prev: demos/mobile/index
pagination_next: demos/cli/index
sidebar_position: 1
@ -11,14 +10,10 @@ sidebar_custom_props:
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
[Electron](https://www.electronjs.org/) is a modern toolkit for building desktop
apps. Electron apps use the same technologies powering Chromium and NodeJS.
The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported
from the main or the renderer thread.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
The ["Complete Example"](#complete-example) section covers a complete desktop
app to read and write workbooks. The app will look like the screenshots below:
The "Complete Example" creates an app that looks like the screenshots below:
<table><thead><tr>
<th><a href="#complete-example">Windows</a></th>
@ -40,22 +35,8 @@ app to read and write workbooks. The app will look like the screenshots below:
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
imported from the main or the renderer thread.
The SheetJS `readFile` and `writeFile` methods will use the Electron `fs` module
where available.
<details>
<summary><b>Renderer Configuration</b> (click to show)</summary>
Electron 9 and later require the preference `nodeIntegration: true` in order to
`require('xlsx')` in the renderer process.
Electron 12 and later also require `worldSafeExecuteJavascript: true` and
`contextIsolation: true`.
</details>
Electron presents a `fs` module. The `require('xlsx')` call loads the CommonJS
module, so `XLSX.readFile` and `XLSX.writeFile` work in the renderer thread.
### Reading Files
@ -207,12 +188,12 @@ This demo was tested in the following environments:
| OS and Version | Architecture | Electron | Date |
|:---------------|:-------------|:---------|:-----------|
| macOS 15.3 | `darwin-x64` | `35.1.2` | 2025-03-31 |
| macOS 14.5 | `darwin-arm` | `35.1.2` | 2025-03-30 |
| Windows 11 | `win11-x64` | `33.2.1` | 2025-02-11 |
| Windows 11 | `win11-arm` | `33.2.1` | 2025-02-23 |
| Linux (HoloOS) | `linux-x64` | `33.2.1` | 2025-01-02 |
| Linux (Debian) | `linux-arm` | `33.2.1` | 2025-02-16 |
| macOS 14.4 | `darwin-x64` | `29.1.4` | 2024-03-15 |
| macOS 14.1.2 | `darwin-arm` | `27.1.3` | 2023-12-01 |
| Windows 10 | `win10-x64` | `28.2.0` | 2024-03-04 |
| Windows 11 | `win11-arm` | `27.1.3` | 2023-12-01 |
| Linux (HoloOS) | `linux-x64` | `29.1.4` | 2024-03-21 |
| Linux (Debian) | `linux-arm` | `27.1.3` | 2023-12-01 |
:::
@ -250,26 +231,6 @@ curl -LO https://docs.sheetjs.com/electron/index.html
curl -LO https://docs.sheetjs.com/electron/index.js
```
:::note pass
In PowerShell, the command may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'LO'.
```
`curl.exe` must be invoked directly:
```bash
curl.exe -LO https://docs.sheetjs.com/electron/package.json
curl.exe -LO https://docs.sheetjs.com/electron/main.js
curl.exe -LO https://docs.sheetjs.com/electron/index.html
curl.exe -LO https://docs.sheetjs.com/electron/index.js
```
:::
2) Install dependencies:
```bash
@ -282,7 +243,7 @@ npm install
npx -y electron .
```
The app will run.
The app will show.
4) To build a standalone app, run the builder:
@ -307,18 +268,15 @@ The program will run on ARM64 Windows.
### Testing
5) Download [the test file `pres.numbers`](https://docs.sheetjs.com/pres.numbers)
5) Download [the test file `pres.numbers`](https://sheetjs.com/pres.numbers)
6) Launch the generated application:
| Architecture | Command |
|:-------------|:--------------------------------------------------------------|
| `darwin-x64` |`open ./out/sheetjs-electron-darwin-x64/sheetjs-electron.app` |
| `darwin-arm` |`open ./out/sheetjs-electron-darwin-arm64/sheetjs-electron.app`|
| `win11-x64` |`.\out\sheetjs-electron-win32-x64\sheetjs-electron.exe` |
| `win11-arm` |`.\out\sheetjs-electron-win32-x64\sheetjs-electron.exe` |
| `linux-x64` |`./out/sheetjs-electron-linux-x64/sheetjs-electron` |
| `linux-arm` |`./out/sheetjs-electron-linux-arm64/sheetjs-electron` |
| `darwin-x64` | `open ./out/sheetjs-electron-darwin-x64/sheetjs-electron.app` |
| `win10-x64` | `.\out\sheetjs-electron-win32-x64\sheetjs-electron.exe` |
| `linux-x64` | `./out/sheetjs-electron-linux-x64/sheetjs-electron` |
#### Electron API
@ -360,8 +318,8 @@ and select `pres.numbers`.
## Electron Breaking Changes
The first version of this demo used Electron `1.7.5`. The current demo includes
the required changes for Electron `35.1.2`.
The first version of this demo used Electron 1.7.5. The current demo includes
the required changes for Electron 28.2.0.
There are no Electron-specific workarounds in the library, but Electron broke
backwards compatibility multiple times. A summary of changes is noted below.
@ -375,7 +333,6 @@ methods have been renamed:
|:-----------------|:---------------------|
| `showOpenDialog` | `showOpenDialogSync` |
| `showSaveDialog` | `showSaveDialogSync` |
**This change was not properly documented!**
Electron 9 and later require the preference `nodeIntegration: true` in order to
@ -389,4 +346,4 @@ Electron 14 and later must use `@electron/remote` instead of `remote`. An
:::
[^1]: See ["Makers"](https://www.electronforge.io/config/makers) in the Electron Forge documentation. On Linux, the demo generates `rpm` and `deb` distributables. On Arch Linux and the Steam Deck, `sudo pacman -Syu rpm-tools dpkg fakeroot` installed required packages. On Debian and Ubuntu, `sudo apt-get install rpm` sufficed.
[^1]: See ["Makers"](https://www.electronforge.io/config/makers) in the Electron Forge documentation. On Linux, the demo generates `rpm` and `deb` distributables. On Arch Linux and the Steam Deck, `sudo pacman -Syu rpm-tools dpkg fakeroot` installed required packages.

@ -1,6 +1,5 @@
---
title: Sheets in NW.js
sidebar_label: NW.js
title: NW.js
pagination_prev: demos/mobile/index
pagination_next: demos/cli/index
sidebar_position: 2
@ -13,14 +12,10 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
[NW.js](https://nwjs.io/), formerly `node-webkit`, is a modern toolkit for
building desktop apps using web technologies.
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
can be referenced in a `SCRIPT` tag from the entry point HTML page.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
The ["Complete Example"](#complete-example) section covers a complete desktop
app to read and write workbooks. The app will look like the screenshots below:
The "Complete Example" creates an app that looks like the screenshots below:
<table><thead><tr>
<th><a href="#complete-example">Windows</a></th>
@ -42,9 +37,6 @@ app to read and write workbooks. The app will look like the screenshots below:
## Integration Details
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
can be referenced in a `SCRIPT` tag from the entry point HTML page.
NW.js provides solutions for reading and writing files.
### Reading Files
@ -119,14 +111,16 @@ input.click();
This demo was tested in the following environments:
| OS and Version | Architecture | NW.js | Date | Notes |
|:---------------|:-------------|:----------|:-----------|:---------------------|
| macOS 15.3.2 | `darwin-x64` | `0.94.0` | 2025-03-31 | |
| macOS 14.5 | `darwin-arm` | `0.94.0` | 2025-03-30 | |
| Windows 11 | `win11-x64` | `0.100.0` | 2025-05-27 | |
| Windows 11 | `win11-arm` | `0.94.0` | 2025-02-23 | |
| Linux (HoloOS) | `linux-x64` | `0.89.0` | 2025-01-10 | |
| Linux (Debian) | `linux-arm` | `0.60.0` | 2025-02-16 | Unofficial build[^1] |
| OS and Version | Architecture | NW.js | Date |
|:---------------|:-------------|:---------|:-----------|
| macOS 14.3.1 | `darwin-x64` | `0.85.0` | 2024-03-12 |
| macOS 14.1.2 | `darwin-arm` | `0.82.0` | 2023-12-01 |
| Windows 10 | `win10-x64` | `0.83.0` | 2024-03-04 |
| Windows 11 | `win11-arm` | `0.82.0` | 2023-12-01 |
| Linux (HoloOS) | `linux-x64` | `0.85.0` | 2024-03-12 |
There is no official Linux ARM64 release. The community release[^1] was tested
and verified on 2023-09-27.
:::
@ -146,7 +140,7 @@ cd sheetjs-nwjs
"version": "0.0.0",
"main": "index.html",
"dependencies": {
"nw": "0.100.0",
"nw": "0.85.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz"
}
}`}
@ -167,22 +161,6 @@ In the terminal window, the download can be performed with:
curl -LO https://docs.sheetjs.com/nwjs/index.html
```
:::note pass
In PowerShell, the command may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'L'.
```
`curl.exe` must be invoked directly:
```bash
curl.exe -LO https://docs.sheetjs.com/nwjs/index.html
```
:::
3) Install dependencies:
```bash
@ -195,56 +173,15 @@ npm i
npx nw .
```
On launch, the app will fetch and parse https://docs.sheetjs.com/pres.numbers .
Using the file input element, a file can be selected from the filesystem and the
table will refresh with the contents of the selected file.
Click "Export Data!" and save the generated file to `SheetJSNWDemo.xlsx`. This
file can be opened in Excel or another spreadsheet editor.
<details>
<summary><b>Linux ARM64 support</b> (click to show)</summary>
NW.js does not officially support `linux-arm`. The official recommendation is to
use a third-party pre-built version.
```bash
curl -LO https://github.com/LeonardLaszlo/nw.js-armv7-binaries/releases/download/nw60-arm64_2022-01-08/nw60-arm64_2022-01-08.tar.gz
tar -xzf nw60-arm64_2022-01-08.tar.gz
cp usr/docker/dist/nwjs-chromium-ffmpeg-branding/nwjs-v0.60.1-linux-arm64.tar.gz
tar -xzf nwjs-v0.60.1-linux-arm64.tar.gz
./nwjs-v0.60.1-linux-arm64/nw .
```
Unfortunately `nw-builder` will not be able to build a standalone program.
</details>
The app will show and you should be able to verify reading and writing by using
the file input element to select a spreadsheet and clicking the export button.
5) To build a standalone app, run the builder:
```bash
npx -p nw-builder@4.11.6 nwbuild --mode=build --version=0.100.0 --glob=false --outDir=../out ./
npx -p nw-builder nwbuild --mode=build --version=0.85.0 --glob=false --outDir=../out ./
```
This will generate the standalone app in the `..\out\` folder.
:::caution pass
There is a regression in `nw-builder` versions `4.12.0` and `4.13.14`.
In local `win11-x64` testing, `4.11.6` generates the standalone application.
:::
6) Launch the generated application:
| Architecture | Command |
|:-------------|:--------------------------------------------------------------|
| `darwin-x64` | `open ../out/sheetjs-nwjs.app` |
| `darwin-arm` | `open ../out/sheetjs-nwjs.app` |
| `win11-x64` | `..\out\sheetjs-nwjs.exe` |
| `win11-arm` | `..\out\sheetjs-nwjs.exe` |
| `linux-x64` | `../out/sheetjs-nwjs` |
[^1]: The [`nw60-arm64_2022-01-08` release](https://github.com/LeonardLaszlo/nw.js-armv7-binaries/releases/tag/nw60-arm64_2022-01-08) included an ARM64 version of `nw`.

@ -33,7 +33,7 @@ app to read and write workbooks. The app will look like the screenshots below:
<th><a href="#complete-example">Linux</a></th>
</tr></thead><tbody><tr><td>
![Windows screenshot](pathname:///wails/win11.png)
![Windows screenshot](pathname:///wails/win10.png)
</td><td>
@ -54,21 +54,6 @@ platform provides many native features out of the box.
:::
:::note Tested Deployments
This demo was tested in the following environments:
| OS and Version | Architecture | Wails | Date |
|:---------------|:-------------|:----------|:-----------|
| macOS 15.3.2 | `darwin-x64` | `v2.10.1` | 2025-03-31 |
| macOS 14.5 | `darwin-arm` | `v2.10.1` | 2025-03-30 |
| Windows 11 | `win11-x64` | `v2.10.1` | 2025-05-27 |
| Windows 11 | `win11-arm` | `v2.10` | 2025-02-23 |
| Linux (HoloOS) | `linux-x64` | `v2.9.2` | 2025-01-02 |
| Linux (Debian) | `linux-arm` | `v2.10` | 2025-02-16 |
:::
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
@ -306,10 +291,24 @@ async function exportFile(table_element) {
## Complete Example
:::note Tested Deployments
This demo was tested in the following environments:
| OS and Version | Architecture | Wails | Date |
|:---------------|:-------------|:---------|:-----------|
| macOS 14.4 | `darwin-x64` | `v2.8.0` | 2024-03-15 |
| macOS 14.1.2 | `darwin-arm` | `v2.6.0` | 2023-12-01 |
| Windows 10 | `win10-x64` | `v2.8.0` | 2024-03-24 |
| Windows 11 | `win11-arm` | `v2.6.0` | 2023-12-01 |
| Linux (HoloOS) | `linux-x64` | `v2.8.0` | 2024-03-21 |
| Linux (Debian) | `linux-arm` | `v2.6.0` | 2023-12-01 |
:::
0) Read the Wails "Getting Started" guide[^14] and install dependencies.
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
<details><summary><b>Installation Notes</b> (click to show)</summary>
Wails will require:
@ -334,7 +333,7 @@ On macOS and Linux, the `PATH` environment variable must include `~/go/bin`. If
`wails` cannot be found, run the following command in the terminal session:
```bash
export PATH="$PATH":~/go/bin
export PATH="$PATH:~/go/bin"
```
:::
@ -358,7 +357,7 @@ None of the optional packages are required for building and running this demo.
On the Steam Deck (HoloOS), some dependencies must be reinstalled:
```bash
sudo pacman -Syu base-devel gtk3 glib2 pango harfbuzz cairo gdk-pixbuf2 atk libsoup webkit2gtk
sudo pacman -Syu base-devel gtk3 glib2 pango harfbuzz cairo gdk-pixbuf2 atk libsoup
```
:::
@ -400,15 +399,11 @@ wails build
It will print the path to the generated program (typically in `build/bin/`).
5) Run the generated application:
| Architecture | Command |
|:-------------|:------------------------------------------|
| `win11-x64` | `.\build\bin\sheetjs-wails.exe` |
5) Run the generated application.
**Testing**
The program will download [`pres.xlsx`](https://docs.sheetjs.com/pres.xlsx) and
The program will download [`pres.xlsx`](https://sheetjs.com/pres.xlsx) and
display the contents of the first worksheet in a table.
To test export features, click "Export XLSX". The app will ask for a file name

@ -9,16 +9,13 @@ sidebar_custom_props:
summary: Webview + Rust Backend
---
# Data Wrangling in Tauri Apps
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
export const c = {style: {color:"cyan"}};
export const y = {style: {color:"gold"}};
export const g = {style: {color:"green"}};
export const B = {style: {fontWeight:"bold"}};
[Tauri](https://tauri.app/) is a modern toolkit for building desktop apps. Tauri
apps leverage platform-native browser engines to build lightweight programs.
@ -38,7 +35,7 @@ app to read and write workbooks. The app will look like the screenshots below:
<th><a href="#complete-example">Linux</a></th>
</tr></thead><tbody><tr><td>
![Windows screenshot](pathname:///tauri/win11.png)
![Windows screenshot](pathname:///tauri/win10.png)
</td><td>
@ -50,44 +47,21 @@ app to read and write workbooks. The app will look like the screenshots below:
</td></tr></tbody></table>
:::note Tested Deployments
This demo was tested in the following environments:
| OS and Version | Architecture | Tauri | Date |
|:---------------|:-------------|:----------|:-----------|
| macOS 15.3.2 | `darwin-x64` | `v1.6.0` | 2025-03-31 |
| macOS 14.5 | `darwin-arm` | `v1.6.0` | 2025-03-30 |
| Windows 11 | `win11-x64` | `v1.6.0` | 2025-05-27 |
| Windows 11 | `win11-arm` | `v1.6.0` | 2025-02-23 |
| Linux (HoloOS) | `linux-x64` | `v1.6.0` | 2025-01-02 |
| Linux (Debian) | `linux-arm` | `v1.6.0` | 2025-05-27 |
:::
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
installed and imported from JavaScript code.
:::info pass
The following explanation applies to Tauri 1.
The `allowlist` security model was abandoned in Tauri 2.
:::
:::note pass
Tauri 1.x does not provide the equivalent of NodeJS `fs` module. The raw
Tauri currently does not provide the equivalent of NodeJS `fs` module. The raw
`@tauri-apps/api` methods used in the examples are not expected to change.
:::
For security reasons, Tauri apps must explicitly enable system features.[^1]
They are enabled in `src-tauri/tauri.conf.json` in the `allowlist` subsection of
the `tauri` section of the configuration file.
the `tauri` section of the config.
- The `fs` entitlement[^2] enables reading and writing file data.
@ -236,7 +210,7 @@ function SheetJSImportKaioponent() {
<table><tbody>{data.map((row) =>
<tr>{row.map((cell) => <td>{cell}</td>)}</tr>
)}</tbody></table>
</> );
</>);
}
```
@ -355,6 +329,7 @@ function SheetJSExportKaioponent() {
return ( <button type="button" onclick={save_callback}>Save Data</button> );
}
```
</TabItem>
@ -362,15 +337,29 @@ function SheetJSExportKaioponent() {
## Complete Example
:::note Tested Deployments
This demo was tested in the following environments:
| OS and Version | Architecture | Tauri | Date |
|:---------------|:-------------|:----------|:-----------|
| macOS 14.4 | `darwin-x64` | `v1.5.11` | 2024-03-15 |
| macOS 14.0 | `darwin-arm` | `v1.5.2` | 2023-10-18 |
| Windows 10 | `win10-x64` | `v1.5.11` | 2024-03-24 |
| Windows 11 | `win11-arm` | `v1.5.7` | 2023-12-01 |
| Linux (HoloOS) | `linux-x64` | `v1.5.11` | 2024-03-21 |
| Linux (Debian) | `linux-arm` | `v1.5.7` | 2023-12-01 |
:::
0) Read Tauri "Getting Started" guide and install prerequisites.[^16]
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
<details><summary><b>Installation Notes</b> (click to show)</summary>
At a high level, the following software is required for building Tauri apps:
- a native platform-specific C/C++ compiler (for example, macOS requires Xcode)
- a browser engine integration (for example, Linux requires `webkit2gtk`)
- a browser engine integration (for example, linux requires `webkit2gtk`)
- [Rust](https://www.rust-lang.org/tools/install)
The platform configuration can be verified by running:
@ -382,20 +371,18 @@ npx @tauri-apps/cli info
If required dependencies are installed, the output will show a checkmark next to
"Environment". The output from the most recent macOS test is shown below:
<pre>
<span {...g}>[✔]</span> <span style={{...y.style,...B.style}}>Environment</span>
{` `}<span {...c}>-</span> <span {...B}>OS</span>: Mac OS 14.5.0 arm64 (X64)
{` `}<span {...g}></span> <span {...B}>Xcode Command Line Tools</span>: installed
{` `}<span {...g}></span> <span {...B}>rustc</span>: 1.87.0 (17067e9ac 2025-05-09)
{` `}<span {...g}></span> <span {...B}>cargo</span>: 1.87.0 (99624be96 2025-05-06)
{` `}<span {...g}></span> <span {...B}>rustup</span>: 1.28.2 (e4f3ad6f8 2025-04-28)
{` `}<span {...g}></span> <span {...B}>Rust toolchain</span>: stable-aarch64-apple-darwin (default)
{` `}<span {...c}>-</span> <span {...B}>node</span>: 20.18.0
{` `}<span {...c}>-</span> <span {...B}>pnpm</span>: 9.12.3
{` `}<span {...c}>-</span> <span {...B}>npm</span>: 10.8.2
{` `}<span {...c}>-</span> <span {...B}>bun</span>: 1.2.14
{` `}<span {...c}>-</span> <span {...B}>deno</span>: deno 2.3.3
</pre>
```
[✔] Environment
- OS: Mac OS 14.4.0 X64
✔ Xcode Command Line Tools: installed
✔ rustc: 1.76.0 (07dca489a 2024-02-04)
✔ cargo: 1.76.0 (c84b36747 2024-01-18)
✔ rustup: 1.27.0 (bbb9276d2 2024-03-08)
✔ Rust toolchain: stable-x86_64-apple-darwin (default)
- node: 20.11.1
- npm: 10.2.4
- bun: 1.0.31
```
:::caution pass
@ -412,7 +399,7 @@ build step will correctly detect the platform architecture.
<TabItem value="vuejs" label="VueJS">
```bash
npm create tauri-app@3.x -- -m npm -t vue-ts SheetJSTauri -y
npm create tauri-app@latest -- -m npm -t vue-ts SheetJSTauri -y
```
</TabItem>
@ -426,7 +413,7 @@ TypeScript template and manually wires Kaioken
:::
```bash
npm create tauri-app@3.x -- -m npm -t vanilla-ts SheetJSTauri -y
npm create tauri-app@latest -- -m npm -t vanilla-ts SheetJSTauri -y
```
</TabItem>
@ -447,8 +434,6 @@ npm i --save-dev @tauri-apps/cli`}
</TabItem>
<TabItem value="kaioken" label="Kaioken" default>
Install the Kaioken dependencies:
```bash
npm add kaioken --save
npm add vite-plugin-kaioken -D --save
@ -478,23 +463,14 @@ npm add vite-plugin-kaioken -D --save
// highlight-end
```
In the same file, look for `"title"` and change the value to `SheetJS x Tauri`:
In the same file, look for the `"identifier"` key and replace the value with `com.sheetjs.tauri`:
```json title="src-tauri/tauri.conf.json (edit highlighted line)"
"windows": [
{
// highlight-next-line
"title": "SheetJS x Tauri",
"width": 800,
```
In the same file, look for `"identifier"` and change the value to `com.sheetjs.tauri`:
```json title="src-tauri/tauri.conf.json (edit highlighted line)"
"targets": "all",
"icons/icon.ico"
],
// highlight-next-line
"identifier": "com.sheetjs.tauri",
"icon": [
"longDescription": "",
```
<Tabs groupId="framework">
@ -517,27 +493,36 @@ curl -o src/App.vue https://docs.sheetjs.com/tauri/App.vue
```ts title="vite.config.ts (add highlighted lines)"
import { defineConfig } from "vite";
// highlight-next-line
import kaioken from "vite-plugin-kaioken";
import kaioken from "vite-plugin-kaioken"
// https://vitejs.dev/config/
export default defineConfig(async () => ({
// highlight-start
esbuild: {
jsxInject: `import * as kaioken from "kaioken"`,
jsx: "transform",
jsxFactory: "kaioken.createElement",
jsxFragment: "kaioken.fragment",
loader: "tsx",
include: ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"],
},
plugins: [kaioken()],
// highlight-end
```
- Edit `tsconfig.json`. In `compilerOptions` add the option `"jsx": "preserve"`:
- Add the highlighted line to `tsconfig.json`:
```js title="tsconfig.json (add highlighted line)"
{
"compilerOptions": {
// highlight-next-line
"jsx": "preserve",
"target": "ES2020",
```
- Replace `index.html` with the following codeblock:
```html title="index.html (replace contents)"
```html title="index.html"
<!doctype html>
<html lang="en">
<head>
@ -574,7 +559,7 @@ table.center {
- Replace `src/main.ts` with the following codeblock:
```ts title="src/main.ts (replace contents)"
```ts title="src/main.ts"
import { mount } from "kaioken";
import App from "./App";
@ -613,7 +598,7 @@ If the build fails, see ["Troubleshooting"](#troubleshooting) for more details.
Depending on the version of Tauri, the command may be
```bash
./src-tauri/target/release/sheetjstauri
./src-tauri/target/release/SheetJSTauri
```
or
@ -625,7 +610,7 @@ or
or
```bash
./src-tauri/target/release/SheetJSTauri
./src-tauri/target/release/sheetjstauri
```
</TabItem>
@ -640,11 +625,10 @@ or
The following features should be manually verified:
- When it is loaded, the app will download https://docs.sheetjs.com/pres.numbers
- When it is loaded, the app will download <https://sheetjs.com/pres.numbers>
and display the data in a table.
- Clicking "Save Data" will show a save dialog. If there is no filename, type
`SheetJSTauri.xlsb`. Click "Save". The app will write a file which can be
opened in a spreadsheet editor.
- Clicking "Save Data" will show a save dialog. After selecting a path and name,
the app will write a file. That file can be opened in a spreadsheet editor.
- Edit the file in a spreadsheet editor, then click "Load Data" and select the
edited file. The table will refresh with new contents.
@ -652,22 +636,6 @@ The following features should be manually verified:
:::note pass
In some tests, the build failed with the error message:
```
Search string not found: "/supportedTSExtensions = .*(?=;)/"
```
This is a known issue with `vue-tsc`. The dependency must be upgraded:
```bash
npm install vue-tsc@latest
```
:::
:::note pass
During the last Linux ARM64 test, the build failed to create an AppImage:
```
@ -697,7 +665,7 @@ sudo pacman -S openssl
:::note pass
In some macOS tests, the build failed with the following error message:
During the last macOS test, the build failed with the following error message:
```
Error failed to bundle project: error running bundle_dmg.sh
@ -722,14 +690,6 @@ select "Automation" in the body. Look for "Terminal", expand the section, and en
:::
:::note pass
In some tests, the fonts did not match the screenshots.
**The Inter font static TTFs must be manually downloaded and installed.**[^17]
:::
[^1]: See ["Security"](https://tauri.app/v1/references/architecture/security#allowing-api) in the Tauri documentation
[^2]: See [`FsAllowlistConfig`](https://tauri.app/v1/api/config/#fsallowlistconfig) in the Tauri documentation
[^3]: See [`DialogAllowlistConfig`](https://tauri.app/v1/api/config/#dialogallowlistconfig) in the Tauri documentation
@ -745,5 +705,4 @@ In some tests, the fonts did not match the screenshots.
[^13]: See [`fs`](https://tauri.app/v1/api/js/fs#writebinaryfile) in the Tauri documentation
[^14]: See ["Array of Arrays Input" in "Utility Functions"](/docs/api/utilities/array#array-of-arrays-input)
[^15]: See ["Workbook Helpers" in "Utility Functions"](/docs/api/utilities/wb)
[^16]: See ["Prerequisites"](https://tauri.app/v1/guides/getting-started/prerequisites) in the Tauri documentation
[^17]: Click "Get font" in the [Inter Google Fonts listing](https://fonts.google.com/specimen/Inter)
[^16]: See ["Prerequisites"](https://tauri.app/v1/guides/getting-started/prerequisites) in the Tauri documentation

@ -33,7 +33,7 @@ app to read and write workbooks. The app will look like the screenshots below:
<th><a href="#complete-example">Linux</a></th>
</tr></thead><tbody><tr><td>
![Windows screenshot](pathname:///neu/win11.png)
![Windows screenshot](pathname:///neu/win10.png)
</td><td>
@ -45,24 +45,6 @@ app to read and write workbooks. The app will look like the screenshots below:
</td></tr></tbody></table>
:::note Tested Deployments
This demo was tested in the following environments:
| OS and Version | Architecture | Server | Client | Date |
|:---------------|:-------------|:---------|:---------|:-----------|
| macOS 15.3.2 | `darwin-x64` | `6.0.0` | `6.0.0` | 2025-03-31 |
| macOS 14.5 | `darwin-arm` | `6.0.0` | `6.0.0` | 2025-03-30 |
| Windows 11 | `win11-x64` | `6.1.0` | `6.1.0` | 2025-05-27 |
| Windows 11 | `win11-arm` | `5.6.0` | `5.6.0` | 2025-02-23 |
| Linux (HoloOS) | `linux-x64` | `5.5.0` | `5.5.0` | 2025-01-02 |
| Linux (Debian) | `linux-arm` | `5.6.0` | `5.6.0` | 2025-02-16 |
NeutralinoJS on Windows on ARM generates X64 binaries that run using the X64
compatibility layer. The binaries are not native ARM64 programs!
:::
## Integration Details
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
@ -204,14 +186,28 @@ const save_button_callback = async() => {
## Complete Example
:::note Tested Deployments
This demo was tested in the following environments:
| OS and Version | Architecture | Server | Client | Date |
|:---------------|:-------------|:---------|:---------|:-----------|
| macOS 14.4 | `darwin-x64` | `5.0.0` | `5.0.1` | 2024-03-15 |
| macOS 14.0 | `darwin-arm` | `4.14.1` | `3.12.0` | 2023-10-18 |
| Windows 10 | `win10-x64` | `5.1.0` | `5.1.0` | 2024-03-24 |
| Windows 11 | `win11-arm` | `4.14.1` | `3.12.0` | 2023-12-01 |
| Linux (HoloOS) | `linux-x64` | `5.0.0` | `5.0.1` | 2024-03-21 |
| Linux (Debian) | `linux-arm` | `4.14.1` | `3.12.0` | 2023-12-01 |
:::
The app core state will be the HTML table. Reading files will add the table to
the window. Writing files will parse the table into a spreadsheet.
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
<details><summary><b>Installation Notes</b> (click to show)</summary>
NeutralinoJS uses `portable-file-dialogs`[^12] to show open and save dialogs. On
Linux, a dialog box helper (Zenity or KDialog) must be installed.
Linux, Zenity or KDialog are require.
The last Debian test was run on a system using LXDE. KDialog is supported but
must be explicitly installed:
@ -247,22 +243,6 @@ subdirectory in the `sheetjs-neu` folder:
curl -L -o resources/js/xlsx.full.min.js https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}
</CodeBlock>
:::note pass
In PowerShell, the command may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'L'.
```
`curl.exe` must be invoked directly:
<CodeBlock language="bash">{`\
curl.exe -L -o resources/js/xlsx.full.min.js https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}
</CodeBlock>
:::
3) Add the highlighted line to `neutralino.config.json` in `nativeAllowList`:
```json title="neutralino.config.json (add highlighted line)"
@ -322,7 +302,7 @@ table {
6) Print the version number in the `showInfo` method of `resources/js/main.js`:
```js title="resources/js/main.js (add highlighted lines)"
```js title="resources/js/main.js"
function showInfo() {
document.getElementById('info').innerHTML = `
${NL_APPID} is running on port ${NL_PORT} inside ${NL_OS}
@ -348,18 +328,14 @@ npx @neutralinojs/neu run
```js title="resources/js/main.js (add to end)"
(async() => {
const ab = await (await fetch("https://docs.sheetjs.com/pres.numbers")).arrayBuffer();
const ab = await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer();
const wb = XLSX.read(ab);
const ws = wb.Sheets[wb.SheetNames[0]];
document.getElementById('info').innerHTML = XLSX.utils.sheet_to_html(ws);
})();
```
9) Close the app. Run the app again:
```bash
npx @neutralinojs/neu run
```
9) Close the app and relaunch the app with `npx @neutralinojs/neu run`
When the app loads, a table should show in the main screen.
@ -394,11 +370,7 @@ async function exportData() {
}
```
11) Close the app. Run the app again:
```bash
npx @neutralinojs/neu run
```
11) Close the app and re-run with `npx @neutralinojs/neu run`
When the app loads, click the "Import File" button and select a spreadsheet to
see the contents.
@ -425,18 +397,8 @@ save as `SheetJSNeu` will not automatically add the `.xlsx` extension!
npx @neutralinojs/neu build
```
Platform-specific programs will be created in the `dist` folder:
| Platform | Path to binary |
|:-------------|:---------------------------------------------|
| `darwin-x64` | `./dist/sheetjs-neu/sheetjs-neu-mac_x64` |
| `darwin-arm` | `./dist/sheetjs-neu/sheetjs-neu-mac_arm64` |
| `win11-x64` | `.\dist\sheetjs-neu\sheetjs-neu-win_x64.exe` |
| `win11-arm` | `.\dist\sheetjs-neu\sheetjs-neu-win_x64.exe` |
| `linux-x64` | `./dist/sheetjs-neu/sheetjs-neu-linux_x64` |
| `linux-arm` | `./dist/sheetjs-neu/sheetjs-neu-linux_arm64` |
Run the generated app and confirm that Presidential data is displayed.
Platform-specific programs will be created in the `dist` folder. For example,
the `darwin-arm` program will be `./dist/sheetjs-neu/sheetjs-neu-mac_arm64`
[^1]: See [`nativeAllowList`](https://neutralino.js.org/docs/configuration/neutralino.config.json#nativeallowlist-string) in the NeutralinoJS documentation
[^2]: See [`os.showOpenDialog`](https://neutralino.js.org/docs/api/os#osshowopendialogtitle-options) in the NeutralinoJS documentation

Some files were not shown because too many files have changed in this diff Show More