Compare commits

..

2 Commits

355 changed files with 3138 additions and 13446 deletions

1
.gitignore vendored

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

@ -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

@ -139,10 +139,6 @@ 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
header text. See Docusaurus issue 8568 for more details.

@ -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,3 +1,4 @@
import { read, utils } from 'xlsx';
import url from './cli.xls';
import React, { useEffect, useState } from 'react';
@ -5,9 +6,9 @@ const FrameworkData = () => {
const [fw, setFW] = useState("");
useEffect(() => { (async() => {
const html = await (await fetch(url)).json();
setFW(html["Frameworks"]);
const wb = read(await (await fetch(url)).arrayBuffer(), { dense: true });
setFW(utils.sheet_to_html(wb.Sheets["Frameworks"]));
})(); }, []);
return ( <p dangerouslySetInnerHTML={{__html: fw}}/> );
return ( <div dangerouslySetInnerHTML={{__html: fw}}/> );
};
export default FrameworkData;

@ -34,7 +34,7 @@
</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">
<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"/>
@ -79,7 +79,7 @@
<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>
@ -97,7 +97,7 @@
<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>
@ -106,17 +106,8 @@
<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>

@ -1,3 +1,4 @@
import { read, utils } from 'xlsx';
import url from './desktop.xls';
import React, { useEffect, useState } from 'react';
@ -5,9 +6,9 @@ const FrameworkData = () => {
const [fw, setFW] = useState("");
useEffect(() => { (async() => {
const html = await (await fetch(url)).json();
setFW(html["Frameworks"]);
const wb = read(await (await fetch(url)).arrayBuffer(), { dense: true });
setFW(utils.sheet_to_html(wb.Sheets["Frameworks"]));
})(); }, []);
return ( <p dangerouslySetInnerHTML={{__html: fw}}/> );
return ( <div dangerouslySetInnerHTML={{__html: fw}}/> );
};
export default FrameworkData;

@ -1,13 +1,22 @@
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}}/>
<p>Asterisks () in the Windows columns mark tests that were run in Windows Subsystem for Linux (WSL)</p>
</> );
};
export default EngineData;

@ -132,8 +132,8 @@
<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>
@ -244,7 +244,7 @@
</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="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"/>
@ -292,18 +292,8 @@
<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"/>
<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#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>
@ -337,26 +327,6 @@
<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">Swift</Data></Cell>
@ -367,16 +337,6 @@
<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">Ruby</Data></Cell>

@ -1,3 +1,4 @@
import { read, utils } from 'xlsx';
import url from './mobile.xls';
import React, { useEffect, useState } from 'react';
@ -5,9 +6,9 @@ const FrameworkData = () => {
const [fw, setFW] = useState("");
useEffect(() => { (async() => {
const html = await (await fetch(url)).json();
setFW(html["Frameworks"]);
const wb = read(await (await fetch(url)).arrayBuffer(), { dense: true });
setFW(utils.sheet_to_html(wb.Sheets["Frameworks"]));
})(); }, []);
return ( <p dangerouslySetInnerHTML={{__html: fw}}/> );
return ( <div dangerouslySetInnerHTML={{__html: fw}}/> );
};
export default FrameworkData;

@ -34,7 +34,7 @@
</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">
<Table ss:ExpandedColumnCount="8" ss:ExpandedRowCount="17" 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"/>
@ -79,7 +79,7 @@
<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>
@ -90,7 +90,7 @@
<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>
@ -99,7 +99,7 @@
<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>
@ -110,7 +110,7 @@
<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>
@ -121,18 +121,7 @@
<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>

@ -50,13 +50,14 @@ reading and writing many spreadsheet formats.
`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)
- XLSB / XLS / Lotus 1-2-3 / SpreadsheetML 2003 / Numbers file formats
- Stream utility functions
<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} -->
@ -75,7 +76,7 @@ strongly recommended. Vendoring decouples websites from SheetJS infrastructure.
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,8 +90,9 @@ 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>
@ -116,75 +118,12 @@ 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 VSCode Telemetry and Data Exfiltration
The official Microsoft builds of Visual Studio Code ("VSCode") embed telemetry
and send information to external servers.
**[VSCodium](https://vscodium.com/) is a telemetry-free fork of VSCode.**
When writing code that may process personally identifiable information (PII),
the SheetJS team strongly encourages building VSCode from source or using IDEs
that do not exfiltrate data.
:::
The type checker integrated in VSCodium and VSCode 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 VSCode!**
:::
## ECMAScript Module Imports
:::info pass
:::caution pass
This section refers to imports in HTML pages using `<script type="module">`.
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

@ -174,30 +174,10 @@ yarn remove 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>
<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">
@ -281,8 +261,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() {

@ -161,33 +161,12 @@ 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>
<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>
<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">
@ -239,7 +218,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 +230,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 +246,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';
@ -366,8 +285,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

@ -125,13 +125,25 @@ define(['N/file', 'sheetjs'], function(file, XLSX) {
## 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)
:::danger 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 +219,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>

@ -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';
@ -76,7 +76,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");
@ -119,16 +119,12 @@ builder requires a proper `package.json` that includes the SheetJS dependency.
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.
| Architecture | BunJS | Date |
|:-------------|:--------|:-----------|
| `darwin-x64` | `1.1.4` | 2024-04-19 |
| `win10-x64` | `1.1.4` | 2024-04-19 |
| `win11-x64` | `1.1.8` | 2024-05-17 |
| `linux-x64` | `1.1.4` | 2024-04-25 |
:::
@ -140,36 +136,10 @@ cd sheetjs-bun-dle
echo "{}" > package.json
```
:::caution PowerShell Encoding Errors
:::caution pass
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.
In PowerShell, the redirect will add the UTF16LE BOM. Bun does not currently
support the encoding. The file must be resaved in UTF8 (without BOM) or ASCII.
:::
@ -179,22 +149,6 @@ VSCodium will automatically re-save the file.
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"
@ -249,7 +203,7 @@ 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
```
@ -259,7 +213,6 @@ 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
@ -276,9 +229,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.

@ -942,6 +942,13 @@ of the React Native documentation before testing the demo.
:::
:::caution pass
For Android testing, React Native requires Java 11. It will not work with
current Java releases.
:::
:::danger pass
There are a number of potential pitfalls.
@ -957,10 +964,10 @@ any issues not mentioned in the demo page.**
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.73.6 init SheetJSPres --version="0.73.6"
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.19.8`}
</CodeBlock>
Save the following to `App.tsx` in the project:
@ -1053,10 +1060,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. The app will launch in the emulator.

@ -489,9 +489,8 @@ 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 2024.
The value (third value) will be positive. The following function tests a row
against the requirements:
in column `C`. The year will be between 2007 and 2024 and the value will be
positive. The following function tests a data row:
```js
const is_valid_row = r =>
@ -967,7 +966,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>
@ -1030,7 +1029,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 +1042,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 +1066,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

@ -20,8 +20,8 @@ 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.
generate CSV files for the LangChain 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.
@ -33,47 +33,40 @@ SheetJS Loader to answer questions based on data from a XLS workbook.
This demo was tested in the following configurations:
| Platform | Architecture | Date |
|:------------------------------------------------------------------|:-------------|:-----------|
| 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-01-12 |
| 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-01-24 |
| 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 M2 Max 12-Core CPU + 30-Core GPU (32 GB unified memory) | `darwin-arm` | 2025-03-25 |
| Date | Platform |
|:-----------|:--------------------------------------------------------------|
| 2024-07-15 | Apple M2 Max 12-Core CPU + 30-Core GPU (32 GB unified memory) |
| 2024-07-14 | NVIDIA RTX 4090 (24 GB VRAM) + i9-10910 (128 GB RAM) |
| 2024-07-14 | NVIDIA RTX 4080 SUPER (16 GB VRAM) + i9-10910 (128 GB RAM) |
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 |
| Demo | Platform |
|:------------|:-------------------------------------------------------------|
| LangChainJS | NVIDIA RTX 4070 Ti (12 GB VRAM) + Ryzen 7 5800x (64 GB RAM) |
| LangChainJS | NVIDIA RTX 4060 (8 GB VRAM) + Ryzen 7 5700g (32 GB RAM) |
| LangChainJS | NVIDIA RTX 3090 (24 GB VRAM) + Ryzen 9 3900XT (128 GB RAM) |
| LangChainJS | NVIDIA RTX 3080 (12 GB VRAM) + Ryzen 7 5800X (32 GB RAM) |
| LangChainJS | NVIDIA RTX 3060 (12 GB VRAM) + i5-11400 (32 GB RAM) |
| LangChainJS | NVIDIA RTX 2080 (12 GB VRAM) + i7-9700K (16 GB RAM) |
| LangChainJS | NVIDIA RTX 2060 (6 GB VRAM) + Ryzen 5 3600 (32 GB RAM) |
| LangChainJS | NVIDIA GTX 1080 (8 GB VRAM) + Ryzen 7 5800x (64 GB RAM) |
| LangChainJS | NVIDIA GTX 1070 (8 GB VRAM) + Ryzen 7 7700x (32 GB RAM) |
</details>
Special thanks to the following users for testing with multiple configurations:
Special thanks to:
- [Asadbek Karimov](https://asadk.dev/)
- [Rasmus Tengstedt](https://tengstedt.dev/)
- [`@Rasmus`](https://tengstedt.dev/)
- [Triston Armstrong](https://tristonarmstrong.com/)
- [Ben Halverson](https://benhalverson.dev/)
- [Navid Nami](https://github.com/CaseoJKL)
- [`@Smor`](https://smor.dev/)
- [`@timbr`](https://timbr.dev/)
- [`n3bs`](https://github.com/0xn3bs)
:::
@ -81,14 +74,14 @@ Special thanks to the following users for testing with multiple configurations:
:::note pass
This explanation was verified against LangChainJS 0.2.
This explanation was verified against LangChain 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:
LangChain 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";
@ -133,7 +126,7 @@ Index: 44
</td></tr></tbody>
</table>
The LangChainJS CSV loader will include source metadata in the document:
The LangChain CSV loader will include source metadata in the document:
```js title="Document generated by the CSV loader"
Document {
@ -145,7 +138,7 @@ Document {
## SheetJS Conversion
The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be
imported in NodeJS scripts that use LangChainJS and other JavaScript libraries.
imported in NodeJS scripts that use LangChain and other JavaScript libraries.
A simple pre-processing step can convert workbooks to CSV files that can be
processed by the existing CSV tooling:
@ -166,51 +159,14 @@ flowchart LR
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**
The SheetJS `readFile` method[^2] can read general workbooks. The method returns
a workbook object that conforms to the SheetJS data model[^3].
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**
`sheet_to_csv`[^4] method.
For example, the following NodeJS script reads `pres.xlsx` and displays CSV rows
from the first worksheet:
@ -232,8 +188,6 @@ 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:
@ -243,8 +197,8 @@ 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.
and [Mathematica](/docs/demos/extensions/mathematica) support CSV. 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.
@ -334,9 +288,9 @@ console.log(docs);
## 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.
The `CSVLoader` that ships with LangChain 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
@ -453,7 +407,7 @@ export default class LoadOfSheet extends BufferLoader {
### 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
assuming the UTF8 encoding. This corrupts many spreadsheet formats including
XLSX, XLSB, XLSM and XLS.
:::note pass
@ -463,15 +417,12 @@ 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.
- [Webpack Plugins](/docs/demos/static/webpack#sheetjs-loader) have a special
option to instruct the library to pass raw binary data rather than text.
:::
The `CSVLoader` extends a special `TextLoader` that forces UTF-8 text parsing.
The `CSVLoader` extends a special `TextLoader` that forces UTF8 text parsing.
There is a separate `BufferLoader` class, used by the PDF loader, that passes
the raw data using NodeJS `Buffer` objects.
@ -720,96 +671,26 @@ export default class LoadOfSheet extends BufferLoader {
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 ChatQA-1.5 model[^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.
SheetJS team members have tested this demo on Windows 10 and Windows 11 using
PowerShell and Ollama for Windows.
SheetJS users have also tested this demo within Windows Subsystem for Linux.
:::
:::danger pass
:::caution 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).
This demo was tested using the ChatQA-1.5 model[^9] in Ollama[^10].
<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>
The tested model used up to 9.2GB VRAM. It is strongly recommended to run the
demo on a newer Apple Silicon Mac or a PC with an Nvidia GPU with at least 12GB
VRAM. SheetJS users have tested the demo on systems with as little as 6GB VRAM.
:::
After installing dependencies, start a new terminal session.
1) Create a new project:
0) Create a new project:
```bash
mkdir sheetjs-loader
@ -817,7 +698,7 @@ cd sheetjs-loader
npm init -y
```
2) Download the demo scripts:
1) Download the demo scripts:
- [`loadofsheet.mjs`](pathname:///loadofsheet/loadofsheet.mjs)
- [`query.mjs`](pathname:///loadofsheet/query.mjs)
@ -844,30 +725,26 @@ curl.exe -LO https://docs.sheetjs.com/loadofsheet/loadofsheet.mjs
:::
3) Install the SheetJS NodeJS module:
2) Install the SheetJS NodeJS module:
<CodeBlock language="bash">{`\
npm i --save https://sheet.lol/balls/xlsx-${current}.tgz`}
</CodeBlock>
4) Install dependencies:
3) Install dependencies:
```bash
npm i --save @langchain/core@0.3.44 langchain@0.3.21 @langchain/ollama@0.2.0 peggy@3.0.2
npm i --save @langchain/community@0.2.18 @langchain/core@0.2.15 langchain@0.2.9 peggy@4.0.3 --force
```
:::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.44 langchain@0.3.21 @langchain/ollama@0.2.0 peggy@3.0.2 --force
```
When this demo was last tested, there were error messages relating to dependency
and peer dependency versions. The `--force` flag was required.
:::
5) Download the [cars dataset](pathname:///cd.xls):
4) Download the [cars dataset](pathname:///cd.xls):
```bash
curl -LO https://docs.sheetjs.com/cd.xls
@ -889,34 +766,20 @@ curl.exe -LO https://docs.sheetjs.com/cd.xls
:::
6) Install the `llama3-chatqa:8b-v1.5-q8_0` model using Ollama:
5) Install the `llama3-chatqa:8b-v1.5-q8_0` model using Ollama:
```bash
ollama pull llama3-chatqa:8b-v1.5-q8_0
```
<details>
<summary><b>Additional steps for Intel GPUs</b> (click to show)</summary>
:::note pass
A different embedding model must be used on Intel GPUs:
If the command cannot be found, install Ollama[^10] and run the command in a new
terminal window.
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
6) Run the demo script
```bash
node query.mjs
@ -975,4 +838,4 @@ charts, tables, and other features.
[^7]: See ["Workbook Object"](/docs/csf/book)
[^8]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^9]: See [the official ChatQA website](https://chatqa-project.github.io/) for the ChatQA paper and other model details.
[^10]: Select ["Windows" `x86_64`](https://conda-forge.org/download/) in the Installation page.
[^10]: See [the official Ollama website](https://ollama.com/download) for installation instructions.

@ -38,11 +38,10 @@ 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 126 | 2024-06-21 |
| Safari 17.4 | 2024-06-20 |
:::
@ -515,11 +514,10 @@ 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}>
@ -539,14 +537,6 @@ $= \frac{1}{m+1}\sum_{i=1}^{m+1} x_i$
<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}>
$= \frac{1}{m+1}\sum_{i=1}^{m} x_i + \frac{x_{m+1}}{m+1}$
</td>
@ -584,61 +574,22 @@ $= 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}>&nbsp;</td>
<td style={bs}>
$= M[x;m] + \frac{1}{m+1}(x_{m+1}-M[x;m])$
</td>
</tr>
</tbody>
</table>
---
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$:
<table style={bs}>
<tbody style={bs}>
<tr style={bs}>
<td style={bs}>
$x_1 = 0 + (x_1 - 0)$
$new\_mean$
</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])$
$= old\_mean + (x_{m+1}-old\_mean) / (m+1)$
</td>
</tr>
@ -646,52 +597,16 @@ $= M[x;m] + \frac{1}{m+1}(x_{m+1} - M[x;m])$
</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>
@ -837,10 +752,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 +768,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)

@ -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 131 | `1.1.2` | 2025-01-01 |
| Safari 18.2 | `1.1.2` | 2025-01-01 |
| Konqueror 22 | `1.1.2` | 2025-04-23 |
This example was last tested on 2024 April 25 against DanfoJS 1.1.2.
:::
@ -213,23 +207,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></> );
}
```

@ -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>
@ -30,153 +25,27 @@ results back to spreadsheets.
- ["JS Array Interchange"](#js-array-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 122 | `4.14.0` | 2024-04-07 |
| 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 lang="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.
@ -257,8 +126,6 @@ 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>
@ -319,8 +186,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 +234,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)
@ -520,19 +272,17 @@ Differences from the official example are highlighted below:
*/
async function getData() {
// highlight-start
/* fetch file and pull data into an ArrayBuffer */
/* fetch file */
const carsDataResponse = await fetch('https://docs.sheetjs.com/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 +306,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 +316,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 +336,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,38 +393,38 @@ 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
@ -724,5 +438,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,12 @@ 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.8 | 2024-12-21 |
| `win11-arm` | Duktape `2.7.0` | 2.2.3 | 3.13.2 | 2025-02-23 |
| `linux-x64` | Duktape `2.7.0` | 1.5.3 | 3.11.7 | 2025-01-01 |
| `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.2.2 | 3.12.3 | 2024-06-30 |
| `win10-x64` | Duktape `2.7.0` | 2.2.1 | 3.12.2 | 2024-03-25 |
| `win11-arm` | Duktape `2.7.0` | 2.2.2 | 3.11.5 | 2024-06-20 |
| `linux-x64` | Duktape `2.7.0` | 1.5.3 | 3.11.3 | 2024-03-21 |
| `linux-arm` | Duktape `2.7.0` | 1.5.3 | 3.11.2 | 2024-06-20 |
:::
@ -231,16 +231,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
@ -303,7 +295,7 @@ cd ..
```
</TabItem>
<TabItem value="win11-x64" label="Windows">
<TabItem value="win10-x64" label="Windows">
- Download and extract the source tarball. Commands must be run in WSL `bash`:
@ -322,7 +314,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
@ -370,7 +362,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 .
@ -434,7 +426,7 @@ 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`:
@ -527,12 +519,12 @@ 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.17.1 | 3.11.8 | 2024-12-21 |
| `win11-arm` | Duktape `2.7.0` | 1.23.0 | 3.13.2 | 2025-02-23 |
| `linux-x64` | Duktape `2.7.0` | 1.18.0 | 3.11.7 | 2025-01-01 |
| `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.31 | 3.12.3 | 2024-06-30 |
| `win10-x64` | Duktape `2.7.0` | 0.20.16 | 3.12.2 | 2024-03-25 |
| `win10-arm` | Duktape `2.7.0` | 0.20.31 | 3.11.5 | 2024-06-20 |
| `linux-x64` | Duktape `2.7.0` | 0.20.16 | 3.11.3 | 2024-03-21 |
| `linux-arm` | Duktape `2.7.0` | 0.20.31 | 3.11.2 | 2024-06-20 |
:::
@ -553,25 +545,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,15 +594,13 @@ 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.
---
It is recommended to use a virtual environment for Polars.
`venv` must be installed through the system package manager:
@ -642,8 +618,6 @@ sudo apt-get install python3.11-venv
brew install pyenv-virtualenv
```
---
After installing `venv`, the following commands set up the virtual environment:
```bash

@ -63,7 +63,7 @@ loaded into the site. This sheet will have known columns.
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:
object for each row, using the values in the first rows as keys:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
@ -448,9 +448,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.17.0` | `5.2.11` | 2024-05-21 |
:::
@ -554,7 +554,7 @@ 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() => {
@ -595,9 +595,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.17.0` | `5.2.11` | 2024-05-21 |
:::

@ -59,7 +59,7 @@ loaded into the site. This sheet will have known columns.
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:
object for each row, using the values in the first rows as keys:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
@ -274,16 +274,15 @@ 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() => {
@ -307,16 +306,16 @@ 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>);
}
```
@ -332,7 +331,7 @@ This demo was tested in the following environments:
| ReactJS | ViteJS | Date |
|:---------|:--------|:-----------|
| `18.3.1` | `6.0.1` | 2024-12-12 |
| `18.2.0` | `5.1.6` | 2024-03-13 |
:::
@ -384,19 +383,10 @@ This demo was tested in the following environments:
| ReactJS | CRA | Date |
|:---------|:--------|:-----------|
| `18.2.0` | `5.0.1` | 2024-12-12 |
| `18.2.0` | `5.0.1` | 2024-03-13 |
:::
:::caution pass
CRA has known compatibility issues with React 19[^5]. CRA no longer receives
updates and the ReactJS docs no longer recommend using CRA. For new projects, it
is strongly recommended to use ViteJS with the `react` or `react-ts` templates.
:::
1) Create a new site:
```bash
@ -408,7 +398,6 @@ npx -y create-react-app@5.0.1 --scripts-version=5.0.1 sheetjs-react
<CodeBlock language="bash">{`\
cd sheetjs-react
npm i
npm i react@18.2.0 react-dom@18.2.0 web-vitals --save --save-exact
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm start`}
</CodeBlock>
@ -446,7 +435,7 @@ This demo was tested in the following environments:
| ReactJS | NextJS | Date |
|:---------|:---------|:-----------|
| `19.0.0` | `15.1.0` | 2024-12-13 |
| `18.2.0` | `14.1.3` | 2024-03-13 |
:::
@ -491,34 +480,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";
```
@ -528,7 +509,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.
@ -549,75 +530,9 @@ and the page will attempt to download `SheetJSReactAoO.xlsx`.
npm run build
```
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.22.1` | `5.3.3` | 2024-12-17 |
:::
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.**
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:
6) Start a local web server:
```bash
npx http-server dist
@ -626,7 +541,7 @@ 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
@ -639,11 +554,11 @@ 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[^5] well!
The [`sheet_to_html`](/docs/api/utilities/html#html-table-output) function
generates HTML that is aware of merges and other worksheet features. ReactJS
`dangerouslySetInnerHTML`[^7] prop allows code to set the `innerHTML` attribute,
`dangerouslySetInnerHTML`[^6] 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
@ -652,7 +567,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() {
@ -701,7 +615,7 @@ This demo was tested in the following environments:
| ReactJS | ViteJS | Date |
|:---------|:--------|:-----------|
| `18.3.1` | `6.0.1` | 2024-12-13 |
| `18.2.0` | `5.1.6` | 2024-03-13 |
:::
@ -753,15 +667,7 @@ This demo was tested in the following environments:
| ReactJS | CRA | Date |
|:---------|:--------|:-----------|
| `18.2.0` | `5.0.1` | 2024-12-31 |
:::
:::caution pass
CRA has known compatibility issues with React 19[^5]. CRA no longer receives
updates and the ReactJS docs no longer recommend using CRA. For new projects, it
is strongly recommended to use ViteJS with the `react` or `react-ts` templates.
| `18.2.0` | `5.0.1` | 2024-03-13 |
:::
@ -776,7 +682,6 @@ npx -y create-react-app@5.0.1 --scripts-version=5.0.1 sheetjs-react
<CodeBlock language="bash">{`\
cd sheetjs-react
npm i
npm i react@18.2.0 react-dom@18.2.0 web-vitals --save --save-exact
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npm start`}
</CodeBlock>
@ -802,73 +707,6 @@ The generated site will be placed in the `build` folder.
npx 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.22.1` | `5.3.3` | 2024-12-17 |
:::
```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.**
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 http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
and test the page.
@ -923,6 +761,5 @@ 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]: See ["Merged Cells" in "SheetJS Data Model"](/docs/csf/features/merges) for more details.
[^6]: [`dangerouslySetInnerHTML`](https://react.dev/reference/react-dom/components/common#common-props) is a ReactJS prop supported for all built-in components.

@ -89,7 +89,7 @@ loaded into the site. This sheet will have known columns.
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:
object for each row, using the values in the first rows as keys:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
@ -298,7 +298,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">
@ -425,10 +425,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 +439,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
```
:::
@ -621,10 +619,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 +633,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
```
:::

@ -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
@ -58,7 +58,7 @@ loaded into the site. This sheet will have known columns.
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:
object for each row, using the values in the first rows as keys:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
@ -234,7 +234,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 +305,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>
@ -359,7 +359,7 @@ This demo was tested in the following environments:
| VueJS | ViteJS | Date |
|:---------|:---------|:-----------|
| `3.5.13` | `6.1.0` | 2025-02-15 |
| `3.4.27` | `5.2.11` | 2024-05-26 |
:::
@ -412,7 +412,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 |
:::
@ -520,7 +520,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 |
:::
@ -573,7 +573,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 |
:::

@ -128,9 +128,9 @@ 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>
```
@ -143,7 +143,7 @@ This demo was tested in the following environments:
| SvelteJS | ViteJS | Date |
|:---------|:---------|:-----------|
| `5.25.3` | `6.2.3` | 2025-03-30 |
| `4.2.18` | `5.2.13` | 2024-06-07 |
:::
@ -170,9 +170,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 +178,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
@ -245,7 +243,7 @@ This demo was tested in the following environments:
| SvelteJS | ViteJS | Date |
|:---------|:---------|:-----------|
| `5.25.3` | `6.2.3` | 2025-03-30 |
| `4.2.18` | `5.2.13` | 2024-06-07 |
:::
@ -272,9 +270,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,10 +278,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

@ -36,9 +36,8 @@ 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 |
| Chromiun 125 | `1.8.2` (latest) | 2024-06-09 |
| Chromium 125 | `1.2.32` (legacy) | 2024-06-09 |
:::
@ -77,7 +76,7 @@ and return a SheetJS workbook object[^2].
This controller fetches [a sample file](https://docs.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"
/* The controller function must accept a `$http` argument */
@ -87,9 +86,9 @@ app.controller('sheetjs', function($scope, $http) {
method:'GET', url:'https://docs.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);
@ -170,7 +169,7 @@ depends on the application.
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:
object for each row, using the values in the first rows as keys:
<table>
<thead><tr><th>Spreadsheet</th><th>State</th></tr></thead>
@ -201,8 +200,8 @@ app.controller('sheetjs', function($scope, $http) {
$http({
url:'https://docs.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); });
@ -278,8 +277,8 @@ app.controller('sheetjs', function($scope, $http) {
$http({
url:'https://docs.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,13 +288,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`)
2) Start a local web server with `npx http-server .` and 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,
@ -327,8 +321,8 @@ app.controller('sheetjs', function($scope, $http) {
$http({
url:'https://docs.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); });
@ -383,8 +377,8 @@ app.controller('sheetjs', function($scope, $http) {
$http({
url:'https://docs.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,13 +387,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`)
2) Start a local web server with `npx http-server .` and 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

@ -76,8 +76,7 @@ This demo was tested in the following environments:
| Platform | Date |
|:-------------|:-----------|
| Chromium 133 | 2025-03-30 |
| Konqueror 22 | 2025-04-23 |
| Chromium 125 | 2024-06-08 |
Demos exclusively using Dojo Core were tested using Dojo Toolkit `1.17.3`.
@ -98,7 +97,7 @@ 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>

@ -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 lang="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
---
@ -211,8 +211,8 @@ 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) |
| `3.5.0` | 2024-04-07 | [**KO3**](pathname:///knockout/knockout3.html) |
| `2.3.0` | 2024-04-07 | [**KO2**](pathname:///knockout/knockout2.html) |
:::

@ -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 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 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.2.10` | 2024-04-27 |
| `4.5.3` | 2024-04-27 |
| `3.2.10` | 2024-04-27 |
:::
@ -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:
@ -178,5 +159,3 @@ 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,22 +47,19 @@ 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.21.4` | 2024-06-07 |
| `0.20.2` | 2024-06-07 |
| `0.19.12` | 2024-06-07 |
| `0.18.20` | 2024-06-07 |
| `0.17.19` | 2024-06-07 |
| `0.16.17` | 2024-06-07 |
| `0.15.18` | 2024-06-07 |
| `0.14.54` | 2024-06-07 |
| `0.13.15` | 2024-06-07 |
| `0.12.29` | 2024-06-07 |
| `0.11.23` | 2024-06-07 |
| `0.10.2` | 2024-06-07 |
| `0.9.7` | 2024-06-07 |
:::
@ -90,7 +87,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.24.2 in.js --bundle --outfile=out.js
npx -y esbuild@0.19.8 in.js --bundle --outfile=out.js
```
### Browser Demo

@ -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 | |
:::
@ -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
:::

@ -34,21 +34,21 @@ This demo was tested in the following environments:
| Browserify | Date |
|:-----------|:-----------|
| `17.0.1` | 2025-01-07 |
| `16.5.2` | 2025-01-07 |
| `15.2.0` | 2025-01-07 |
| `14.5.0` | 2025-01-07 |
| `13.3.0` | 2025-01-07 |
| `12.0.2` | 2025-01-07 |
| `11.2.0` | 2025-01-07 |
| `10.2.6` | 2025-01-07 |
| `9.0.8` | 2025-01-07 |
| `8.1.3` | 2025-01-07 |
| `7.1.0` | 2025-01-07 |
| `6.3.4` | 2025-01-07 |
| `5.13.1` | 2025-01-07 |
| `4.2.3` | 2025-01-07 |
| `3.46.1` | 2025-01-07 |
| `17.0.0` | 2024-04-13 |
| `16.5.2` | 2024-04-13 |
| `15.2.0` | 2024-04-13 |
| `14.5.0` | 2024-04-13 |
| `13.3.0` | 2024-04-13 |
| `12.0.2` | 2024-04-13 |
| `11.2.0` | 2024-04-13 |
| `10.2.6` | 2024-04-13 |
| `9.0.8` | 2024-04-13 |
| `8.1.3` | 2024-04-13 |
| `7.1.0` | 2024-04-13 |
| `6.3.4` | 2024-04-13 |
| `5.13.1` | 2024-04-13 |
| `4.2.3` | 2024-04-13 |
| `3.46.1` | 2024-04-13 |
:::

@ -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-04-27 |
| `2.1.22` | 2024-04-27 |
:::
@ -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.

@ -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 |
:::

@ -34,8 +34,8 @@ This demo was tested in the following environments:
| Version | Date |
|:---------|:-----------|
| `2.13.3` | 2024-12-31 |
| `1.12.3` | 2024-12-31 |
| `2.12.0` | 2024-06-08 |
| `1.12.4` | 2024-06-08 |
:::

@ -35,7 +35,7 @@ This demo was tested in the following environments:
| Version | Date |
|:----------|:-----------|
| `1.2.246` | 2025-01-07 |
| `1.2.246` | 2024-04-27 |
:::
@ -73,7 +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 is known to affect versions `1.3.100` and `1.4.17`.
Until the bug is fixed, it is strongly recommended to use `@swc/core@1.2.246`.

@ -62,7 +62,7 @@ This demo was tested in the following environments:
| Version | Date |
|:--------|:-----------|
| `3.8.8` | 2025-01-07 |
| `3.8.8` | 2024-04-14 |
:::
@ -161,14 +161,12 @@ 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 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>
@ -194,7 +192,7 @@ This demo was tested in the following environments:
| Version | Date |
|:--------|:-----------|
| `3.8.0` | 2025-01-07 |
| `3.8.0` | 2024-04-14 |
:::
@ -287,14 +285,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 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>

@ -88,11 +88,10 @@ first worksheet. The SheetJS `sheet_to_html` method[^2] creates the HTML table.
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 126 | 2024-06-19 |
| Safari 17.3 | 2024-06-19 |
:::
@ -403,18 +402,18 @@ This demo was tested in the following 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 | |
| `0.10.48` | 2024-06-21 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `0.12.18` | 2024-06-21 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `4.9.1` | 2024-06-21 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `6.17.1` | 2024-06-21 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `8.17.0` | 2024-06-21 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `10.24.1` | 2024-06-21 | |
| `12.22.12` | 2024-06-21 | |
| `14.21.3` | 2024-06-21 | |
| `16.20.2` | 2024-06-21 | |
| `18.20.3` | 2024-06-21 | |
| `20.15.0` | 2024-06-21 | |
| `22.3.0` | 2024-06-21 | |
The `NODE_TLS_REJECT_UNAUTHORIZED` workaround sets the value to `'0'`:
@ -445,7 +444,7 @@ 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.
For older versions of NodeJS, the script will 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)"
@ -485,9 +484,9 @@ This demo was tested in the following environments:
| NodeJS | Date |
|:-----------|:-----------|
| `18.20.8` | 2025-03-30 |
| `20.18.0` | 2025-03-30 |
| `22.14.0` | 2025-03-30 |
| `18.20.3` | 2024-06-21 |
| `20.15.0` | 2024-06-21 |
| `22.3.0` | 2024-06-21 |
:::
@ -569,20 +568,20 @@ request(url, {encoding: null}, function(err, res, data) {
This demo was tested in the following 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 | |
| NodeJS | Date | Workarounds |
|:-----------|:-----------|:-------------------------------|
| `0.10.48` | 2024-06-21 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `0.12.18` | 2024-06-21 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `4.9.1` | 2024-06-21 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `6.17.1` | 2024-06-21 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `8.17.0` | 2024-06-21 | `NODE_TLS_REJECT_UNAUTHORIZED` |
| `10.24.1` | 2024-06-21 | |
| `12.22.12` | 2024-06-21 | |
| `14.21.3` | 2024-06-21 | |
| `16.20.2` | 2024-06-21 | |
| `18.20.3` | 2024-06-21 | |
| `20.15.0` | 2024-06-21 | |
| `22.3.0` | 2024-06-21 | |
The `NODE_TLS_REJECT_UNAUTHORIZED` workaround sets the value to `'0'`:
@ -613,7 +612,7 @@ 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.
For older versions of NodeJS, the script will 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)"
@ -648,16 +647,13 @@ This demo was tested in the following 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 |
| `10.24.1` | 0.28.1 | 2024-06-21 |
| `12.22.12` | 1.7.2 | 2024-06-21 |
| `14.21.3` | 1.7.2 | 2024-06-21 |
| `16.20.2` | 1.7.2 | 2024-06-21 |
| `18.20.3` | 1.7.2 | 2024-06-21 |
| `20.15.0` | 1.7.2 | 2024-06-21 |
| `22.3.0` | 1.7.2 | 2024-06-21 |
:::
@ -667,7 +663,7 @@ This demo was tested in the following environments:
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.7.2`}
</CodeBlock>
2) Save the following to `SheetJSAxios.js`:
@ -698,27 +694,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

@ -133,11 +133,10 @@ response. If the process was successful, a HTML table will be displayed
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 126 | 2024-06-19 |
| Safari 17.3 | 2024-06-19 |
:::
@ -579,7 +578,7 @@ function SheetJSSuperAgentUL() {
## NodeJS Demos
These examples show how to upload data in NodeJS scripts.
These examples show how to upload data in NodeJS.
### fetch
@ -591,8 +590,8 @@ This demo was tested in the following environments:
| NodeJS | Date |
|:-----------|:-----------|
| `22.13.0` | 2025-01-08 |
| `20.18.1` | 2025-01-08 |
| `20.12.1` | 2024-04-07 |
| `21.7.2` | 2024-04-07 |
:::
@ -705,18 +704,17 @@ 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 |
| `0.10.48` | `2.88.2` | 2024-04-07 |
| `0.12.18` | `2.88.2` | 2024-04-07 |
| `4.9.1` | `2.88.2` | 2024-04-07 |
| `6.17.1` | `2.88.2` | 2024-04-07 |
| `8.17.0` | `2.88.2` | 2024-04-07 |
| `10.24.1` | `2.88.2` | 2024-04-07 |
| `12.22.12` | `2.88.2` | 2024-04-07 |
| `14.21.3` | `2.88.2` | 2024-04-07 |
| `16.20.2` | `2.88.2` | 2024-04-07 |
| `18.20.1` | `2.88.2` | 2024-04-07 |
| `20.12.1` | `2.88.2` | 2024-04-07 |
:::
@ -733,13 +731,6 @@ and `request` module:
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"

@ -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,23 +136,21 @@ 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:

@ -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.44.1 | 2024-12-19 |
| `2.8.1` | 2.1.4 | 2024-12-19 |
This demo was last tested on 2024 March 11 against Drash 2.8.1 and Deno 1.41.2.
:::
@ -128,16 +123,6 @@ 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 SheetJSDenoDOM.ts
```
:::
3) Download the test file https://docs.sheetjs.com/pres.numbers
4) Open `http://localhost:7262/` in your browser.

@ -1,248 +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.1.40` | `4.6.14` | 2024-12-19 |
:::
## 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.1.40` | `0.8.17` | 2024-12-19 |
| BunJS `1.1.40` | `1.1.26` | 2024-12-19 |
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,8 +151,6 @@ 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:

@ -22,13 +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 |
|:----------|:-------------|
| `10.4.15` | `2024-12-22` |
| `9.4.3` | `2024-12-22` |
| `8.4.7` | `2024-12-22` |
This demo was tested on 2024 March 11 using NestJS `10.3.3`.
:::
@ -130,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:
@ -206,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'.
@ -222,7 +203,7 @@ 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
```

@ -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

@ -127,18 +127,15 @@ That approach is not explored in this demo.
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.20.3` | 2024-06-30 | ExpressJS 4.19.2 + Formidable 2.1.2 |
| `20.15.0` | 2024-06-30 | ExpressJS 4.19.2 + Formidable 2.1.2 |
| `22.3.0` | 2024-06-30 | ExpressJS 4.19.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

@ -34,14 +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 131 | `1.9.0` | 2024-12-22 |
| Konqueror 22 | `1.9.0` | 2025-04-23 |
| NodeJS 20 | `1.10.0` | 2024-12-22 |
| BunJS 1.1 | `1.10.0` | 2024-12-22 |
This demo was last tested on 2024 March 11 against `pst-extractor` 1.9.0
:::
@ -293,7 +286,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

@ -32,25 +32,53 @@ before integrating with important inboxes or accounts.
:::danger 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://docs.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,39 @@ 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,61 +131,116 @@ const puppeteer = require('puppeteer');
:::note Tested Deployments
This demo was tested in the following deployments:
| Puppeteer | Date |
|:----------|:-----------|
| `23.11.1` | 2024-12-31 |
| `22.15.0` | 2024-12-31 |
| `21.11.0` | 2024-12-31 |
| `20.9.0` | 2024-12-31 |
| `15.5.0` | 2024-12-31 |
| `10.4.0` | 2024-12-31 |
This demo was last tested on 2024 June 24 against Puppeteer 22.12.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@23.11.1`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz puppeteer@22.12.0`}
</CodeBlock>
</TabItem>
<TabItem value="bunjs" label="BunJS">
<CodeBlock language="bash">{`\
bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz puppeteer@23.11.1`}
</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 June 24 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
@ -234,56 +294,26 @@ const { webkit } = require('playwright'); // import desired browser
:::note Tested Deployments
This demo was tested in the following deployments:
| Playwright | Date |
|:-----------|:-----------|
| `1.49.1` | 2024-12-31 |
This demo was last tested on 2024 June 24 against Playwright 1.45.0.
:::
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.45.0`}
</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
@ -326,18 +356,6 @@ 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-01-19 |
| `linux-x64` | `2.1.1` | 2025-01-07 |
:::
<details>
<summary><b>Integration Details and Demo</b> (click to show)</summary>
@ -383,6 +401,19 @@ 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 |
| `win11-x64` | `2.1.1` | 2024-05-22 |
| `linux-x64` | `2.1.1` | 2024-04-25 |
:::
1) [Download and extract PhantomJS](https://phantomjs.org/download.html)
2) Save the `SheetJSPhantom.js` code snippet to `SheetJSPhantom.js`.
@ -426,7 +457,3 @@ env OPENSSL_CONF=/dev/null QT_QPA_PLATFORM=phantom ./phantomjs-2.1.1-linux-x86_6
:::
</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,7 +31,7 @@ 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.
@ -111,25 +111,23 @@ XLSX.writeFile(workbook, "SheetJSDOM.xlsx");
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 |
| JSDOM | Date |
|:--------|:-----------|
| 24.1.0 | 2024-06-24 |
| 23.2.0 | 2024-06-24 |
| 22.1.0 | 2024-06-24 |
| 21.1.2 | 2024-06-24 |
| 20.0.3 | 2024-06-24 |
| 19.0.0 | 2024-06-24 |
| 18.1.1 | 2024-06-24 |
| 17.0.0 | 2024-06-24 |
| 16.7.0 | 2024-06-24 |
| 15.2.1 | 2024-06-24 |
| 14.1.0 | 2024-06-24 |
| 13.2.0 | 2024-06-24 |
| 12.2.0 | 2024-06-24 |
| 11.12.0 | 2024-06-24 |
| 10.1.0 | 2024-06-24 |
:::
@ -162,36 +160,31 @@ 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.
:::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 |
| 14.12.3 | 2024-06-24 |
| 13.10.1 | 2024-06-24 |
| 12.10.3 | 2024-06-24 |
| 11.2.0 | 2024-06-24 |
| 10.11.2 | 2024-06-24 |
| 9.20.3 | 2024-06-24 |
| 8.9.0 | 2024-06-24 |
| 7.8.1 | 2024-06-24 |
| 6.0.4 | 2024-06-24 |
| 5.4.0 | 2024-06-24 |
| 4.1.0 | 2024-06-24 |
| 3.2.2 | 2024-06-24 |
| 2.55.0 | 2024-06-24 |
:::
@ -243,24 +236,19 @@ Object.defineProperty(tbl.__proto__, "innerHTML", { get: function() {
}});
```
:::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 |
:::
<details>
<summary><b>Complete Demo</b> (click to show)</summary>
:::note Tested Deployments
This demo was last tested on 2024 March 12 against XMLDOM `0.8.10`
:::
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 +280,19 @@ 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.
:::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 |
:::
<details>
<summary><b>Complete Demo</b> (click to show)</summary>
:::note Tested Deployments
This demo was last tested on 2024 March 12 against Cheerio `1.0.0-rc.12`
:::
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 +322,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.46`), the following patches were needed:
- TABLE `rows` property (explained above)
- TR `cells` property (explained above)
@ -350,7 +333,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.46/deno-dom-wasm.ts';
\n\
const doc = new DOMParser().parseFromString(
await (await fetch('https://docs.sheetjs.com/dom/SheetJSTable.html')).text(),
@ -369,24 +352,20 @@ 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.0.4 | 2024-10-30 |
| `win11-arm` | 0.1.48 | 2.2.1 | 2025-02-23 |
| `linux-x64` | 0.1.48 | 2.0.5 | 2025-01-10 |
| `linux-arm` | 0.1.48 | 2.1.10 | 2025-02-16 |
| `darwin-x64` | 0.1.46 | 1.44.4 | 2024-06-19 |
| `darwin-arm` | 0.1.46 | 1.44.4 | 2024-06-19 |
:::
<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:
@ -397,16 +376,6 @@ deno run --allow-net --allow-write SheetJSDenoDOM.ts
The script will create a file `SheetJSDenoDOM.xlsx` that can be opened.
:::caution pass
Deno 2 additionally requires the `--allow-import` entitlement:
```bash
deno run --allow-net --allow-write --allow-import SheetJSDenoDOM.ts
```
:::
</details>
[^1]: See [`table_to_sheet` in "HTML" Utilities](/docs/api/utilities/html#create-new-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 131 | 2024-12-31 |
| Safari 18.2 | 2024-12-31 |
| Konqueror 22 | 2025-04-23 |
This demo was last verified on 2024 April 25.
:::

@ -15,13 +15,7 @@ with a straightforward API.
:::note Tested Deployments
This demo was tested in the following environments:
| Browser | Date |
|:-------------|:-----------|
| Chromium 131 | 2024-12-31 |
| Safari 18.2 | 2024-12-31 |
| Konqueror 22 | 2025-04-23 |
This demo was last verified on 2024 April 25.
:::

@ -24,8 +24,7 @@ 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 |
| Chromium 125 | `6.2.1` | 2024-06-13 |
:::
@ -46,7 +45,7 @@ installation instructions for projects using a framework.
<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>
<script type="text/javascript" src="https://unpkg.com/tabulator-tables@6.2.1/dist/js/tabulator.min.js"></script>
```
:::
@ -140,7 +139,7 @@ function export_xlsx() {
</script>
```
[The official documentation](https://tabulator.info/docs/6.3/download#xlsx)
[The official documentation](https://tabulator.info/docs/6.2/download#xlsx)
covers supported options.
#### Post-processing

@ -28,8 +28,7 @@ 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 |
| Chromium 125 | `1.4.0` | 2024-06-13 |
:::
@ -41,7 +40,7 @@ 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`}
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz vue3-table-lite@1.4.0`}
</CodeBlock>
#### Rows and Columns Bindings
@ -137,7 +136,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.4.0`}
</CodeBlock>
3) Download [`src/App.vue`](pathname:///vtl/App.vue) and replace the contents:

@ -30,28 +30,7 @@ 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.
| Chromiun 125 | `5.3.2` | 2024-06-09 |
:::
@ -365,19 +344,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 +359,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 .
7) Make some changes to the grid data.
6) Make some changes to the grid data.
:::note pass
@ -415,14 +388,14 @@ 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
7) Click on the "Export" button. The browser should attempt to download a XLSX
file (`sheetjs-gdg.xlsx`). Save the file.
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,11 +26,10 @@ 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` | 2024-06-09 | |
| `7.0.0-beta.44` | 2024-06-09 | Editing did not work |
:::
@ -39,8 +38,7 @@ This demo was tested in the following environments:
When this demo was last tested against the latest version, the grid correctly
displayed data but data 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`.
:::
@ -161,18 +159,6 @@ cd sheetjs-rdg
npm i -S https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz react-data-grid@7.0.0-beta.19`}
</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
@ -188,30 +174,6 @@ 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.
:::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.

@ -141,7 +141,7 @@ The following example JSX shows a table using HTML and using MUI components:
### 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`
The MUI `Table` element is really a 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
@ -184,10 +184,9 @@ export default function MUITableSheetJSExport() {
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 |
| Material UI | Emotion | Date |
|:------------|:----------|:-----------|
| `5.15.20` | `11.11.4` | 2024-06-12 |
:::
@ -201,7 +200,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.15.20 @emotion/react@11.11.4 @emotion/styled@11.11.5`}
</CodeBlock>
3) Download [`App.tsx`](pathname:///mui/table/App.tsx) and replace `src/App.tsx`.
@ -216,14 +215,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
@ -337,10 +330,9 @@ export default function App() {
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 |
| Data Grid | Emotion | Date |
|:----------|:----------|:-----------|
| `7.6.2` | `11.11.4` | 2024-06-12 |
:::
@ -354,7 +346,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@7.6.2 @emotion/react@11.11.4 @emotion/styled@11.11.5`}
</CodeBlock>
3) Download [`App.tsx`](pathname:///mui/dg/App.tsx) and replace `src/App.tsx`.
@ -369,30 +361,8 @@ 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)

@ -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.
@ -228,7 +226,7 @@ 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 starting a web server:
```bash
npx http-server _site

@ -53,24 +53,12 @@ overridden through a `package.json` override in the latest versions of NodeJS:
:::danger 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 +179,8 @@ This demo was tested in the following environments:
| GatsbyJS | Date |
|:---------|:-----------|
| `5.14.1` | 2025-01-19 |
| `4.25.8` | 2025-01-02 |
| `5.13.4` | 2024-05-04 |
| `4.25.8` | 2024-03-27 |
:::
@ -233,8 +221,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 +234,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 +257,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
@ -341,18 +329,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)"
{
@ -374,7 +358,7 @@ Press the Execute Query button (`▶`) and data should show up in the right pane
<details>
<summary><b>Sample Output</b> (click to show)</summary>
In GatsbyJS versions `5.13.4` and `4.25.8`, the raw output was:
In GatsbyJS version `5.13.4`, the raw output was:
```json title="GraphQL query result from GatsbyJS 5.13.4"
{
@ -422,7 +406,7 @@ In GatsbyJS versions `5.13.4` and `4.25.8`, the raw output was:
### 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)"
import { graphql } from "gatsby"
@ -445,10 +429,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 +445,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 +484,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 +496,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 +505,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 +527,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)"

@ -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'
@ -204,23 +204,19 @@ 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 |
| `0.20.2` | 2024-04-07 |
| `0.19.12` | 2024-04-07 |
| `0.18.20` | 2024-04-07 |
| `0.17.19` | 2024-04-07 |
| `0.16.17` | 2024-04-07 |
| `0.15.18` | 2024-04-07 |
| `0.14.54` | 2024-04-07 |
| `0.13.15` | 2024-04-07 |
| `0.12.29` | 2024-04-07 |
| `0.11.23` | 2024-04-07 |
| `0.10.2` | 2024-04-07 |
| `0.9.7` | 2024-04-07 |
| `0.9.1` | 2024-04-07 |
:::

@ -169,7 +169,7 @@ 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]]);
@ -243,7 +243,7 @@ export default defineConfig({
plugins: [
{ // this plugin handles ?html tags
name: "vite-sheet-html",
transform(_code, id) {
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]]);
@ -309,7 +309,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");
@ -356,11 +356,10 @@ 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.2.12` | 2024-06-02 |
| `4.5.3` | 2024-06-02 |
| `3.2.10` | 2024-06-02 |
| `2.9.18` | 2024-06-02 |
:::
@ -642,5 +641,5 @@ embedded in the final site and the data is parsed when the page is loaded.
[^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.
[^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.

@ -55,7 +55,7 @@ flowchart LR
aoo --> |src/index.js\nfrontend code| html
```
### Webpack Configuration
### Webpack Config
The Webpack configuration is normally saved to `webpack.config.js`.
@ -184,11 +184,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 2024 April 06 against Webpack 5.91.0
:::
@ -200,7 +196,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.91.0 webpack-cli@5.1.4 webpack-dev-server@5.0.4 --save
mkdir -p dist
mkdir -p src
mkdir -p data

@ -114,10 +114,10 @@ accessed using the variable `pres` in a template:
This demo was tested in the following environments:
| Eleventy | Date |
|:---------|:-----------|
| `2.0.1` | 2024-12-23 |
| `3.0.0` | 2024-12-23 |
| Eleventy | Date |
|:----------------|:-----------|
| `2.0.1` | 2024-03-15 |
| `3.0.0-alpha.5` | 2024-03-15 |
:::
@ -134,17 +134,17 @@ 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>

@ -75,13 +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 |
| ` 9.5.5` | `16.20.2` | 2024-06-07 |
| `10.2.3` | `16.20.2` | 2024-06-07 |
| `11.1.4` | `16.20.2` | 2024-06-07 |
| `12.3.4` | `20.14.0` | 2024-06-07 |
| `13.5.6` | `20.14.0` | 2024-06-07 |
| `14.2.3` | `20.14.0` | 2024-06-07 |
:::
@ -95,7 +94,7 @@ 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.
the SheetJS git server includes code samples for legacy NextJS versions.
:::
@ -114,7 +113,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.
:::
@ -584,7 +583,6 @@ npx -y next@13.5.6 telemetry status
```bash
mkdir sheetjs-next
cd sheetjs-next
npm init -y
mkdir -p pages/sheets/
```
@ -599,7 +597,7 @@ curl -LO https://docs.sheetjs.com/next/sheetjs.xlsx
:::note pass
The `next@13.5.6` dependency can be adjusted to pick a different version. For
The `next@13.5.6` depefndency can be adjusted to pick a different version. For
example, NextJS `12.3.4` is installed with
<CodeBlock language="bash">{`\
@ -667,7 +665,7 @@ cd ../..
6) Test the deployment:
```bash
npx -y next
npx next
```
Open a web browser and access:
@ -693,7 +691,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:
@ -728,7 +726,7 @@ was changed to a stylized lowercase F (`ƒ`) in recent versions of NextJS.
<TabItem value="13" label="NextJS 9 - 13">
```bash
npx -y next export
npx next export
```
</TabItem>
@ -753,7 +751,7 @@ module.exports = {
After adding the line, run the `build` command:
```bash
npx -y next build
npx next build
```
</TabItem>
@ -793,7 +791,7 @@ After editing `next.config.js`, run the build command:
</Tabs>
```bash
npx -y next build
npx next build
```
Inspecting the output, there should be no lines with `λ` or `ƒ`:
@ -816,7 +814,7 @@ Route (pages) Size First Load JS
<TabItem value="13" label="NextJS 9 - 13">
```bash
npx -y next export
npx next export
```
</TabItem>
@ -834,7 +832,7 @@ module.exports = {
After adding the line, run the `build` command:
```bash
npx -y next build
npx next build
```
</TabItem>
@ -845,7 +843,7 @@ The static site will be written to the `out` subfolder
13) 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
@ -861,7 +859,7 @@ page was removed.
[^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).

@ -46,10 +46,10 @@ 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.14.159` | 2024-11-14 |
| Nuxt Content | Nuxt | Date |
|:-------------|:---------|:-----------|
| `1.15.1` | `2.17.3` | 2024-06-04 |
| `2.12.1` | `3.11.2` | 2024-06-04 |
:::
@ -58,7 +58,7 @@ This demo was tested in the following environments:
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.**
@ -268,7 +268,7 @@ 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:
@ -352,7 +352,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">
@ -412,7 +412,7 @@ This will create a static site in the `dist` folder.
9) Serve the static site:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) in a web browser.
@ -499,16 +499,6 @@ 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.

@ -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 |
| SvelteJS | Kit | Date |
|:-----------------|:---------|:-----------|
| `4.2.17` | `2.5.10` | 2024-06-03 |
| `5.0.0-next.149` | `2.5.10` | 2024-06-03 |
:::
@ -190,35 +189,7 @@ When this demo was last tested, SvelteKit required NodeJS major version 20.
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:
@ -227,21 +198,19 @@ When prompted:
- `Add type checking with TypeScript?` select `Yes, using JavaScript with JSDoc`
- `Select additional options` press <kbd>Enter</kbd> (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 the Svelte 5 preview (unstable!)`
:::
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:
@ -342,14 +311,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:

@ -52,14 +52,8 @@ 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 |
| `3.6.5` | Starlight 0.14.0 | 2024-04-14 |
| `4.6.1` | Starlight 0.21.5 | 2024-04-14 |
:::
@ -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:
@ -221,10 +215,10 @@ cd sheetjs-astro
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`:
For major version 3, Starlight must be version `0.14.0`:
```bash
npm install --force astro@4 @astrojs/starlight@0.22.4
npm install --force astro@3.6.5 @astrojs/starlight@0.14.0
```
The version can be verified by running:
@ -233,22 +227,6 @@ 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):
@ -349,7 +327,8 @@ 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

@ -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 */}
@ -232,25 +226,19 @@ This demo was tested in the following environments:
| 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 |
| iOS 15.1 | iPhone 12 Pro Max | `0.73.6` | 2024-03-13 |
| Android 29 | NVIDIA Shield | `0.73.6` | 2024-03-13 |
**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 | `0.76.5` | `win11-x64` | 2024-12-22 |
| Android 35 | Pixel 9 | `0.76.5` | `linux-x64` | 2025-01-02 |
:::
:::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!
| 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.74.2` | `darwin-arm` | 2024-06-20 |
| iOS 17.5 | iPhone SE (3rd gen) | `0.74.2` | `darwin-arm` | 2024-06-20 |
| Android 34 | Pixel 3a | `0.73.5` | `win10-x64` | 2024-03-05 |
| Android 34 | Pixel 3a | `0.73.7` | `linux-x64` | 2024-04-29 |
:::
@ -289,48 +277,19 @@ export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
1) Create project:
```bash
npx -y @react-native-community/cli@15 init SheetJSRNFetch --version="0.76.8"
npx -y react-native@0.74.2 init SheetJSRNFetch --version="0.74.2"
```
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.2 @types/react-native-table-component`}
</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
@ -338,7 +297,7 @@ curl -LO https://docs.sheetjs.com/reactnative/App.tsx
**Android Testing**
4) Install or switch to Java 17[^7]
4) Install or switch to Java 17[^6]
5) Start the Android emulator:
@ -378,25 +337,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>
@ -512,12 +452,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.
@ -526,12 +460,12 @@ 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>
These instructions were verified against Xcode 16.2.
These instructions were verified against Xcode 15.3.
A) Open the included iOS workspace in Xcode:
@ -563,29 +497,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":
@ -594,7 +505,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".
@ -749,10 +660,10 @@ for accessing data and are subject to change in future platform versions.
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`:
@ -1087,17 +998,17 @@ This demo was tested in the following environments:
| 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 |
| iOS 15.5 | iPhone 13 Pro Max | `0.73.6` | 2024-03-31 |
| Android 29 | NVIDIA Shield | `0.73.6` | 2024-03-31 |
**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 | `0.76.5` | `win11-x64` | 2024-12-22 |
| 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 |
| Android 34 | Pixel 3a | `0.73.6` | `linux-x64` | 2024-03-31 |
:::
@ -1105,7 +1016,7 @@ This demo was tested in the following environments:
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.
:::
@ -1117,10 +1028,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:
@ -1128,7 +1039,7 @@ 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 react-native-table-component@1.2.2 react-native-document-picker@9.3.1`}
npm i -S react-native-table-component@1.2.2 react-native-document-picker@9.1.1`}
</CodeBlock>
3) Download [`index.js`](pathname:///mobile/index.js) and replace:
@ -1149,37 +1060,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**
@ -1192,7 +1084,7 @@ 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.21.2
npm i -S react-native-blob-util@0.19.8
```
Add the highlighted lines to `index.js`:
@ -1240,7 +1132,7 @@ 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`:
@ -1295,21 +1187,6 @@ 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"
@ -1682,15 +1559,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) 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.
[^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.

@ -65,8 +65,7 @@ This demo was tested in the following environments:
|:-----------|:--------------------|:---------|:-------------|:-----------|
| Android 34 | Pixel 3a | `8.7.2` | `darwin-arm` | 2024-06-09 |
| iOS 17.5 | iPhone SE (3rd gen) | `8.7.2` | `darwin-arm` | 2024-06-09 |
| Android 35 | Pixel 9 | `8.8.3` | `win11-x64` | 2024-12-21 |
| Android 35 | Pixel 9 | `8.8.3` | `linux-x64` | 2025-01-02 |
| Android 34 | Pixel 3a | `8.6.5` | `win10-x64` | 2024-04-07 |
:::
@ -273,25 +272,21 @@ npx -p nativescript ns error-reporting disable
:::caution pass
In previous test runs, NativeScript did not support the latest Android API.
The error message from `npx -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>
If NativeScript does not properly supports the latest API level, a previous 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:
@ -591,30 +586,9 @@ If the emulator cannot be rooted, the following command works in macOS:
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:
- 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:
After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library:
```text
id | name | role
@ -635,21 +609,7 @@ If the emulator cannot be rooted, the following command works in macOS:
```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.
@ -678,13 +638,7 @@ npx -p nativescript ns run ios
22) Open the file with a spreadsheet editor.
After the header row, insert a row and make the following assignments:
- 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:
After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library:
```text
id | name | role
@ -874,5 +828,4 @@ file named `SheetJSNS.xls`.
[^4]: See [`write` in "Writing Files"](/docs/api/write-options)
[^5]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
[^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.
[^7]: See [`read` in "Reading Files"](/docs/api/parse-options)

@ -45,16 +45,15 @@ This demo was tested in the following environments:
| 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 |
| Android 30 | NVIDIA Shield | `2.16.4` | 2024-06-09 |
| iOS 15.1 | iPad Pro | `2.16.4` | 2024-06-09 |
**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 |
| Android 34 | Pixel 3a | `2.16.4` | `darwin-arm` | 2024-06-09 |
| iOS 17.5 | iPhone SE (3rd gen) | `2.16.4` | `darwin-arm` | 2024-06-09 |
:::
@ -77,7 +76,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,23 +166,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
```
:::
(you may need to run `sudo npm i -g` if there are permission issues)
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
@ -192,8 +181,6 @@ Quasar requires Java 17
</details>
### Base Project
1) Create a new app:
```bash
@ -206,12 +193,14 @@ When prompted:
- "What would you like to build?": `App with Quasar CLI, let's go!`
- "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`)
- "Project product name": `SheetJSQuasar`
- "Project description": `SheetJS + Quasar`
- "Pick a Vue component style": `Composition API with <script setup>`
- "Author": (press <kbd>Enter</kbd>, 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`
@ -229,105 +218,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 +237,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 ..
```
4) 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,10 +268,10 @@ 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>.
@ -375,6 +283,52 @@ then restart the development process.
:::
6) Add the Dialog plugin to `quasar.config.js`:
```js title="quasar.config.js (add highlighted line)"
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 (change highlighted lines)"
<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 (add highlighted lines)"
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,6 +340,38 @@ then restart the development process.
:::
8) Wire up the `updateFile` function:
```ts title="src/pages/IndexPage.vue (add highlighted lines)"
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
@ -403,11 +389,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 (add highlighted lines)"
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,23 +473,121 @@ 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 ..
```
11) Start the simulator:
```bash
quasar dev -m android
```
If prompted to select an external IP, press <kbd>Enter</kbd>.
15) Test the application:
:::caution pass
If the app is blank or not refreshing, delete the app and close the simulator,
then restart the development process.
:::
:::warning pass
On Windows, 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 CMD window and run the command.
:::
To test that reading works:
- 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
adb exec-out run-as org.sheetjs.quasar cat files/files/SheetJSQuasar.xlsx > /tmp/SheetJSQuasar.xlsx
npx xlsx-cli /tmp/SheetJSQuasar.xlsx
```
**iOS Device**
12) Close all open emulators and simulators.
13) Disconnect any iOS or Android devices connected to the computer.
14) Connect the iOS device to the computer.
15) Open the Xcode project:
```bash
open src-cordova/platforms/ios/SheetJSQuasar.xcodeproj
```
Select "SheetJSQuasar" in the Navigator. In the main pane, select "Signing &amp;
Capabilities" and ensure a Team is selected. Save and close the project.
16) Start the dev process:
```bash
quasar dev -m ios
```
If prompted to select an external IP, press <kbd>Enter</kbd>.
17) 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.
**Android Device**
18) Close all open emulators and simulators.
19) Disconnect any iOS or Android devices connected to the computer.
20) Connect the Android device to the computer.
21) Start the dev process:
```bash
quasar dev -m android
```
If prompted to select an external IP, press <kbd>Enter</kbd>.
22) Test the application:
- Press the Home button (or swipe up with one finger) and switch to Browser.
- Download https://docs.sheetjs.com/pres.numbers
@ -485,48 +604,6 @@ 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:
```bash
open src-cordova/platforms/ios/SheetJSQuasar.xcodeproj
```
Select "SheetJSQuasar" in the Navigator. In the main pane, select "Signing &amp;
Capabilities" and ensure a Team is selected. Save and close the project.
20) Start the dev process:
```bash
npx quasar dev -m cordova -T ios
```
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.

@ -53,31 +53,24 @@ This demo was tested in the following environments:
| OS | Device | Config | Date |
|:-----------|:--------------------|:-------|:-----------|
| Android 34 | NVIDIA Shield | B | 2025-03-30 |
| iOS 15.6 | iPhone 13 Pro Max | B | 2025-03-30 |
| Android 30 | NVIDIA Shield | A | 2024-05-30 |
| iOS 15.1 | iPad Pro | A | 2024-05-30 |
**Simulators**
| 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 |
| Android 34 | Pixel 3a | A | `darwin-arm` | 2024-05-30 |
| iOS 17.5 | iPhone SE (3rd gen) | A | `darwin-arm` | 2024-05-30 |
<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`
- Ionic: `@ionic/angular 8.2.0`, `@ionic/angular-toolkit 11.0.1`
- Cordova: `cordova-lib@12.0.1`, `android 13.0.0, ios 7.1.0`
- File Integration: `@awesome-cordova-plugins/file` version `6.7.0`
</details>
@ -85,18 +78,26 @@ Configuration B:
:::danger 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
```
:::
@ -173,7 +174,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 +195,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);
@ -230,12 +231,7 @@ 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].
@ -272,9 +268,9 @@ 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
@ -296,9 +292,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 +341,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 +367,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 +386,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 +455,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
```
:::
:::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!**
:::
[^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.
@ -689,4 +531,3 @@ the file with the new line.
[^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.

@ -44,17 +44,18 @@ This demo was tested in the following environments:
| 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 |
| Android 30 | NVIDIA Shield | `6.0.0` / `6.0.0` | 2024-06-02 |
| iOS 15.1 | iPad Pro | `6.0.0` / `6.0.0` | 2024-06-02 |
**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 | `6.2.0` / `6.0.2` | `win11-x64` | 2024-12-21 |
| Android 35 | Pixel 9 | `6.2.0` / `6.0.2` | `linux-x64` | 2025-01-02 |
| Android 34 | Pixel 3a | `6.0.0` / `6.0.0` | `darwin-x64` | 2024-06-02 |
| iOS 17.5 | iPhone 15 Pro Max | `6.0.0` / `6.0.0` | `darwin-x64` | 2024-06-02 |
| Android 34 | Pixel 3a | `6.0.0` / `6.0.0` | `darwin-arm` | 2024-06-02 |
| iOS 17.5 | iPhone 15 Pro Max | `6.0.0` / `6.0.0` | `darwin-arm` | 2024-06-02 |
| Android 34 | Pixel 3a | `6.0.0` / `6.0.0` | `win10-x64` | 2024-05-28 |
:::
@ -92,7 +93,7 @@ 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],
method, generates a 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"
@ -127,7 +128,7 @@ Starting from a SheetJS workbook object[^3], the `write` method with the option
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
workbook object from a HTML table. The workbook object is exported to the XLSX
format and written to the device.
```html title="Sample component for data export"
@ -215,9 +216,7 @@ iOS development is only supported on macOS.
<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>
@ -277,20 +276,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 +292,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
@ -319,12 +302,10 @@ npx cap run android
The app should look like the screenshot at the top of the page.
10) Test the export functionality.
9) Test the export functionality.
Touch "Export XLSX". If the emulator asks for permission, tap "Allow". A popup
will show the exported path.
![Export Confirmation Popup](pathname:///cap/and-export-popup.png)
Touch "Export XLSX" and the emulator will ask for permission. 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`.
@ -332,51 +313,57 @@ the "Documents" folder to find `SheetJSCap.xlsx`.
<details open>
<summary><b>Downloading the generated file</b> (click to hide)</summary>
`adb` must be run from the root user:
```bash
adb root
```
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 `/`:
The first line of the output starting with `/` is the desired path:
```text
find: /proc/8533/task/8533/exe: No such file or directory
find: /proc/8533/exe: No such file or directory
// highlight-next-line
/data/media/0/Documents/SheetJSCap.xlsx
/storage/emulated/0/Documents/SheetJSCap.xlsx
```
The `/storage` path can be pulled using `adb pull`:
`adb pull` can download the file:
```bash
adb pull "/storage/emulated/0/Documents/SheetJSCap.xlsx" SheetJSCap.xlsx
adb pull "/data/media/0/Documents/SheetJSCap.xlsx" SheetJSCap.xlsx
```
`SheetJSCap.xlsx` can be opened with a spreadsheet editor such as Excel.
</details>
11) Test the import functionality.
10) Test the import functionality.
Edit `SheetJSCap.xlsx`, setting cell `A7` to `SheetJS Dev` and setting cell `B7`
to `47`. Save the file.
Create a spreadsheet or find an existing file. Click and drag the file into the
Android emulator window. The file will be uploaded to a Downloads folder in the
emulator.
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.
Tap on "Choose File" in the app. In the selector, tap `≡` and select "Downloads"
to find the uploaded file. After selecting the file, the table will refresh.
### iOS
12) Create iOS app.
11) 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.
12) 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 +380,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.
13) Run the app in the simulator
```bash
npm run build
@ -401,16 +388,14 @@ 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.
15) Test the export functionality.
14) Test the export functionality.
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`"
@ -426,41 +411,24 @@ find ~/Library/Developer -name SheetJSCap.xlsx
</details>
16) Test the import functionality.
15) Test the import functionality.
Edit `SheetJSCap.xlsx`, setting cell `A7` to `SheetJS Dev` and setting cell `B7`
to `47`. Save the file.
Create a spreadsheet or find an existing 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".
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.
Tap on "Choose File" in the app and "Choose File" in the popup. In the picker,
tap "Recents" and select the new file. After selecting the file, the table will refresh.
### Android Device
17) Connect an Android device using a USB cable.
16) 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`.
17) 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:
18) Build APK and run on device:
```bash
npm run build
@ -499,19 +467,17 @@ to the `application` open tag in `android/app/src/main/AndroidManifest.xml`:
### iOS Device
21) Connect an iOS device using a USB cable
19) Connect an iOS device using a USB cable
If prompted to "Trust This Computer", tap "Trust" and enter the device passcode.
20) Close any Android / iOS emulators.
22) Close any Android / iOS emulators.
23) Enable developer code signing certificates.
21) 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:
22) Run on device:
```bash
npm run build
@ -521,35 +487,6 @@ 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)

@ -14,9 +14,6 @@ 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).
@ -50,16 +47,15 @@ This demo was tested in the following environments:
| 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 |
| Android 30 | NVIDIA Shield | `3.4.3` | `3.22.2` | 2024-06-09 |
| iOS 15.1 | iPad Pro | `3.4.3` | `3.22.2` | 2024-06-09 |
**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 35 | Pixel 3a | `3.5.0` | `3.24.0` | `win11-x64` | 2024-08-10 |
| Android 34 | Pixel 3a | `3.4.3` | `3.22.2` | `darwin-x64` | 2024-06-09 |
| iOS 17.5 | iPhone 15 Pro Max | `3.4.3` | `3.22.2` | `darwin-x64` | 2024-06-09 |
:::
@ -228,24 +224,24 @@ 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>
```
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
```
</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>
```
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
```
</TabItem>
<TabItem value="win" label="Windows">
<pre>
<span {...g}>[✓]</span> Android toolchain - develop for Android devices (Android SDK version 35.0.0)
</pre>
```
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
```
</TabItem>
</Tabs>
@ -294,11 +290,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,12 +307,13 @@ If Google Chrome is not installed, `flutter doctor` will show an issue:
If Chromium is installed, the environment variable should be manually assigned:
```bash
export CHROME_EXECUTABLE=/Applications/Chromium.app/Contents/MacOS/Chromium
```
<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">
@ -332,18 +324,6 @@ 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>
@ -351,49 +331,15 @@ Click "OK" in each window (3 windows) and restart your computer.
</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_3a_API_35 • Pixel 3a API 35 • Google • android
```
</TabItem>
</Tabs>
1) Disable telemetry. The following commands were confirmed to work:
```bash
@ -429,18 +375,16 @@ 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
@ -450,7 +394,7 @@ On macOS, `~/Library/Android/sdk/emulator/` is the typical location for the
```bash
export PATH="$PATH":~/Library/Android/sdk/emulator
emulator -avd Pixel_9_Pro_XL_API_35
emulator -avd Pixel_3a_API_34
```
:::
@ -505,32 +449,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:
@ -554,50 +472,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.
@ -611,41 +491,7 @@ 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>
:::info Troubleshooting
:::info pass
In some demo runs, the build failed with an Android SDK error:
@ -673,57 +519,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.
@ -766,28 +561,6 @@ 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
@ -850,16 +623,6 @@ device will ask for permission:
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:

@ -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,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.5 | `darwin-arm` | `30.0.8` | 2024-05-28 |
| Windows 10 | `win10-x64` | `31.2.0` | 2024-07-12 |
| Windows 11 | `win11-arm` | `30.0.8` | 2024-05-28 |
| Linux (HoloOS) | `linux-x64` | `29.1.4` | 2024-03-21 |
| Linux (Debian) | `linux-arm` | `30.0.8` | 2024-05-28 |
:::
@ -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
@ -315,7 +276,7 @@ The program will run on ARM64 Windows.
|:-------------|:--------------------------------------------------------------|
| `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` |
| `win10-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` |
@ -360,8 +321,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 30.0.8.
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 +336,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 +349,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
@ -121,12 +113,12 @@ 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.94.0` | 2024-12-19 | |
| 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] |
| macOS 14.3.1 | `darwin-x64` | `0.85.0` | 2024-03-12 | |
| macOS 14.5 | `darwin-arm` | `0.88.0` | 2024-05-28 | |
| Windows 10 | `win10-x64` | `0.83.0` | 2024-03-04 | |
| Windows 11 | `win11-arm` | `0.88.0` | 2024-05-28 | |
| Linux (HoloOS) | `linux-x64` | `0.89.0` | 2024-07-07 | |
| Linux (Debian) | `linux-arm` | `0.60.0` | 2024-05-23 | Unofficial build[^1] |
:::
@ -146,7 +138,7 @@ cd sheetjs-nwjs
"version": "0.0.0",
"main": "index.html",
"dependencies": {
"nw": "0.94.0",
"nw": "0.89.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz"
}
}`}
@ -187,47 +179,18 @@ 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>
5) To build a standalone app, run the builder:
```bash
npx -p nw-builder@4.11.6 nwbuild --mode=build --version=0.94.0 --glob=false --outDir=../out ./
npx -p nw-builder nwbuild --mode=build --version=0.89.0 --glob=false --outDir=../out ./
```
This will generate the standalone app in the `..\out\` folder.
:::caution pass
There is a regression in `nw-builder` version `4.12.0`. In local `win11-x64`
testing, version `4.11.6` correctly generated 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>
@ -295,14 +295,14 @@ async function exportFile(table_element) {
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.9.2` | 2024-12-21 |
| 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 |
| OS and Version | Architecture | Wails | Date |
|:---------------|:-------------|:---------|:-----------|
| macOS 14.4 | `darwin-x64` | `v2.8.0` | 2024-03-15 |
| macOS 14.5 | `darwin-arm` | `v2.8.2` | 2024-05-28 |
| Windows 10 | `win10-x64` | `v2.8.0` | 2024-03-24 |
| Windows 11 | `win11-arm` | `v2.8.2` | 2024-05-28 |
| Linux (HoloOS) | `linux-x64` | `v2.8.0` | 2024-03-21 |
| Linux (Debian) | `linux-arm` | `v2.8.2` | 2024-05-28 |
:::
@ -358,7 +358,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
```
:::

@ -14,7 +14,6 @@ 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"}};
@ -38,7 +37,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>
@ -55,24 +54,16 @@ app to read and write workbooks. The app will look like the screenshots below:
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.
@ -353,12 +344,12 @@ 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` | 2024-12-21 |
| 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-02-16 |
| macOS 14.4 | `darwin-x64` | `v1.5.11` | 2024-04-20 |
| macOS 14.5 | `darwin-arm` | `v1.5.14` | 2024-05-26 |
| Windows 10 | `win10-x64` | `v1.5.11` | 2024-03-24 |
| Windows 11 | `win11-arm` | `v1.5.14` | 2024-05-28 |
| Linux (HoloOS) | `linux-x64` | `v1.5.11` | 2024-03-21 |
| Linux (Debian) | `linux-arm` | `v1.5.14` | 2024-05-28 |
:::
@ -370,7 +361,7 @@ This demo was tested in the following environments:
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:
@ -384,17 +375,15 @@ If required dependencies are installed, the output will show a checkmark next to
<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}>OS</span>: Mac OS 14.4.1 X64
{` `}<span {...g}></span> <span {...B}>Xcode Command Line Tools</span>: installed
{` `}<span {...g}></span> <span {...B}>rustc</span>: 1.85.1 (4eb161250 2025-03-15)
{` `}<span {...g}></span> <span {...B}>cargo</span>: 1.85.1 (d73d2caf9 2024-12-31)
{` `}<span {...g}></span> <span {...B}>rustup</span>: 1.28.1 (f9edccde0 2025-03-05)
{` `}<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.7
{` `}<span {...c}>-</span> <span {...B}>deno</span>: deno 2.2.6
{` `}<span {...g}></span> <span {...B}>rustc</span>: 1.77.2 (25ef9e3d8 2024-04-09)
{` `}<span {...g}></span> <span {...B}>cargo</span>: 1.77.2 (e52e36006 2024-03-26)
{` `}<span {...g}></span> <span {...B}>rustup</span>: 1.27.0 (bbb9276d2 2024-03-08)
{` `}<span {...g}></span> <span {...B}>Rust toolchain</span>: stable-x86_64-apple-darwin (default)
{` `}<span {...g}>-</span> <span {...B}>node</span>: 20.12.1
{` `}<span {...g}>-</span> <span {...B}>npm</span>: 10.5.0
{` `}<span {...g}>-</span> <span {...B}>bun</span>: 1.1.4
</pre>
:::caution pass
@ -412,7 +401,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 +415,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>
@ -481,7 +470,6 @@ npm add vite-plugin-kaioken -D --save
In the same file, look for `"title"` and change the value to `SheetJS x Tauri`:
```json title="src-tauri/tauri.conf.json (edit highlighted line)"
"windows": [
{
// highlight-next-line
"title": "SheetJS x Tauri",
@ -537,7 +525,7 @@ export default defineConfig(async () => ({
- 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 +562,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 +601,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 +613,7 @@ or
or
```bash
./src-tauri/target/release/SheetJSTauri
./src-tauri/target/release/sheetjstauri
```
</TabItem>
@ -651,22 +639,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:
```

@ -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>
@ -192,15 +192,15 @@ 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` | `5.5.0` | `5.5.0` | 2024-12-20 |
| 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 |
| macOS 14.4 | `darwin-x64` | `5.0.0` | `5.0.1` | 2024-03-15 |
| macOS 14.5 | `darwin-arm` | `5.1.0` | `5.1.0` | 2024-05-25 |
| Windows 10 | `win10-x64` | `5.1.0` | `5.1.0` | 2024-03-24 |
| Windows 11 | `win11-arm` | `5.1.0` | `5.1.1` | 2024-05-28 |
| Linux (HoloOS) | `linux-x64` | `5.0.0` | `5.0.1` | 2024-03-21 |
| Linux (Debian) | `linux-arm` | `5.1.0` | `5.1.1` | 2024-05-28 |
NeutralinoJS on Windows on ARM generates X64 binaries that run using the X64
compatibility layer. The binaries are not native ARM64 programs!
On `win11-arm`, the Electron runner is a proper ARM64 binary but the binaries
generated by Electron Forge are x64. The x64 binaries run in Windows on ARM.
:::
@ -211,7 +211,7 @@ the window. Writing files will parse the table into a spreadsheet.
<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:
@ -413,12 +413,9 @@ 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` |
| `linux-arm` | `.\dist\sheetjs-neu\sheetjs-neu-linux_arm64` |
Run the generated app and confirm that Presidential data is displayed.

@ -46,12 +46,11 @@ This demo was tested in the following environments:
| OS and Version | Architecture | RN Platform | Date |
|:---------------|:-------------|:------------|:-----------|
| Windows 11 C++ | `win11-x64` | `v0.75.11` | 2024-12-22 |
| Windows 11 C# | `win11-x64` | `v0.75.11` | 2024-12-22 |
| Windows 11 C++ | `win11-arm` | `v0.77.2` | 2025-02-23 |
| Windows 11 C# | `win11-arm` | `v0.77.2` | 2025-02-23 |
| MacOS 15.3.2 | `darwin-x64` | `v0.76.7` | 2025-03-31 |
| MacOS 14.5 | `darwin-arm` | `v0.75.16` | 2024-12-22 |
| Windows 10 | `win10-x64` | `v0.74.6` | 2024-05-28 |
| Windows 11 | `win11-x64` | `v0.74.6` | 2024-05-28 |
| Windows 11 | `win11-arm` | `v0.74.5` | 2024-05-25 |
| MacOS 14.4 | `darwin-x64` | `v0.73.22` | 2024-03-24 |
| MacOS 14.5 | `darwin-arm` | `v0.73.30` | 2024-05-28 |
:::
@ -421,66 +420,36 @@ iex (New-Object System.Net.WebClient).DownloadString('https://aka.ms/rnw-vs2022-
If any step fails to install, open the dependencies page and expand "Manual
setup instructions" to find instructions for manual installation.
:::note pass
Even though React Native for Windows recommends enabling "Developer Mode", it is
not a requirement for this demo.
:::
</details>
### Project Setup
1) Create a new project using React Native `0.77.1`:
1) Create a new project using React Native `0.74.1`:
```bash
npx -y @react-native-community/cli@16 init SheetJSWin --version="0.77.1"
npx react-native init SheetJSWin --template react-native@0.74.1
cd SheetJSWin
npm install --save react-native-windows@0.77.2
```
:::info 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`.[^5]
:::
2) Create the Windows part of the application:
<Tabs groupId="rnwlang">
<TabItem value="cs" label="C#">
```bash
npx react-native init-windows --no-telemetry --overwrite --template old/uwp-cs-app
npx react-native-windows-init --no-telemetry --overwrite --language=cs
```
</TabItem>
<TabItem value="cpp" label="C++">
```bash
npx react-native init-windows --no-telemetry --overwrite --template old/uwp-cpp-app
npx react-native-windows-init --no-telemetry --overwrite
```
</TabItem>
</Tabs>
:::note pass
Older versions of this demo used the `react-native-windows-init` package. This
package does not support React Native `0.77`. The official instructions now
recommend directly installing `react-native-windows`.
When this demo was last tested, there was no template for a C# application using
the React Native "New Architecture". For consistency, both the C# and C++ demos
are tested using the "Old Architecture".
:::
3) Install the SheetJS library:
<CodeBlock language="bash">{`\
@ -629,11 +598,7 @@ curl -LO https://docs.sheetjs.com/reactnative/rnw/App.tsx
</TabItem>
</Tabs>
:::note pass
The `rm` command may fail if `App.js` does not exist. That error can be ignored.
:::
8) Launch the app again:
@ -675,35 +640,35 @@ select `pres.xlsx` . The app will refresh and display the data from the file.
## macOS Demo
0) Follow the "Setting up the development environment"[^6] guide in the React
:::info pass
When the demo was last tested, the official website asserted that the React
Native for macOS required React Native `0.71`.
**The official documentation is out of date.**
There exist official `react-native-macos` releases compatible with RN `0.73`
:::
0) Follow the "Setting up the development environment"[^5] guide in the React
Native documentation for "React Native CLI Quickstart" + "macOS" + "iOS".
### Project Setup
1) Create a new React Native project using React Native `0.76.8`:
1) Create a new React Native project using React Native `0.73`:
```bash
npx -y @react-native-community/cli init SheetJSmacOS --version 0.76.8
npx -y react-native init SheetJSmacOS --template react-native@^0.73.0
cd SheetJSmacOS
```
If prompted to install CocoaPods, type `Y`.
:::info 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`.[^5]
:::
2) Create the MacOS part of the application:
```bash
npx -y react-native-macos-init --no-telemetry
cd macos; pod install; cd ..
```
:::caution pass
@ -745,9 +710,9 @@ npx react-native run-macos
Close the running app from the dock and close the Metro terminal window.
:::caution pass
:::danger pass
In some test runs, the app failed with a warning
When the demo was last tested, the app failed with a warning
> No bundle URL present.
@ -817,6 +782,7 @@ A) Copy the highlighted line and paste under `/* Begin PBXBuildFile section */`:
/* Begin PBXBuildFile section */
// highlight-next-line
4717DC6A28CC499A00A9BE56 /* RCTDocumentPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 4717DC6928CC499A00A9BE56 /* RCTDocumentPicker.m */; };
5142014D2437B4B30078DB4F /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5142014C2437B4B30078DB4F /* AppDelegate.mm */; };
```
:::
@ -924,6 +890,7 @@ highlighted lines:
4717DC6928CC499A00A9BE56 /* RCTDocumentPicker.m */,
// highlight-end
5142014A2437B4B30078DB4F /* SheetJSmacOS-macOS */,
13B07FAE1A68108700A75B9A /* SheetJSmacOS-iOS */,
```
:::
@ -966,7 +933,7 @@ In some test runs, the development mode app failed with a "bundle URL" error:
> No bundle URL present.
The ["Production"](#production) section covers release builds and tests.
The ["Production" section](#production) section covers release builds and tests.
:::
@ -1011,39 +978,6 @@ If there are no instances, the app path can be found in the `DerivedData` folder
find ~/Library/Developer/Xcode/DerivedData -name SheetJSmacOS.app | grep Release
```
:::caution pass
During the last test, `xcodebuild` failed. Scrolling through the log reveals:
```
Welcome to Metro v0.80.12
Fast - Scalable - Integrated
node:events:497
throw er; // Unhandled 'error' event
^
Error: EMFILE: too many open files, watch
at FSWatcher._handle.onchange (node:internal/fs/watchers:207:21)
```
The file descriptor limits must be increased[^7].
```bash
sudo sysctl -w kern.maxfiles=10485760
sudo sysctl -w kern.maxfilesperproc=1048576
```
After making this change, forcefully reset `watchman`:
```bash
watchman shutdown-server
watchman watch-del-all
```
:::
15) Run the release app:
```bash
@ -1059,6 +993,4 @@ The app will refresh and display the data from the file.
[^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 ["Turbo Native Modules"](https://reactnative.dev/docs/the-new-architecture/pillars-turbomodules) in the React Native documentation.
[^5]: See [the compatibility table](https://github.com/react-native-community/cli) in the CLI project repository to determine which version of `@react-native-community/cli` is required for a given `react-native` version.
[^6]: See ["Setting up the development environment"](https://reactnative.dev/docs/environment-setup) in the React Native documentation. Select the "React Native CLI Quickstart" tab and choose the Development OS "macOS" and the Target OS "iOS".
[^7]: See ["macOS File Descriptor Limits"](https://facebook.github.io/watchman/docs/install.html#mac-os-file-descriptor-limits) in the `watchman` docs for more details
[^5]: See ["Setting up the development environment"](https://reactnative.dev/docs/environment-setup) in the React Native documentation. Select the "React Native CLI Quickstart" tab and choose the Development OS "macOS" and the Target OS "iOS".

@ -35,14 +35,14 @@ The latest prebuilt package matches NodeJS version `14.15.3`.
This demo was tested in the following deployments:
| Architecture | Version | NodeJS | Source | Date |
|:-------------|:---------------|:----------|:----------|:-----------|
| `darwin-x64` | `5.0.0-beta.4` | `14.15.3` | Pre-built | 2025-04-21 |
| `darwin-arm` | `4.0.0-rc.6` | `22.14.0` | Compiled | 2025-04-03 |
| `win11-x64` | `4.0.0-rc.6` | `14.15.3` | Pre-built | 2024-12-19 |
| `win11-arm` | `4.0.0-rc.6` | `22.14.0` | Compiled | 2025-02-23 |
| `linux-x64` | `5.0.0-beta.4` | `14.15.3` | Pre-built | 2025-04-21 |
| `linux-arm` | `4.0.0-rc.6` | `22.13.0` | Compiled | 2025-02-15 |
| Architecture | Version | NodeJS | Source | Date |
|:-------------|:-------------|:----------|:----------|:-----------|
| `darwin-x64` | `4.0.0-rc.6` | `14.15.3` | Pre-built | 2024-05-28 |
| `darwin-arm` | `4.0.0-rc.6` | `18.20.3` | Compiled | 2024-05-25 |
| `win10-x64` | `4.0.0-rc.4` | `14.15.3` | Pre-built | 2024-04-18 |
| `win11-arm` | `4.0.0-rc.6` | `20.10.0` | Compiled | 2024-05-28 |
| `linux-x64` | `4.0.0-rc.4` | `14.15.3` | Pre-built | 2024-03-21 |
| `linux-arm` | `4.0.0-rc.6` | `18.20.3` | Compiled | 2024-05-26 |
:::
@ -96,7 +96,7 @@ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz exit-on-epi
3) Create the standalone program:
```bash
npx -y nexe -t 14.15.3 xlsx-cli.js
npx nexe -t 14.15.3 xlsx-cli.js
```
<details open>
@ -109,7 +109,7 @@ missing. For unsupported NodeJS versions, packages must be built from source:
<TabItem value="unix" label="Linux/MacOS">
```bash
npx -y nexe xlsx-cli.js --build --python=$(which python3) --make="-j8"
npx nexe xlsx-cli.js --build --python=$(which python3) --make="-j8"
```
</TabItem>
@ -118,14 +118,13 @@ npx -y nexe xlsx-cli.js --build --python=$(which python3) --make="-j8"
On Windows x64, the `--build` flag suffices:
```bash
npx -y nexe xlsx-cli.js --build --make="-j8"
npx nexe xlsx-cli.js --build --make="-j8"
```
On Windows ARM, the target `windows-arm64-22.14.0` must be specified and the
command must be run in a "Native Tools Command Prompt":
On Windows ARM, the target `windows-arm64-20.10.0` must be specified:
```bash
npx -y nexe xlsx-cli.js --build --make="-j8" --target=windows-arm64-22.14.0
npx nexe xlsx-cli.js --build --make="-j8" --target=windows-arm64-20.10.0
```
**`vcbuild.bat` issues**
@ -136,36 +135,8 @@ The Windows ARM build may fail with a `vcbuild.bat` error:
Error: vcbuild.bat nosign release arm64 exited with code: 1
```
Pass the `--verbose` flag for more details:
```bash
npx -y nexe xlsx-cli.js --build --make="-j8" --target=windows-arm64-22.14.0 --verbose
```
Common error messages:
_"Python was not found"_
```
Python was not found; run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Manage App Execution Aliases.
```
Out of the box, Windows 11 will alias `python`, redirecting unsuspecting users
to the App Installer. This redirect must be disabled:
Type `alias` in the search bar and select "Manage app execution aliases", In the
settings pane, scroll down and turn off the alias for `python.exe`.
_"Could not find Python"_
```
Could not find Python.
```
After installing Python using the official installer, the command should be run
in a new terminal window.
_Python version mismatch ("Please use")_
Pass the `-v` flag for more details. In the most recent test, the error stemmed
from a Python version mismatch:
```
Node.js configure: found Python 2.7.18
@ -178,8 +149,8 @@ The resolved version of Python can be found with
where python
```
In some tests, a Python 2 version appeared first. This was fixed by finding the
Python 3 location and prepending it to `PATH`:
In the most recent test, a Python 2 version appeared first. This was fixed by
finding the Python 3 location and prepending it to `PATH`:
```cmd
set PATH="C:\correct\path\to\python\three";%PATH%
@ -211,16 +182,6 @@ This generates `xlsx-cli` or `xlsx-cli.exe` depending on platform.
</TabItem>
</Tabs>
The program should display CSV contents from the first sheet:
```text title="Expected Output"
Name,Index
Bill Clinton,42
GeorgeW Bush,43
Barack Obama,44
Donald Trump,45
Joseph Biden,46
```
[^1]: The project does not have a website. The [source repository](https://github.com/nexe/nexe) is publicly available.
[^2]: The NodeJS website hosts [prebuilt installers](https://nodejs.org/en/download/prebuilt-installer).

@ -26,7 +26,7 @@ spreadsheets and converting to other formats.
:::caution pass
With the official release of [NodeJS SEA](/docs/demos/cli/nodesea), Vercel opted
to deprecate `pkg`. It is still useful for deploying apps with NodeJS version 18
to deprecate `pkg`. It is still useful for deploying apps embedding NodeJS v18
or earlier since those versions do not support NodeJS SEA.
:::
@ -37,19 +37,12 @@ This demo was tested in the following deployments:
| Architecture | Version | NodeJS | Date |
|:-------------|:--------|:---------|:-----------|
| `darwin-x64` | `5.8.1` | `18.5.0` | 2025-04-21 |
| `darwin-arm` | `5.8.1` | `18.5.0` | 2025-02-13 |
| `win11-x64` | `5.8.1` | `18.5.0` | 2024-12-19 |
| `win11-arm` | `5.8.1` | `18.5.0` | 2025-02-23 |
| `linux-x64` | `5.8.1` | `18.5.0` | 2025-04-21 |
| `linux-arm` | `5.8.1` | `18.5.0` | 2025-02-15 |
:::
:::caution pass
`pkg` on Windows on ARM uses the X64 compatibility layer. It does not generate
a native ARM64 binary!
| `darwin-x64` | `5.8.1` | `18.5.0` | 2024-05-28 |
| `darwin-arm` | `5.8.1` | `18.5.0` | 2024-05-25 |
| `win10-x64` | `5.8.1` | `18.5.0` | 2024-04-18 |
| `win11-arm` | `5.8.1` | `18.5.0` | 2024-05-28 |
| `linux-x64` | `5.8.1` | `18.5.0` | 2024-03-21 |
| `linux-arm` | `5.8.1` | `18.5.0` | 2024-05-26 |
:::
@ -140,30 +133,11 @@ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz exit-on-epi
4) Create the standalone program:
```bash
npx -y pkg xlsx-cli.js
npx pkg xlsx-cli.js
```
This generates `xlsx-cli-linux`, `xlsx-cli-macos`, and `xlsx-cli-win.exe` .
:::info pass
If it is not possible to switch NodeJS versions, the `-t` flag can be used to
override the target detection:
**X64**
```bash
npx -y pkg -t 'node18-win-x64,node18-linux-x64,node18-macos-x64' xlsx-cli.js
```
**ARM64**
```bash
npx -y pkg -t 'node18-win-arm64,node18-linux-arm64,node18-macos-arm64' xlsx-cli.js
```
:::
5) Run the generated program, passing `pres.numbers` as the argument:
<Tabs groupId="os">

@ -30,11 +30,11 @@ This demo was tested in the following deployments:
| Architecture | Version | NodeJS | Date |
|:-------------|:--------|:----------|:-----------|
| `darwin-x64` | `2.4.4` | `23.11.0` | 2025-04-21 |
| `darwin-arm` | `2.4.4` | `22.14.0` | 2025-04-03 |
| `win11-x64` | `2.4.4` | `16.20.2` | 2024-12-19 |
| `linux-x64` | `2.4.4` | `23.11.0` | 2025-04-21 |
| `linux-arm` | `2.4.4` | `23.8.0` | 2025-02-15 |
| `darwin-x64` | `2.4.0` | `22.2.0` | 2024-05-28 |
| `darwin-arm` | `2.4.3` | `22.2.0` | 2024-05-25 |
| `win10-x64` | `2.4.2` | `16.20.2` | 2024-04-18 |
| `linux-x64` | `2.4.0` | `21.7.1` | 2024-03-21 |
| `linux-arm` | `2.4.3` | `20.13.1` | 2024-05-26 |
:::
@ -91,12 +91,12 @@ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz exit-on-epi
<TabItem value="unix" label="Linux/MacOS">
```bash
npx -y boxednode@2.4.4 -s xlsx-cli.js -t xlsx-cli
npx boxednode@2.4.3 -s xlsx-cli.js -t xlsx-cli
```
:::note pass
:::caution pass
In previous `linux-arm` test runs, the build failed with an error:
When this demo was last tested in `linux-arm`, the build failed with an error:
<pre>
../deps/v8/src/base/small-vector.h: In instantiation of <span {...B}>class v8::base::SmallVector&lt;std::pair&lt;const v8::internal::compiler::turboshaft::PhiOp*, const v8::internal::compiler::turboshaft::OpIndex&gt;, 16&gt;</span>:
@ -106,14 +106,14 @@ In previous `linux-arm` test runs, the build failed with an error:
{" |"} ^~~~~
</pre>
This affects NodeJS `22.2.0`, but does not affect `20.13.1` or `23.8.0`. It also
affects the [V8 JavaScript Engine](/docs/demos/engines/v8#build-v8).
This affects NodeJS `22.2.0`, but does not affect `20.13.1`. It affects the
[V8 JavaScript Engine](/docs/demos/engines/v8#build-v8).
The `-n` flag controls the target NodeJS version. For this demo, the following
command uses NodeJS `23.8.0`:
command uses NodeJS `20.13.1`:
```bash
npx -y boxednode@2.4.4 -s xlsx-cli.js -t xlsx-cli -n 23.8.0
npx boxednode@2.4.3 -s xlsx-cli.js -t xlsx-cli -n 20.13.1
```
:::
@ -122,12 +122,12 @@ npx -y boxednode@2.4.4 -s xlsx-cli.js -t xlsx-cli -n 23.8.0
<TabItem value="win" label="Windows">
```bash
npx -y boxednode@2.4.4 -s xlsx-cli.js -t xlsx-cli.exe -n 16.20.2
npx boxednode@2.4.3 -s xlsx-cli.js -t xlsx-cli.exe -n 16.20.2
```
:::info pass
The Windows builds require Visual Studio with "Desktop development with C++"
The Windows 10 build requires Visual Studio with "Desktop development with C++"
workload, Python 3.11, and NASM[^2].
**The build command must be run in "x64 Native Tools Command Prompt"**

@ -158,12 +158,13 @@ This demo was tested in the following deployments:
| Architecture | NodeJS | Date |
|:-------------|:----------|:-----------|
| `darwin-x64` | `22.14.0` | 2025-04-21 |
| `darwin-arm` | `23.8.0` | 2025-02-16 |
| `win11-x64` | `22.13.0` | 2025-01-19 |
| `win11-arm` | `22.14.0` | 2025-02-23 |
| `linux-x64` | `22.12.0` | 2025-01-02 |
| `linux-arm` | `22.13.0` | 2025-02-16 |
| `darwin-x64` | `22.2.0` | 2024-05-28 |
| `darwin-arm` | `22.2.0` | 2024-05-29 |
| `win10-x64` | `20.12.0` | 2024-03-26 |
| `win11-x64` | `20.13.1` | 2024-05-22 |
| `win11-arm` | `20.14.0` | 2024-06-11 |
| `linux-x64` | `20.11.1` | 2024-03-18 |
| `linux-arm` | `20.14.0` | 2024-06-10 |
:::
@ -284,7 +285,7 @@ codesign --remove-signature ./sheet2csv
```
</TabItem>
<TabItem value="win11-x64" label="Windows">
<TabItem value="win10-x64" label="Windows">
In PowerShell, the `Get-Command` command displays the location to `node.exe`:
@ -313,9 +314,6 @@ signtool remove /s .\sheet2csv.exe
`signtool` is included in the Windows SDK[^4].
If the Windows SDK is installed but the command fails, run the comand in the
"x64 Native Tools Command Prompt" that ships with Visual Studio.
:::
</TabItem>
@ -346,7 +344,7 @@ codesign -s - ./sheet2csv
```
</TabItem>
<TabItem value="win11-x64" label="Windows">
<TabItem value="win10-x64" label="Windows">
```bash
npx -y postject --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 sheet2csv.exe NODE_SEA_BLOB sheet2csv.blob
@ -354,7 +352,7 @@ npx -y postject --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 s
11) Resign the binary.
The following PowerShell commands generate a self-signed certificate:
The following sequence generates a self-signed certificate:
```powershell
$cert = New-SelfSignedCertificate -Type CodeSigning -DnsName www.onlyspans.net -CertStoreLocation Cert:\CurrentUser\My
@ -368,15 +366,6 @@ After creating a cert, sign the binary:
signtool sign /v /f mycert.pfx /p hunter2 /fd SHA256 sheet2csv.exe
```
:::info pass
`signtool` is included in the Windows SDK[^4].
If the Windows SDK is installed but the command fails, run the comand in the
"x64 Native Tools Command Prompt" that ships with Visual Studio.
:::
</TabItem>
<TabItem value="linux-x64" label="Linux">
@ -416,7 +405,7 @@ Signature=adhoc
```
</TabItem>
<TabItem value="win11-x64" label="Windows">
<TabItem value="win10-x64" label="Windows">
13) Validate the binary signature:
@ -424,15 +413,6 @@ Signature=adhoc
signtool verify sheet2csv.exe
```
:::info pass
`signtool` is included in the Windows SDK[^4].
If the Windows SDK is installed but the command fails, run the comand in the
"Native Tools Command Prompt" that ships with Visual Studio.
:::
If the certificate is self-signed, there may be an error:
```

@ -54,22 +54,22 @@ For example, the following script accepts one command line argument, parses the
specified file using the SheetJS `readFile` method[^1], generates CSV text from
the first worksheet using `sheet_to_csv`[^2], and prints to terminal:
```ts title="sheet2csv.ts"
<CodeBlock language="ts" title="sheet2csv.ts">{`\
const XLSX = require("xlsx");
\n\
/* process.argv[2] is the first argument to the script */
const filename = process.argv[2];
\n\
/* read file */
const wb = XLSX.readFile(filename);
\n\
/* generate CSV of first sheet */
const ws = wb.Sheets[wb.SheetNames[0]];
const csv = XLSX.utils.sheet_to_csv(ws);
\n\
/* print to terminal */
console.log(csv);
```
console.log(csv);`}
</CodeBlock>
## Complete Example
@ -79,19 +79,11 @@ This demo was last tested in the following deployments:
| Architecture | BunJS | Date |
|:-------------|:---------|:-----------|
| `darwin-x64` | `1.2.10` | 2025-04-21 |
| `darwin-arm` | `1.2.8` | 2025-04-03 |
| `win11-x64` | `1.1.40` | 2024-12-19 |
| `win11-arm` | `1.2.3` | 2025-02-23 |
| `linux-x64` | `1.1.43` | 2025-01-10 |
| `linux-arm` | `1.2.2` | 2025-02-16 |
:::
:::caution pass
BunJS on Windows on ARM uses the X64 compatibility layer. It does not generate
a native ARM64 binary!
| `darwin-x64` | `1.1.10` | 2024-05-28 |
| `darwin-arm` | `1.1.10` | 2024-05-29 |
| `win10-x64` | `1.1.12` | 2024-06-10 |
| `linux-x64` | `1.1.12` | 2024-06-09 |
| `linux-arm` | `1.1.12` | 2024-06-10 |
:::
@ -114,11 +106,13 @@ bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
:::caution pass
In some test runs, the command failed with a module resolution error:
On Windows, the command failed with a `ENOTEMPTY` error:
<CodeBlock>{`\
error: https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz failed to resolve`}
</CodeBlock>
```
error: InstallFailed extracting tarball for https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz
error: moving "https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz" to cache dir failed
ENOTEMPTY: Directory not empty (NtSetInformationFile())
```
The workaround is to prepend `xlsx@` to the URL:

@ -88,9 +88,6 @@ The same permissions that apply to normal Deno scripts apply to the compiler:
- The `--allow-net` option must be specified to allow the program to download
and upload spreadsheets.
- The `--allow-import` option must be specified in Deno 2 to allow the program
to import SheetJS scripts.
More flags can be found in the official permissions list[^5]
@ -102,19 +99,13 @@ This demo was last tested in the following deployments:
| Architecture | Deno | Date |
|:-------------|:---------|:-----------|
| `darwin-x64` | `2.2.11` | 2025-04-21 |
| `darwin-arm` | `2.1.10` | 2025-02-13 |
| `win11-x64` | `2.1.6` | 2025-01-19 |
| `win11-arm` | `2.2.1` | 2025-02-23 |
| `linux-x64` | `2.1.4` | 2025-01-02 |
| `linux-arm` | `2.1.10` | 2025-02-15 |
:::
:::caution pass
Deno on Windows on ARM uses the X64 compatibility layer. It does not generate
a native ARM64 binary!
| `darwin-x64` | `1.43.6` | 2024-05-28 |
| `darwin-arm` | `1.43.6` | 2024-05-23 |
| `win10-x64` | `1.41.3` | 2024-03-24 |
| `win11-x64` | `1.43.6` | 2024-05-25 |
| `win11-arm` | `1.43.6` | 2024-05-25 |
| `linux-x64` | `1.41.3` | 2024-03-18 |
| `linux-arm` | `1.43.6` | 2024-05-25 |
:::
@ -129,7 +120,7 @@ curl -o pres.numbers https://docs.sheetjs.com/pres.numbers
2) Test the script with `deno run`:
```bash
deno run -r --allow-read --allow-import https://docs.sheetjs.com/cli/sheet2csv.ts pres.numbers
deno run -r --allow-read https://docs.sheetjs.com/cli/sheet2csv.ts pres.numbers
```
The script should display CSV contents from the first sheet:
@ -143,39 +134,14 @@ Donald Trump,45
Joseph Biden,46
```
:::note pass
In older versions of Deno, the `--allow-import` flag must be omitted:
```bash
deno run -r --allow-read https://docs.sheetjs.com/cli/sheet2csv.ts pres.numbers
```
:::
3) Compile `sheet2csv`:
```bash
deno compile -r --allow-read --allow-import https://docs.sheetjs.com/cli/sheet2csv.ts
```
:::note pass
In older versions of Deno, the `--allow-import` flag must be omitted:
3) Compile and run `sheet2csv`:
```bash
deno compile -r --allow-read https://docs.sheetjs.com/cli/sheet2csv.ts
```
:::
4) Run the command:
```bash
./sheet2csv pres.numbers
```
The program should display the same CSV contents as the script (from step 2).
The program should display the same CSV contents as the script (from step 2)
[^1]: See [`readFile` in "Reading Files"](/docs/api/parse-options)
[^2]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output)

@ -1,247 +0,0 @@
---
title: TxikiJS Standalone Apps
sidebar_label: txiki.js
pagination_prev: demos/desktop/index
pagination_next: demos/data/index
sidebar_custom_props:
summary: Compiled apps powered by QuickJS and txiki.js
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
`txiki.js` is a small runtime powered by [QuickJS](/docs/demos/engines/quickjs).
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses `txiki.js` and SheetJS to create a standalone CLI tool for
parsing spreadsheets and generating CSV rows.
:::caution TxikiJS support is considered experimental.
Great open source software grows with user tests and reports. Any issues should
be reported to the `txiki.js` project for further diagnosis.
:::
## Integration Details
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
can be evaluated and consumed in TxikiJS.
The platform provides APIs for filesystem operations that differ from NodeJS:
- `tjs.readFile` reads raw data from a specified filename and returns a Promise
that resolves to a `Uint8Array`
- `tjs.args` is an array of arguments. In the compiled program, the first value
will be the program name and the second value will be the first argument.
The SheetJS filesystem methods ([`readFile`](/docs/api/parse-options) and
[`writeFile`](/docs/api/parse-options)) do not recognize the `txiki.js` APIs.
Fortunately, `read` and `write` directly work with `Uint8Array` data.
The following example reads and parses `pres.xlsx` in the current directory:
```js title="Parse pres.xlsx in TxikiJS"
/* read data from pres.xlsx into a Uint8Array */
const data = await tjs.readFile("pres.xlsx");
/* parse data and generate a SheetJS workbook object */
const wb = XLSX.read(data);
```
### Script Requirements
The compiler does not bundle scripts automatically. Scripts that exclusively use
web APIs, SheetJS API methods, and `tjs` API methods can be bundled and compiled.
[`esbuild`](/docs/demos/frontend/bundler/esbuild) is the recommended bundler.
For example, the following script accepts one command line argument, parses the
specified file using `tjs.readFile` and the SheetJS `read` method[^1], generates
CSV text from the first sheet using `sheet_to_csv`[^2], and prints the result:
```js title="sheet2csv.js (before bundling)"
const XLSX = require("./xlsx.full.min");
/* tjs.args[1] is the first argument to the script */
const filename = tjs.args[1];
/* read and parse file */
const data = await tjs.readFile(filename);
const wb = XLSX.read(data);
/* generate CSV of first sheet */
const ws = wb.Sheets[wb.SheetNames[0]];
const csv = XLSX.utils.sheet_to_csv(ws);
/* print to terminal */
console.log(csv);
```
## Complete Example
:::note Tested Deployments
This demo was tested in the following deployments:
| Architecture | TxikiJS | Date |
|:-------------|:----------|:-----------|
| `darwin-x64` | `24.12.0` | 2025-04-19 |
| `darwin-arm` | `24.12.0` | 2025-04-19 |
| `win11-x64` | `24.12.0` | 2025-04-19 |
| `win11-arm` | `24.12.0` | 2025-04-19 |
| `linux-x64` | `24.12.0` | 2025-04-19 |
| `linux-arm` | `24.12.0` | 2025-04-19 |
:::
:::caution pass
TxikiJS on Windows on ARM uses the X64 compatibility layer. It does not generate
a native ARM64 binary!
:::
0) Create a new project folder and download `txiki.js`[^3]:
<Tabs groupId="triple">
<TabItem value="darwin-x64" label="MacOS">
```bash
mkdir sheetjs-txiki
cd sheetjs-txiki
curl -LO https://github.com/saghul/txiki.js/releases/download/v24.12.0/txiki-macos.zip
unzip txiki-macos.zip
mv txiki-macos/tjs .
chmod +x tjs
```
</TabItem>
<TabItem value="linux-x64" label="Linux">
```bash
mkdir sheetjs-txiki
cd sheetjs-txiki
git clone --recursive https://github.com/saghul/txiki.js --shallow-submodules
cd txiki.js
make
cp build/tjs ../
cd ..
```
</TabItem>
<TabItem value="win11-x64" label="Windows">
```bash
mkdir sheetjs-txiki
cd sheetjs-txiki
curl.exe -LO https://github.com/saghul/txiki.js/releases/download/v24.12.0/txiki-windows-x86_64.zip
tar xf txiki-windows-x86_64.zip
mv txiki-windows-x86_64\* .
```
</TabItem>
</Tabs>
1) Download the test file https://docs.sheetjs.com/pres.numbers:
```bash
curl -LO https://docs.sheetjs.com/pres.numbers
```
:::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/pres.numbers
```
:::
2) Save the [contents of the `sheet2csv.js` code block](#script-requirements)
to `sheet2csv.js` in the project folder.
```bash
curl -LO https://docs.sheetjs.com/txikijs/sheet2csv.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/txikijs/sheet2csv.js
```
:::
3) Download the SheetJS Standalone script and move to the project directory:
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>xlsx.full.min.js</a></li>
</ul>
<CodeBlock language="bash">{`\
curl -LO 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 'LO'.
```
`curl.exe` must be invoked directly:
<CodeBlock language="bash">{`\
curl.exe -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}
</CodeBlock>
:::
4) Bundle the script:
```js
npx -y esbuild sheet2csv.js --bundle --outfile=bundle.js --platform=neutral
```
5) Compile and run `sheet2csv`:
```bash
./tjs compile bundle.js sheet2csv
./sheet2csv pres.numbers
```
The program should display the same CSV contents as the script:
```text title="Expected Output"
Name,Index
Bill Clinton,42
GeorgeW Bush,43
Barack Obama,44
Donald Trump,45
Joseph Biden,46
```
[^1]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^2]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output)
[^3]: When this demo was last tested, the [ZIP archive for version `24.12.0`](https://github.com/saghul/txiki.js/releases/tag/v24.12.0) was downloaded and extracted.

@ -54,13 +54,6 @@ The following frameworks have been tested on the following platforms:
<FrameworkData/>
:::note pass
Asterisks (✱) in the Windows for ARM column mark tests that generated x64
binaries. The CLI tools run under the x64 emulator integrated in Windows.
:::
:::tip pass
The [`xlsx-cli`](https://cdn.sheetjs.com/xlsx-cli/) NodeJS script is available

@ -147,7 +147,7 @@ function SheetJSQLWriter() {
Query builders are designed to simplify query generation and normalize field
types and other database minutiae.
**KnexJS**
**Knex**
**[The exposition has been moved to a separate page.](/docs/demos/data/knex)**

@ -37,13 +37,12 @@ SQLite-compatible database built into Chromium and Google Chrome.
This demo was tested in the following environments:
| Platform | Connector Library | Date |
|:-----------------|:----------------------------|:-----------|
| Chromium 131 | `sql.js` (`1.8.0`) | 2025-01-08 |
| Konqueror 22 | `sql.js` (`1.8.0`) | 2025-04-23 |
| NodeJS `20.18.0` | `better-sqlite3` (`11.7.2`) | 2025-01-08 |
| BunJS `1.1.43` | (built-in) | 2025-01-08 |
| Deno `2.1.4` | `sqlite` (`3.9.1`) | 2025-01-09 |
| Platform | Connector Library | Date |
|:-----------------|:---------------------------|:-----------|
| Chromium 122 | `sql.js` (`1.8.0`) | 2024-04-09 |
| NodeJS `20.12.1` | `better-sqlite3` (`9.4.5`) | 2024-04-09 |
| BunJS `1.1.3` | (built-in) | 2024-04-09 |
| Deno `1.42.1` | `sqlite` (`3.8`) | 2024-04-09 |
:::
@ -316,16 +315,6 @@ curl -LO https://docs.sheetjs.com/sqlite/SheetJSQLiteDeno.ts
deno run --allow-read --allow-write SheetJSQLiteDeno.ts
```
:::caution pass
Deno 2 requires the `--allow-import` entitlement:
```bash
deno run --allow-read --allow-write --allow-import SheetJSQLiteDeno.ts
```
:::
Open `SheetJSQLiteDeno.xlsx` with a spreadsheet editor.
[^1]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)

@ -25,10 +25,10 @@ This demo was tested in the following environments:
| Version | Database | Connector Module | Date |
|:----------|:---------|:-----------------|:-----------|
| `0.21.20` | SQLite | `sqlite3` | 2025-01-09 |
| `2.4.2` | SQLite | `better-sqlite3` | 2025-01-09 |
| `2.5.1` | SQLite | `better-sqlite3` | 2025-01-09 |
| `3.1.0` | SQLite | `better-sqlite3` | 2025-01-09 |
| `0.21.20` | SQLite | `sqlite3` | 2024-04-09 |
| `2.4.2` | SQLite | `better-sqlite3` | 2024-04-09 |
| `2.5.1` | SQLite | `better-sqlite3` | 2024-04-09 |
| `3.1.0` | SQLite | `better-sqlite3` | 2024-04-09 |
:::
@ -192,7 +192,7 @@ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz knex be
For KnexJS version `0.21.20`, the `sqlite3` module must be installed:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz knex@0.21.20 sqlite3`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz knex sqlite3`}
</CodeBlock>
:::

@ -36,12 +36,9 @@ This demo was tested in the following environments:
| Postgres | Connector Library | Date |
|:---------|:------------------|:-----------|
| `17.2` | `pg` (`8.13.1`) | 2025-01-03 |
| `16.6` | `pg` (`8.13.1`) | 2025-01-03 |
| `15.10` | `pg` (`8.13.1`) | 2025-01-03 |
| `14.15` | `pg` (`8.13.1`) | 2025-01-03 |
| `13.18` | `pg` (`8.13.1`) | 2025-01-03 |
| `12.22` | `pg` (`8.13.1`) | 2025-01-03 |
| `16.2.1` | `pg` (`8.11.4`) | 2024-03-31 |
| `15.6` | `pg` (`8.11.4`) | 2024-03-31 |
| `14.11` | `pg` (`8.11.4`) | 2024-03-31 |
:::
@ -132,140 +129,80 @@ for(let row of aoo) {
### Creating a Table
The worksheet can be scanned to determine column names and types. With the names
and types, a `CREATE TABLE` query can be written.
The array of objects can be scanned to determine column names and types. With
the names and types, a `CREATE TABLE` query can be written.
<details>
<summary><b>Implementation Details</b> (click to show)</summary>
The `sheet_to_pg_table` function:
The `aoo_to_pg_table` function:
- scans worksheet cells to determine column names and types
- scans each row object to determine column names and types
- drops and creates a new table with the determined column names and types
- loads the entire dataset into the new table
```js
/* create table and load data given a worksheet and a PostgreSQL client */
async function sheet_to_pg_table(client, worksheet, tableName) {
if (!worksheet['!ref']) return;
/* create table and load data given an array of objects and a PostgreSQL client */
async function aoo_to_pg_table(client, aoo, table_name) {
/* define types that can be converted (e.g. boolean can be stored in float) */
const T_FLOAT = ["float8", "boolean"];
const T_BOOL = ["boolean"];
const range = XLSX.utils.decode_range(worksheet['!ref']);
/* Extract headers from first row, clean names for PostgreSQL */
const headers = [];
for (let col = range.s.c; col <= range.e.c; col++) {
const cellAddress = XLSX.utils.encode_cell({ r: range.s.r, c: col });
const cell = worksheet[cellAddress];
const headerValue = cell ? String(cell.v).replace(/[^a-zA-Z0-9_]/g, '_') : `column_${col + 1}`;
headers.push(headerValue.toLowerCase());
}
/* Group cell values by column for type deduction */
const columnValues = headers.map(() => []);
for (let row = range.s.r + 1; row <= range.e.r; row++) {
for (let col = range.s.c; col <= range.e.c; col++) {
const cellAddress = XLSX.utils.encode_cell({ r: row, c: col });
const cell = worksheet[cellAddress];
columnValues[col].push(cell);
}
}
/* Deduce PostgreSQL type for each column */
/* types is a map from column headers to Knex schema column type */
const types = {};
headers.forEach((header, idx) => {
types[header] = deduceType(columnValues[idx]);
});
/* names is an ordered list of the column header names */
const names = [];
/* loop across each row object */
aoo.forEach(row =>
/* Object.entries returns a row of [key, value] pairs */
Object.entries(row).forEach(([k,v]) => {
/* If this is first occurrence, mark unknown and append header to names */
if(!types[k]) { types[k] = ""; names.push(k); }
/* skip null and undefined values */
if(v == null) return;
/* check and resolve type */
switch(typeof v) {
/* change type if it is empty or can be stored in a float */
case "number": if(!types[k] || T_FLOAT.includes(types[k])) types[k] = "float8"; break;
/* change type if it is empty or can be stored in a boolean */
case "boolean": if(!types[k] || T_BOOL.includes(types[k])) types[k] = "boolean"; break;
/* no other type can hold strings */
case "string": types[k] = "text"; break;
default: types[k] = "text"; break;
}
})
);
/* Delete table if it exists in the DB */
await client.query(format('DROP TABLE IF EXISTS %I', tableName));
const query = format("DROP TABLE IF EXISTS %I;", table_name);
await client.query(query);
/* Create table */
const createTableSQL = format(
'CREATE TABLE %I (%s)',
tableName,
headers.map(header => format('%I %s', header, types[header])).join(', ')
);
await client.query(createTableSQL);
{
const entries = Object.entries(types);
const Istr = entries.map(e => format(`%I ${e[1]}`, e[0])).join(", ");
let query = format.withArray(`CREATE TABLE %I (${Istr});`, [ table_name ]);
await client.query(query);
}
/* Insert data row by row */
for (let row = range.s.r + 1; row <= range.e.r; row++) {
const values = headers.map((header, col) => {
const cellAddress = XLSX.utils.encode_cell({ r: row, c: col });
const cell = worksheet[cellAddress];
return parseValue(cell, types[header]);
});
const insertSQL = format(
'INSERT INTO %I (%s) VALUES (%s)',
tableName,
headers.map(h => format('%I', h)).join(', '),
values.map(() => '%L').join(', ')
/* Insert each row */
for(let row of aoo) {
const ent = Object.entries(row);
const Istr = Array.from({length: ent.length}, ()=>"%I").join(", ");
const Lstr = Array.from({length: ent.length}, ()=>"%L").join(", ");
let query = format.withArray(
`INSERT INTO %I (${Istr}) VALUES (${Lstr});`,
[ table_name, ...ent.map(x => x[0]), ...ent.map(x => x[1]) ]
);
await client.query(format(insertSQL, ...values));
await client.query(query);
}
}
function deduceType(cells) {
if (!cells || cells.length === 0) return 'text';
const nonEmptyCells = cells.filter(cell => cell && cell.v != null);
if (nonEmptyCells.length === 0) return 'text';
// Check for dates by looking at both cell type and formatted value
const isDateCell = cell => cell?.t === 'd' || (cell?.t === 'n' && cell.w && /\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{4}|\d{2}-[A-Za-z]{3}-\d{4}|[A-Za-z]{3}-\d{2}|\d{1,2}-[A-Za-z]{3}/.test(cell.w));
if (nonEmptyCells.some(isDateCell)) { return 'date'; }
const allBooleans = nonEmptyCells.every(cell => cell.t === 'b');
if (allBooleans) { return 'boolean'; }
const allNumbers = nonEmptyCells.every(cell => cell.t === 'n' || (cell.t === 's' && !isNaN(cell.v.replace(/[,$\s%()]/g, ''))));
if (allNumbers) {
const numbers = nonEmptyCells.map(cell => {
if (cell.t === 'n') return cell.v;
return parseFloat(cell.v.replace(/[,$\s%()]/g, ''));
});
const needsPrecision = numbers.some(num => {
const str = num.toString();
return str.includes('e')
|| (str.includes('.') && str.split('.')[1].length > 6)
|| Math.abs(num) > 1e15;
});
return needsPrecision ? 'numeric' : 'double precision';
}
return 'text';
}
function parseValue(cell, type) {
if (!cell || cell.v == null) return null;
switch (type) {
case 'date':
if (cell.t === 'd') { return cell.v.toISOString().split('T')[0]; }
if (cell.t === 'n') {
const date = new Date((cell.v - 25569) * 86400 * 1000);
return date.toISOString().split('T')[0];
}
return null;
case 'numeric':
case 'double precision':
if (cell.t === 'n') return cell.v;
if (cell.t === 's') {
const cleaned = cell.v.replace(/[,$\s%()]/g, '');
if (!isNaN(cleaned)) return parseFloat(cleaned);
}
return null;
case 'boolean':
return cell.t === 'b' ? cell.v : null;
default:
return String(cell.v);
}
return client;
}
```
@ -274,7 +211,7 @@ function parseValue(cell, type) {
## Complete Example
0) Install and start the PostgreSQL serve r.
0) Install and start the PostgreSQL server.
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
@ -293,23 +230,6 @@ Or, if you don't want/need a background service you can just run:
LC_ALL="C" /usr/local/opt/postgresql@16/bin/postgres -D /usr/local/var/postgresql@16
```
On Linux, install `postgresql` by running the following script:
```bash
echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list
wget -qO - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt update
sudo apt install postgresql-16
sudo systemctl start postgresql
# Optional: Create user with password
sudo -u postgres createuser -P $USER
sudo -u postgres psql -c "ALTER USER $USER WITH SUPERUSER;"
```
If running the optional user creation steps above, a PostgreSQL password will be required. [^8]
Run the command to start a local database instance.
</details>
@ -320,17 +240,6 @@ Run the command to start a local database instance.
dropdb SheetJSPG
```
:::caution pass
Some Linux installations do not create the `dropdb` command. The command can be
run through the `postgres` command-line tool:
```bash title="dropdb in Ubuntu/Debian"
sudo -i -u postgres dropdb SheetJSPG
```
:::
:::info pass
If the server is running elsewhere, or if the username is different from the
@ -344,35 +253,12 @@ current user, command-line flags can override the defaults.
:::
:::note pass
If the database does not exist, the command will fail with the following error:
```
dropdb: error: database removal failed: ERROR: database "SheetJSPG" does not exist
```
This error can be safely ignored.
:::
2) Create an empty `SheetJSPG` database using the `createdb` command:
```bash
createdb SheetJSPG
```
:::caution pass
Some Linux installations do not create the `createdb` command. The command can be
run through the `postgres` command-line tool:
```bash title="createdb in Ubuntu/Debian"
sudo -i -u postgres createdb SheetJSPG
```
:::
:::note pass
`createdb` supports the same `-h`, `-p`, and `-U` flags as `dropdb`.
@ -392,7 +278,7 @@ npm init -y
4) Install the `pg` connector module:
```bash
npm i --save pg@8.13.1
npm i --save pg@8.11.4
```
5) Save the following example codeblock to `PGTest.js`:
@ -430,10 +316,6 @@ correct host name and port number.
- If the server expects a different username and password, uncomment the `user`
and `password` lines and replace the values with the username and password.
- For Ubuntu/Debian PostgreSQL installations, the default user is `postgres`.
The password must be set during installation or using `sudo -u postgres psql`
followed by `\password postgres` in the `psql` prompt.
7) Run the script:
```bash
@ -497,11 +379,6 @@ correct host name and port number.
- If the server expects a different username and password, uncomment the `user`
and `password` lines and replace the values with the username and password.
- For Ubuntu/Debian PostgreSQL installations, the default user is postgres.
The password must be set during installation or using `sudo -u postgres psql`
followed by `\password postgres` in the `psql` prompt.
11) Fetch the example file [`pres.numbers`](https://docs.sheetjs.com/pres.numbers):
```bash
@ -543,5 +420,4 @@ psql SheetJSPG -c 'SELECT * FROM "Presidents";'
[^4]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`.
[^5]: See [`writeFile` in "Writing Files"](/docs/api/write-options)
[^6]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^7]: The [`pg-format`](https://npm.im/pg-format) package is available on the public NPM registry. Even though the project is marked as deprecated, the official [`pg` website still recommends `pg-format`](https://node-postgres.com/features/queries#parameterized-query:~:text=use%20pg%2Dformat%20package%20for%20handling%20escaping)
[^8]: PostgreSQL on Linux uses [SCRAM authentication by default, which requires a password](https://www.postgresql.org/docs/current/auth-password.html)
[^7]: The [`pg-format`](https://npm.im/pg-format) package is available on the public NPM registry. Even though the project is marked as deprecated, the official [`pg` website still recommends `pg-format`](https://node-postgres.com/features/queries#parameterized-query:~:text=use%20pg%2Dformat%20package%20for%20handling%20escaping)

@ -34,9 +34,9 @@ against SQL injection and other vulnerabilities.
This demo was tested in the following environments:
| MariaDB | Connector Library | Date |
|:---------|:--------------------|:-----------|
| `11.6.2` | `mysql2` (`3.12.0`) | 2025-01-19 |
| MariaDB | Connector Library | Date |
|:---------|:-------------------|:-----------|
| `11.3.2` | `mysql2` (`3.9.7`) | 2024-05-04 |
:::

@ -12,8 +12,6 @@ sidebar_custom_props:
</head>
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
[AlaSQL](https://alasql.org/) is a pure JavaScript in-memory SQL database. It
@ -30,12 +28,10 @@ strategies for general data processing in AlaSQL expressions.
This demo was tested in the following environments:
| Environment | AlaSQL | Date |
|:-------------|:-------|:----------:|
| Chromium 135 | 3.1.0 | 2025-04-23 |
| Konqueror 22 | 3.1.0 | 2025-04-23 |
| NodeJS | 3.1.0 | 2025-04-23 |
| BunJS | 3.1.0 | 2025-04-23 |
| Environment | AlaSQL | Date |
|:--------------------|:-------|:----------:|
| NodeJS | 3.1.0 | 2024-06-03 |
| Standalone (Chrome) | 3.1.0 | 2024-06-03 |
:::
@ -267,8 +263,8 @@ const { promise: alasql } = require("alasql");
1) Create an empty folder for the project:
```bash
mkdir sheetjs-alasql
cd sheetjs-alasql
mkdir alasql
cd alasql
```
2) In the folder, create a stub `package.json` with the `xlsx` override:
@ -283,23 +279,10 @@ cd sheetjs-alasql
3) Install SheetJS and AlaSQL:
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
<CodeBlock language="bash">{`\
npm i --save alasql@3.1.0 https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="bun" label="Bun">
<CodeBlock language="bash">{`\
bun i --save alasql@3.1.0 https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
</Tabs>
4) Download the test file https://docs.sheetjs.com/pres.numbers :
```bash
@ -328,24 +311,11 @@ const { promise: alasql } = require("alasql");
6) Run the test script
<Tabs groupId="ssplat">
<TabItem value="nodejs" label="NodeJS">
```bash
node SheetJSAlaSQL.js
```
</TabItem>
<TabItem value="bun" label="Bun">
```bash
bun run SheetJSAlaSQL.js
```
</TabItem>
</Tabs>
The script will print the following array of objects:
The output should display:
```js
[
@ -355,7 +325,7 @@ The script will print the following array of objects:
]
```
The script will generate `SheetJSAlaSQL1.xlsx` with an additional row:
The script should generate `SheetJSAlaSQL1.xlsx` with the additional row:
```csv
Name,Index

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