Compare commits

..

14 Commits

Author SHA1 Message Date
3b81f8931b chore/docs: [Electron Demo] - fix mistakes in docs. 2025-05-08 20:04:36 +02:00
8c4bd369c4 chore/docs: [Electron Demo] - improve code comments and documentation. 2025-05-03 15:53:11 +02:00
418f16a872 feat/docs: [Electron Demo] - full context isolation between processes. 2025-05-02 18:35:16 +02:00
603b49a9fd deps: [Electron Demo] - bump electron 35.1.2 --> 36.1.0 2025-05-02 15:59:04 +02:00
89a8f00ff3 feat: [Electron Demo] - rendering optimizations and error handling. 2025-05-02 15:50:50 +02:00
e5a1d470ad chore: [Electron Demo] - update footer to feature small icons, update screenshots. 2025-05-01 12:51:38 +02:00
3134922c55 docs: [Electron Demo] - update documentation to include information about file type associations. 2025-05-01 12:38:41 +02:00
843441893b fix: [Electron Demo] - move dropzone to entire document/window. 2025-05-01 12:15:07 +02:00
d35203cb2b chore: [Electron Demo] - remove unicode emoji in code comments. 2025-04-30 20:55:39 +02:00
d1efa326f6 feat: [Electron Demo] - implement OS level file associations via dist build. 2025-04-30 20:15:48 +02:00
93de5bce80 fix: [Electron Demo] - update screenshots, and re-style the details element. 2025-04-30 18:13:38 +02:00
4ff62b8032 chore: [Electron Demo] - remove unused variable declaration. 2025-04-30 14:04:59 +02:00
fd0d5a6ad0 chore: fix spelling error.
refactor: replace tabs with `details` element, hide dropzone on file-uploads.
2025-04-30 13:53:56 +02:00
22d9563f39 feat: [Electron Demo] - Add CSS styles, loading spinner, worksheet tabs. 2025-04-28 21:07:18 +02:00
83 changed files with 1572 additions and 2172 deletions

3
.gitignore vendored

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

@ -352,7 +352,7 @@
<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>
<Cell ss:StyleID="s16"><Data ss:Type="String"></Data></Cell>

@ -118,20 +118,20 @@ importScripts("https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.mi
### Type Checker
:::danger VS Code Telemetry and Data Exfiltration
:::danger VSCode Telemetry and Data Exfiltration
The official builds of Visual Studio Code ("VS Code" or "VSCode") embed
telemetry and send information to Microsoft servers.
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 VS Code.**
**[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 VS Code from source or using IDEs
the SheetJS team strongly encourages building VSCode from source or using IDEs
that do not exfiltrate data.
:::
The type checker integrated in VSCodium and VS Code does not currently provide
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:
@ -176,7 +176,7 @@ The `.d.ts` file extension must be omitted.
JSDoc types using the `@import` directive are not supported in `<script>` tags.
**This is a known bug with VS Code!**
**This is a known bug with VSCode!**
:::

@ -644,7 +644,7 @@ After saving the file, run a local web server in the folder with the HTML file.
For example, if NodeJS is installed:
```bash
npx -y http-server .
npx http-server .
```
The server process will display a URL (typically `http://127.0.0.1:8080`). Open

@ -489,20 +489,20 @@ function SheetJSAoAFilled() {
### Select Data Rows
At this point, each data row will have the year in column `A` and dollar value
in column `C`. The year (first value in the row) will be between 2007 and 2029.
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:
```js
const is_valid_row = r =>
r[0] >= 2007 && r[0] <= 2029 // year (column A) is between 2007 and 2029
r[0] >= 2007 && r[0] <= 2024 // year (column A) is between 2007 and 2024
&& r[2] > 0; // dollar value (column C) is positive
```
`Array#filter`, using the previous test, can select the matching rows:
```js
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2024 && r[2] > 0);
```
<details>
@ -522,7 +522,7 @@ function SheetJSAoAFiltered() {
var last_year = 0;
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2024 && r[2] > 0);
/* display data */
setRows(rows);
})(); }, []);
@ -598,7 +598,7 @@ function SheetJSObjects() {
var last_year = 0;
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2024 && r[2] > 0);
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
/* display data */
@ -706,7 +706,7 @@ function StudentAidTotal() {
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2024 && r[2] > 0);
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
@ -761,7 +761,7 @@ Save the following script to `SheetJSStandaloneDemo.html`:
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
\n\
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2024 && r[2] > 0);
\n\
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
@ -781,7 +781,7 @@ After saving the file, run a local web server in the folder with the HTML file.
For example, if NodeJS is installed:
```bash
npx -y http-server .
npx http-server .
```
The server process will display a URL (typically `http://127.0.0.1:8080`). Open
@ -827,7 +827,7 @@ const XLSX = require("xlsx");
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2024 && r[2] > 0);
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
@ -900,7 +900,7 @@ Save the following script to `SheetJSNW.html`:
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
\n\
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2024 && r[2] > 0);
\n\
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));
@ -1001,7 +1001,7 @@ const App = () => {
raw_data.forEach(r => last_year = r[0] = (r[0] != null ? r[0] : last_year));
/* select data rows */
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2029 && r[2] > 0);
const rows = raw_data.filter(r => r[0] >= 2007 && r[0] <= 2024 && r[2] > 0);
/* generate row objects */
const objects = rows.map(r => ({FY: r[0], FQ: r[1], total: r[8]}));

@ -35,7 +35,6 @@ This demo was tested in the following configurations:
| Platform | Architecture | Date |
|:------------------------------------------------------------------|:-------------|:-----------|
| NVIDIA RTX 5090 (32 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | 2025-05-17 |
| NVIDIA RTX 4090 (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | 2025-04-17 |
| NVIDIA RTX 4090 (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `linux-x64` | 2025-01-28 |
| AMD RX 7900 XTX (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | 2025-01-12 |

@ -42,7 +42,7 @@ This demo was tested in the following deployments:
|:-------------|:----------------|:-------|:-------|:-----------|
| `darwin-x64` | Duktape `2.7.0` | 2.2.3 | 3.13.1 | 2025-03-31 |
| `darwin-arm` | Duktape `2.7.0` | 2.2.3 | 3.13.2 | 2025-03-30 |
| `win11-x64` | Duktape `2.7.0` | 2.2.3 | 3.11.9 | 2025-04-28 |
| `win11-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 |
@ -203,7 +203,7 @@ DataFrame. The DataFrame will be exported to the binary XLSB spreadsheet format.
:::note pass
The Windows build requires Visual Studio with "Desktop development with C++".
**Commands must be run in a "Native Tools Command Prompt" session.**
Commands must be run in a "Native Tools Command Prompt" session.
:::
@ -215,14 +215,6 @@ python3 -m pip install pandas
:::info pass
On Windows, Python may be available as `python.exe`:
```bash
python.exe -m pip install pandas
```
---
On macOS and Linux, the install command may require root access:
```bash
@ -313,13 +305,15 @@ cd ..
</TabItem>
<TabItem value="win11-x64" label="Windows">
- Download and extract the source tarball:
- Download and extract the source tarball. Commands must be run in WSL `bash`:
```bash
curl -LO https://duktape.org/duktape-2.7.0.tar.xz
tar -xJf duktape-2.7.0.tar.xz
```
(Run `bash`, then run the aforementioned commands, then run `exit` to exit WSL)
- Enter the source folder:
```bash
@ -478,17 +472,6 @@ def eval_file(ctx, path):
python3 SheetJSPandas.py pres.numbers
```
:::info pass
On Windows, Python may be available as `python.exe`:
```bash
python.exe SheetJSPandas.py pres.numbers
```
:::
If successful, the script will display DataFrame metadata:
```
@ -546,7 +529,7 @@ This demo was tested in the following deployments:
|:-------------|:----------------|:--------|:-------|:-----------|
| `darwin-x64` | Duktape `2.7.0` | 1.26.0 | 3.13.1 | 2025-03-31 |
| `darwin-arm` | Duktape `2.7.0` | 1.26.0 | 3.13.2 | 2025-03-30 |
| `win11-x64` | Duktape `2.7.0` | 1.28.1 | 3.11.9 | 2025-04-28 |
| `win11-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 |
@ -616,18 +599,10 @@ python3 -m pip install polars
:::info pass
On Windows, Python may be available as `python.exe`:
```bash
python.exe -m pip install polars
```
---
On macOS and Linux, the install command may require root access:
```bash
sudo python3 -m pip install polars
sudo python3 -m pip install pandas
```
:::
@ -687,16 +662,6 @@ cp ../libduktape.* ../SheetJSPandas.py ../sheetjs.py ../*.js ../*.numbers .
python3 SheetJSPandas.py pres.numbers
```
:::info pass
On Windows, Python may be available as `python.exe`:
```bash
python.exe SheetJSPandas.py pres.numbers
```
:::
:::note pass
If the virtual environment was configured in the previous step, run:
@ -727,7 +692,7 @@ shape: (5, 2)
It will also export the DataFrame to `SheetJSPolars.xlsb`. The file can be
inspected with a spreadsheet editor that supports XLSB files.
[^1]: See ["JavaScript Engines"](/docs/demos/engines/) for more examples.
[^1]: See ["Other Languages"](/docs/demos/engines/) for more examples.
[^2]: See [`ctypes`](https://docs.python.org/3/library/ctypes.html) in the Python documentation.
[^3]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^4]: See ["Workbook Object"](/docs/csf/book)

@ -518,7 +518,7 @@ The generated site will be placed in the `dist` folder.
9) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -665,7 +665,7 @@ The generated site will be placed in the `dist` folder.
9) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser

@ -135,7 +135,7 @@ The SheetJS [`read`](/docs/api/parse-options) and [`sheet_to_json`](/docs/api/ut
functions simplify state updates. They are best used in the function bodies of
`useEffect`[^2] and `useCallback`[^3] hooks.
A `useEffect` hook can download and update state when the site is loaded:
A `useEffect` hook can download and update state when a person loads the site:
```mermaid
flowchart LR
@ -150,13 +150,12 @@ flowchart LR
wb --> |wb.Sheets\nselect sheet| ws
ws --> |sheet_to_json\n\n| aoo
aoo --> |setPres\nfrom `setState`| state
linkStyle 1,2,3 color:blue,stroke:blue;
```
<Tabs groupId="lang">
<TabItem name="JS" value="JavaScript">
```js title="In a useEffect hook, update state with data from a remote workbook"
```js
import { useEffect } from 'react';
import { read, utils } from 'xlsx';
@ -183,7 +182,7 @@ useEffect(() => { (async() => {
</TabItem>
<TabItem name="TS" value="TypeScript" default>
```ts title="In a useEffect hook, update state with data from a remote workbook"
```ts
import { useEffect } from 'react';
import { read, utils } from 'xlsx';
@ -253,10 +252,9 @@ flowchart LR
state --> |json_to_sheet\n\n| ws
ws --> |book_new\nbook_append_sheet| wb
wb --> |writeFile\n\n| file
linkStyle 0,1,2 color:blue,stroke:blue;
```
```ts title="Export data from state to a new XLSX workbook"
```ts
import { useCallback } from 'react';
import { utils, writeFile } from 'xlsx';
@ -334,7 +332,7 @@ This demo was tested in the following environments:
| ReactJS | ViteJS | Date |
|:---------|:--------|:-----------|
| `19.1.0` | `6.3.5` | 2025-05-11 |
| `18.3.1` | `6.0.1` | 2024-12-12 |
:::
@ -371,7 +369,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -386,14 +384,23 @@ This demo was tested in the following environments:
| ReactJS | CRA | Date |
|:---------|:--------|:-----------|
| `19.1.0` | `5.1.0` | 2025-05-11 |
| `18.2.0` | `5.0.1` | 2024-12-12 |
:::
:::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
npx -y create-react-app@5.1.0 sheetjs-react
npx -y create-react-app@5.0.1 --scripts-version=5.0.1 sheetjs-react
```
2) Install the SheetJS dependency and start the dev server:
@ -401,7 +408,7 @@ npx -y create-react-app@5.1.0 sheetjs-react
<CodeBlock language="bash">{`\
cd sheetjs-react
npm i
npm i react@19.1.0 react-dom@19.1.0 web-vitals --save --save-exact
npm i 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>
@ -424,7 +431,7 @@ The generated site will be placed in the `build` folder.
6) Start a local web server:
```bash
npx -y http-server build
npx http-server build
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -439,7 +446,7 @@ This demo was tested in the following environments:
| ReactJS | NextJS | Date |
|:---------|:---------|:-----------|
| `19.1.0` | `15.3.2` | 2025-05-11 |
| `19.0.0` | `15.1.0` | 2024-12-13 |
:::
@ -560,7 +567,7 @@ This demo was tested in the following environments:
| Preact | ViteJS | Date |
|:----------|:----------|:-----------|
| `10.26.6` | `5.4.19` | 2025-05-11 |
| `10.22.1` | `5.3.3` | 2024-12-17 |
:::
@ -571,12 +578,7 @@ npm init preact sheetjs-preact
```
This will initiate the project creation process. **Follow the on-screen prompts and
press Enter to accept the default options:**
- `Project language:` JavaScript
- `Use router?` No
- `Prerender app (SSG)?` No
- `Use ESLint?` No
press Enter to accept the default options.**
2) Install the SheetJS dependency and start the dev server:
@ -618,7 +620,7 @@ The generated site will be placed in the `dist` folder.
7) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -639,149 +641,10 @@ The main disadvantage of the Array of Objects approach is the specific nature
of the columns. For more general use, passing around an Array of Arrays works.
However, this does not handle merge cells[^6] well!
HTML Tables support elements with `rowspan` and `colspan` attributes.
#### State
The state will be the serialized HTML string:
<Tabs groupId="lang">
<TabItem name="JS" value="JavaScript">
```ts
import { useState } from 'react';
/* the component state is a string */
const [__html, setHtml] = useState("");
```
</TabItem>
<TabItem name="TS" value="TypeScript" default>
```ts
import { useState } from 'react';
/* the component state is a string */
const [__html, setHtml] = useState<string>("");
```
</TabItem>
</Tabs>
:::info Use of the variable name `__html`
Examples use the name `__html` due to the design of `dangerouslySetInnerHTML`.
`dangerouslySetInnerHTML` expects objects of the form `{ __html: "html code" }`.
For example, the following snippet assumes `html` is the variable name:
```jsx
<div ref={tbl} dangerouslySetInnerHTML={{ __html: html }} />
```
By using the name `__html`, the ES6 shorthand syntax simplifies the code:
```jsx
<div ref={tbl} dangerouslySetInnerHTML={{ __html }} />
```
:::
#### Updating State
The [`sheet_to_html`](/docs/api/utilities/html#html-table-output) function
generates HTML that is aware of merges and other worksheet features.
A `useEffect` hook can download and update state when the site is loaded:
```mermaid
flowchart LR
url[(Remote\nFile)]
ab[(Data\nArrayBuffer)]
wb(SheetJS\nWorkbook)
ws(SheetJS\nWorksheet)
html(HTML\nTABLE)
state((component\nstate))
url --> |fetch\n\n| ab
ab --> |read\n\n| wb
wb --> |wb.Sheets\nselect sheet| ws
ws --> |sheet_to_html\n\n| html
html --> |setHtml\nfrom `setState`| state
linkStyle 1,2,3 color:blue,stroke:blue;
```
```js title="In a useEffect hook, update state with HTML generated from a remote workbook"
import { useEffect } from 'react';
import { read, utils } from 'xlsx';
/* Fetch and update the state once */
useEffect(() => { (async() => {
/* Download from https://docs.sheetjs.com/pres.numbers */
const f = await fetch("https://docs.sheetjs.com/pres.numbers");
const ab = await f.arrayBuffer();
// highlight-start
/* parse */
const wb = read(ab);
/* generate HTML TABLE from first worksheet */
const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
const data = utils.sheet_to_html(ws); // generate objects
/* update state */
setHtml(data); // update state
// highlight-end
})(); }, []);
```
#### Rendering Data
ReactJS `dangerouslySetInnerHTML`[^7] prop allows code to set the `innerHTML`
attribute, effectively inserting the code into the page.
It is strongly recommended to set the `innerHTML` of a parent `DIV` container.
By attaching a `ref`, callbacks will be able to access the live `TABLE` element.
```jsx title="Example JSX for displaying HTML TABLE code"
<div ref={tbl} dangerouslySetInnerHTML={{ __html }} />
```
#### Exporting Data
The [`writeFile`](/docs/api/write-options) and [`table_to_book`](/docs/api/utilities/html#html-table-input)
functions simplify exporting data. They are best used in the function bodies of
`useCallback`[^4] hooks attached to button or other elements.
A callback can generate a local file when a user clicks a button:
```mermaid
flowchart LR
state((component\nstate))
wb(SheetJS\nWorkbook)
file[(XLSX\nexport)]
state --> |table_to_book\n\n| wb
wb --> |writeFile\n\n| file
linkStyle 0,1 color:blue,stroke:blue;
```
```ts title="Export data from HTML TABLE element to a new XLSX workbook"
import { useCallback } from 'react';
import { utils, writeFile } from 'xlsx';
/* get data from live HTML TABLE and export to XLSX */
const exportFile = useCallback(() => {
/* get live reference to HTML TABLE element */
const elt = tbl.current.getElementsByTagName("TABLE")[0];
/* generate workbook from element */
// highlight-next-line
const wb = utils.table_to_book(elt);
/* export to XLSX */
writeFile(wb, "SheetJSReactAoO.xlsx");
}, [pres]);
```
#### Complete Component
generates HTML that is aware of merges and other worksheet features. ReactJS
`dangerouslySetInnerHTML`[^7] prop allows code to set the `innerHTML` attribute,
effectively inserting the code into the page.
In this example, the component attaches a `ref` to the `DIV` container. During
export, the first `TABLE` child element can be parsed with [`table_to_book`](/docs/api/utilities/html#html-table-input) to
@ -838,7 +701,7 @@ This demo was tested in the following environments:
| ReactJS | ViteJS | Date |
|:---------|:--------|:-----------|
| `19.1.0` | `6.3.5` | 2025-05-11 |
| `18.3.1` | `6.0.1` | 2024-12-13 |
:::
@ -875,7 +738,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -890,14 +753,22 @@ This demo was tested in the following environments:
| ReactJS | CRA | Date |
|:---------|:--------|:-----------|
| `19.1.0` | `5.1.0` | 2025-05-11 |
| `18.2.0` | `5.0.1` | 2024-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.
:::
1) Create a new site:
```bash
npx -y create-react-app@5.1.0 sheetjs-react
npx -y create-react-app@5.0.1 --scripts-version=5.0.1 sheetjs-react
```
2) Install the SheetJS dependency and start the dev server:
@ -905,7 +776,7 @@ npx -y create-react-app@5.1.0 sheetjs-react
<CodeBlock language="bash">{`\
cd sheetjs-react
npm i
npm i react@19.1.0 react-dom@19.1.0 web-vitals --save --save-exact
npm i 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>
@ -928,7 +799,7 @@ The generated site will be placed in the `build` folder.
6) Start a local web server:
```bash
npx -y http-server build
npx http-server build
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -941,9 +812,9 @@ and test the page.
This demo was tested in the following environments:
| Preact | ViteJS | Date |
|:----------|:---------|:-----------|
| `10.26.6` | `5.4.19` | 2025-05-11 |
| Preact | ViteJS | Date |
|:----------|:--------|:-----------|
| `10.22.1` | `5.3.3` | 2024-12-17 |
:::
@ -952,15 +823,11 @@ npm init preact sheetjs-preact
```
This will initiate the project creation process. **Follow the on-screen prompts and
press Enter to accept the default options:**
- `Project language:` JavaScript
- `Use router?` No
- `Prerender app (SSG)?` No
- `Use ESLint?` No
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
@ -999,7 +866,7 @@ The generated site will be placed in the `dist` folder.
7) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -1046,8 +913,8 @@ const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({
## Legacy Deployments
[SheetJS Standalone Scripts](/docs/getting-started/installation/standalone) use
simple `SCRIPT` tags and work with legacy deployments that do not use a bundler.
[The Standalone Scripts](/docs/getting-started/installation/standalone) play nice
with legacy deployments that do not use a bundler.
[The legacy demo](pathname:///react/index.html) shows a simple ReactJS component
transpiled in the browser using Babel standalone library.

@ -397,7 +397,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -449,7 +449,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server .output/public/
npx http-server .output/public/
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -558,7 +558,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -610,7 +610,7 @@ The generated site will be placed in the `dist` folder.
6) Start a local web server:
```bash
npx -y http-server .output/public/
npx http-server .output/public/
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser

@ -91,7 +91,7 @@ in the ["API Reference"](/docs/api/) section of the documentation.
<!-- 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"
@ -441,7 +441,7 @@ generated. The `dist` folder in this demo can be deployed on a static host.
9) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser
@ -611,7 +611,7 @@ generated. The `dist` folder in this demo can be deployed on a static host.
9) Start a local web server:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser

@ -173,7 +173,7 @@ npx vite build
7) Verify the new site by running a local web server in the `dist` folder:
```bash
npx -y http-server dist
npx http-server dist
```
8) Access the displayed URL (typically `http://localhost:8080`) in a web browser

@ -145,7 +145,7 @@ npx -y esbuild@0.19.8 esbrowser.js --bundle --outfile=esb.browser.js
5) Start a local HTTP server:
```bash
npx -y http-server .
npx http-server .
```
Access the displayed URL (typically `http://localhost:8080`) with a web browser.

@ -322,7 +322,7 @@ npx webpack --mode=production
6) Start a local HTTP server:
```bash
npx -y http-server .
npx http-server .
```
7) Load the displayed URL (typically `http://localhost:8080/`) in a web browser.

@ -177,7 +177,7 @@ npm install --save browserify@3.46.1
5) Start a local HTTP server:
```bash
npx -y http-server .
npx http-server .
```
6) Load the displayed URL (typically `http://localhost:8080/`) in a web browser.

@ -209,7 +209,7 @@ uses normal functions and traditional Promise chains.
3) Start a local HTTP server:
```bash
npx -y http-server .
npx http-server .
```
4) Load the displayed URL (typically `http://localhost:8080/`) in a web browser.

@ -169,7 +169,7 @@ This step will create `bundle.js`
5) Start a local HTTP server:
```bash
npx -y http-server .
npx http-server .
```
Access the displayed URL (typically `http://localhost:8080/`) in a web browser.

@ -34,8 +34,8 @@ This demo was tested in the following environments:
| Version | Date |
|:---------|:-----------|
| `2.14.4` | 2025-05-07 |
| `1.12.3` | 2025-05-07 |
| `2.13.3` | 2024-12-31 |
| `1.12.3` | 2024-12-31 |
:::
@ -239,7 +239,7 @@ The production site will be stored in the `dist` folder
7) Start a local web server and serve the `dist` folder:
```bash
npx -y http-server dist
npx http-server dist
```
Access the displayed URL (typically `http://localhost:8080/`) in a web browser.

@ -195,7 +195,7 @@ This command will create the script `lib/web.js`
6) Start a local HTTP server, then go to `http://localhost:8080/`
```bash
npx -y http-server .
npx http-server .
```
Click on "Click here to export" to generate a file.

@ -164,7 +164,7 @@ npx snowpack@3.8.8 build
5) Start a local HTTP server:
```bash
npx -y http-server build/
npx http-server build/
```
6) Open a web browser to the displayed URL (typically `http://localhost:8080/`).
@ -290,7 +290,7 @@ npx wmr@3.8.0 build
5) Start a local HTTP server:
```bash
npx -y http-server dist/
npx http-server dist/
```
6) Open a web browser to the displayed URL (typically `http://localhost:8080/`).

@ -36,13 +36,12 @@ the file can be downloaded or previewed in the browser.
This demo was tested in the following deployments:
| Platform | Version | Date |
|:--------------|:---------|:-----------|
| Chromium 136 | `1.9.0` | 2025-05-07 |
| Safari 17.5 | `1.9.0` | 2025-05-07 |
| Konqueror 22 | `1.9.0` | 2025-05-07 |
| NodeJS 24.0.0 | `1.11.0` | 2025-05-07 |
| BunJS 1.2.10 | `1.11.0` | 2025-05-07 |
| 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 |
:::

@ -377,7 +377,7 @@ This demo was tested in the following deployments:
|:-------------|:--------|:-------|:-----------|
| `darwin-x64` | 0.1.48 | 2.2.6 | 2025-03-31 |
| `darwin-arm` | 0.1.48 | 2.2.12 | 2025-04-24 |
| `win11-x64` | 0.1.48 | 2.2.12 | 2025-04-28 |
| `win11-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 |
@ -392,17 +392,17 @@ This demo was tested in the following deployments:
2) Run the script with `--allow-net` and `--allow-write` entitlements:
```bash
deno run --allow-net --allow-write --allow-import SheetJSDenoDOM.ts
deno run --allow-net --allow-write SheetJSDenoDOM.ts
```
The script will create a file `SheetJSDenoDOM.xlsx` that can be opened.
:::note pass
:::caution pass
In older versions of Deno, the `--allow-import` flag must be omitted:
Deno 2 additionally requires the `--allow-import` entitlement:
```bash
deno run --allow-net --allow-write SheetJSDenoDOM.ts
deno run --allow-net --allow-write --allow-import SheetJSDenoDOM.ts
```
:::

@ -231,7 +231,7 @@ This will create a static site in the `_site` folder
8) Test the generated site by starting a web server:
```bash
npx -y http-server _site
npx http-server _site
```
The program will display a URL (typically `http://localhost:8080`). Accessing

@ -294,7 +294,7 @@ The final script will be saved to `out.js`
7) Start a local web server to host the project folder:
```bash
npx -y http-server .
npx http-server .
```
The command will print a list of URLs.

@ -430,7 +430,7 @@ Save and refresh the page. A data table should be displayed
```bash
npm run build
npx -y http-server dist/
npx http-server dist/
```
The terminal will display a URL, typically `http://127.0.0.1:8080` . Access
@ -506,7 +506,7 @@ Save and refresh the page. A data table should be displayed
```bash
npm run build
npx -y http-server dist/
npx http-server dist/
```
The terminal will display a URL, typically `http://127.0.0.1:8080` . Access
@ -594,7 +594,7 @@ const data = utils.sheet_to_json<IPresident>(ws);
```bash
npm run build
npx -y http-server dist/
npx http-server dist/
```
The terminal will display a URL ( `http://127.0.0.1:8080` ). Access that page

@ -334,7 +334,7 @@ The final site will be placed in the `dist` folder.
12) Start a local web server to host the `dist` folder:
```bash
npx -y http-server dist
npx http-server dist
```
The command will print a list of URLs.

@ -32,7 +32,6 @@ flowchart LR
file --> |.eleventy.js\ncustom parser| buffer
buffer --> |.eleventy.js\ncustom parser| aoo
aoo --> |index.njk\ntemplate| html
linkStyle 1 color:blue,stroke:blue;
```
:::tip No Telemetry
@ -115,11 +114,10 @@ accessed using the variable `pres` in a template:
This demo was tested in the following environments:
| Eleventy | Date |
|:---------------|:-----------|
| `2.0.1` | 2025-05-07 |
| `3.0.0` | 2025-05-07 |
| `3.1.0-beta.1` | 2025-05-07 |
| Eleventy | Date |
|:---------|:-----------|
| `2.0.1` | 2024-12-23 |
| `3.0.0` | 2024-12-23 |
:::
@ -214,7 +212,7 @@ Eleventy will place the generated site in the `_site` subfolder.
9) Start a web server to host the static site:
```bash
npx -y http-server _site
npx http-server _site
```
Open a web browser and access the displayed URL ( `http://localhost:8080` ).

@ -49,8 +49,7 @@ This demo was tested in the following environments:
| Nuxt Content | Nuxt | Date |
|:-------------|:-----------|:-----------|
| `1.15.1` | `2.18.1` | 2025-04-23 |
| `2.13.4` | `3.17.2` | 2025-05-12 |
| `3.5.1` | `3.17.3` | 2025-05-18 |
| `2.13.4` | `3.14.159` | 2024-11-14 |
:::
@ -526,8 +525,8 @@ script files. The module script is expected to export a module configured with
- Add the transformer to Nuxt Content in the `content:context` hook
```js title="sheetmodule.ts (Module)"
import { resolve } from 'path';
import { defineNuxtModule } from '@nuxt/kit';
import { resolve } from 'path'
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
/* module setup method */
@ -550,7 +549,7 @@ The module must be loaded in `nuxt.config.ts` and added to the `modules` array:
```ts title="nuxt.config.ts"
// highlight-next-line
import SheetJSModule from './sheetmodule';
import SheetJSModule from './sheetmodule'
export default defineNuxtConfig({
// @ts-ignore
@ -607,7 +606,7 @@ from the script setup will be shaped like the return value from the transformer.
:::caution pass
For some older versions, parts of the Nuxt dependency tree did not support
NodeJS version 20. If the `pnpm install` step fails with a message like
NodeJS version 20. If the `yarn install` step fails with a message like
```
error @nuxt/kit@3.4.1: The engine "node" is incompatible with this module.
@ -620,18 +619,17 @@ The recommended solution is to switch to Node 18.
1) Create a stock app and install dependencies:
```bash
npx -y nuxi init -t content --packageManager pnpm --no-gitInit sheetjs-nc2 -M ,
npx -y nuxi init -t content --packageManager yarn --no-gitInit sheetjs-nc2
cd sheetjs-nc2
npx -y pnpm install
npx -y pnpm install @nuxt/content@2 --save
npx -y pnpm install @types/node @nuxt/kit --save
npx -y yarn install
npx -y yarn add --dev @types/node
```
2) Install the SheetJS library and start the server:
<CodeBlock language="bash">{`\
npx -y pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npx -y pnpm dev`}
npx -y yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npx -y yarn dev`}
</CodeBlock>
@ -641,7 +639,7 @@ When the build finishes, the terminal will display a URL like:
> Local: http://localhost:3000/
```
The server is listening on that URL. Open the link in a web browser.
The server is listening on that URL. Open the link in a web browser.
3) Download https://docs.sheetjs.com/pres.xlsx and move to the `content` folder.
@ -651,12 +649,12 @@ curl -L -o content/pres.xlsx https://docs.sheetjs.com/pres.xlsx
4) Create the transformer. Two files must be saved at the root of the project:
- [`sheetformer.ts`](https://docs.sheetjs.com/nuxt/2/sheetformer.ts) (raw transformer module)
- [`sheetformer.ts`](https://docs.sheetjs.com/nuxt/3/sheetformer.ts) (raw transformer module)
- [`sheetmodule.ts`](https://docs.sheetjs.com/nuxt/3/sheetmodule.ts) (Nuxt configuration module)
```bash
curl -O https://docs.sheetjs.com/nuxt/2/sheetformer.ts
curl -O https://docs.sheetjs.com/nuxt/2/sheetmodule.ts
curl -O https://docs.sheetjs.com/nuxt/3/sheetformer.ts
curl -O https://docs.sheetjs.com/nuxt/3/sheetmodule.ts
```
After creating the source files, the module must be added to `nuxt.config.ts`:
@ -670,13 +668,14 @@ export default defineNuxtConfig({
// @ts-ignore
telemetry: false,
// highlight-end
devtools: { enabled: true },
// highlight-start
modules: [
// highlight-next-line
SheetJSModule,
'@nuxt/content'
],
devtools: { enabled: true },
// ...
content: {}
// highlight-end
});
```
@ -685,13 +684,33 @@ Stop the dev server (<kbd>CTRL</kbd>+<kbd>C</kbd>) and run the following:
```bash
npx -y nuxi clean
npx -y nuxi cleanup
npx -y pnpm run dev
npx -y nuxi typecheck
npx -y yarn run dev
```
5) Download [`pres.vue`](pathname:///nuxt/2/pres.vue) and save to `app/pages`:
Loading `http://localhost:3000/pres` should show some JSON data:
```json
{
// ...
"data": {
"_path": "/pres",
// ...
"_id": "content:pres.xlsx",
"body": [
{
"name": "Sheet1", // <-- sheet name
"data": [ // <-- array of data objects
{
"Name": "Bill Clinton",
"Index": 42
},
```
5) Download [`pres.vue`](pathname:///nuxt/3/pres.vue) and save to `pages`:
```bash
curl -o app/pages/pres.vue https://docs.sheetjs.com/nuxt/2/pres.vue
curl -o pages/pres.vue https://docs.sheetjs.com/nuxt/3/pres.vue
```
Stop the dev server (<kbd>CTRL</kbd>+<kbd>C</kbd>) and run the following:
@ -699,247 +718,24 @@ Stop the dev server (<kbd>CTRL</kbd>+<kbd>C</kbd>) and run the following:
```bash
npx -y nuxi clean
npx -y nuxi cleanup
npx -y pnpm run dev
npx -y yarn run dev
```
6) From the browser window in step 2, access `/pres` from the site. For example,
if the URL in step 2 was `http://localhost:3000/`, the new page should be
`http://localhost:3000/pres`.
The browser should now display an HTML table.
This page should now display an HTML table.
7) To verify that hot loading works, open `pres.xlsx` from the `content` folder
6) To verify that hot loading works, open `pres.xlsx` from the `content` folder
with a spreadsheet editor.
Set cell `A7` to "SheetJS Dev" and set `B7` to `47`. Save the spreadsheet.
The page should automatically refresh with the new content.
8) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
9) Copy `app/pages/pres.vue` to `app/pages/index.vue`:
```bash
cp app/pages/pres.vue app/pages/index.vue
```
:::info pass
In older test runs, the Nuxt starter created a default `/` page. The most recent
test did not create the index page, resulting in build errors. This step ensures
some sort of index page exists.
:::
10) Build the static site:
```bash
npx -y pnpm run generate
```
This will create a static site in `.output/public`, which can be served with:
```bash
npx -y http-server .output/public
```
Access the displayed URL (typically `http://localhost:8080`) in a web browser.
To confirm that the spreadsheet data is added to the site, view the page source.
Searching for `Bill Clinton` reveals the following encoded HTML row:
```html
<tr><td>Bill Clinton</td><td>42</td></tr>
```
## Nuxt Content v3
:::danger pass
When this demo was last tested, the official Nuxt Content v3 custom transformers
and custom collections examples did not work.
:::
[ViteJS modules](/docs/demos/static/vitejs) can be used in Nuxt v3.
### Installation
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
safely imported from `nuxt.config.ts` or transformer or module scripts. As long
as the SheetJS modules are not imported in the various `.vue` pages, the library
will not be added to the final page bundle!
### Configuration
The `vite` property in the NuxtJS config is passed to ViteJS. Plugins and other
configuration options can be copied to the object. `vite.config.js` for the
[Pure Data Plugin](/docs/demos/static/vitejs#pure-data-plugin) is shown below:
```js title="vite.config.js (raw ViteJS)"
import { readFileSync } from 'fs';
import { read, utils } from 'xlsx';
import { defineConfig } from 'vite';
export default defineConfig({
assetsInclude: ['**/*.xlsx'], // xlsx file should be treated as assets
plugins: [
{ // this plugin handles ?sheetjs tags
name: "vite-sheet",
transform(_code, id) {
if(!id.match(/\?sheetjs$/)) return;
var wb = read(readFileSync(id.replace(/\?sheetjs$/, "")));
var data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
return `export default JSON.parse('${JSON.stringify(data).replace(/\\/g, "\\\\")}')`;
}
}
]
});
```
The `assetsInclude` and `plugins` properties should be added within the `vite`
property in the object passed to `defineNuxtConfig`.
:::danger pass
NuxtJS does not properly honor the `?sheetjs` tag! As a result, the transform
explicitly tests for the `.xlsx` extension.
:::
```ts title="nuxt.config.ts"
import { readFileSync } from 'fs';
import { read, utils } from 'xlsx';
export default defineNuxtConfig({
// highlight-next-line
vite: { // these options are passed to ViteJS
assetsInclude: ['**/*.xlsx'], // xlsx file should be treated as assets
plugins: [
{ // this plugin handles .xlsx
name: "vite-sheet",
transform(_code, id) {
if(!id.match(/\.xlsx$/)) return;
var wb = read(readFileSync(id.replace(/\?sheetjs$/, "")));
var data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
return `export default JSON.parse('${JSON.stringify(data).replace(/\\/g, "\\\\")}')`;
}
},
],
},
// ...
```
### Template Use
Pages can reference spreadsheets using a relative file reference. The ViteJS
plugin will transform files with the `.xlsx` extension.
```js title="Script section of .vue VueJS page"
import data from '../../pres.xlsx'; // data is an array of objects
```
In the template, `data` is an array of objects that works with `v-for`[^4]:
```xml title="Template section of .vue VueJS page"
<table>
<thead><tr><th>Name</th><th>Index</th></tr></thead><tbody>
<!-- loop over the rows of each worksheet -->
<tr v-for="row in data" v-bind:key="row.Index">
<!-- here `row` is a row object generated from sheet_to_json -->
<td>{{ row.Name }}</td>
<td>{{ row.Index }}</td>
</tr>
</table>
```
### Nuxt Content 3 Demo
1) Create a stock app and install dependencies:
```bash
npx -y nuxi init -t content --packageManager pnpm --no-gitInit sheetjs-nc3 -M ,
cd sheetjs-nc3
npx -y pnpm install
```
2) Install the SheetJS library and start the server:
<CodeBlock language="bash">{`\
npx -y pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npx -y pnpm dev`}
</CodeBlock>
When the build finishes, the terminal will display a URL like:
```
> Local: http://localhost:3000/
```
The server is listening on that URL. Open the link in a web browser.
3) Download https://docs.sheetjs.com/pres.xlsx and move to the root folder:
```bash
curl -o pres.xlsx https://docs.sheetjs.com/pres.xlsx
```
4) Replace `nuxt.config.ts` with the following codeblock:
```ts title="nuxt.config.ts"
import { readFileSync } from 'fs';
import { read, utils } from 'xlsx';
export default defineNuxtConfig({
vite: { // these options are passed to ViteJS
assetsInclude: ['**/*.xlsx'], // xlsx file should be treated as assets
plugins: [
{ // this plugin handles .xlsx
name: "vite-sheet",
transform(_code, id) {
if(!id.match(/\.xlsx$/)) return;
var wb = read(readFileSync(id.replace(/\?sheetjs$/, "")));
var data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
return `export default JSON.parse('${JSON.stringify(data).replace(/\\/g, "\\\\")}')`;
}
},
],
},
modules: [
'@nuxt/content',
],
devtools: { enabled: true },
});
```
5) Create a new file `app.vue` with the following contents:
```jsx title="app.vue (create new file)"
<script setup>
import data from '../../pres.xlsx'
</script>
<template>
<table><thead><tr><th>Name</th><th>Index</th></tr></thead><tbody>
<tr v-for="row in data" v-bind:key="row.Index">
<td>{{ row.Name }}</td>
<td>{{ row.Index }}</td>
</tr>
</tbody></table>
</template>
```
6) Refresh the browser window. This page should now display an HTML table.
7) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
8) Build the static site:
```bash
npx -y pnpm run generate
npx -y yarn run generate
```
This will create a static site in `.output/public`, which can be served with:

@ -345,7 +345,7 @@ AstroJS will place the generated site in the `dist` subfolder.
9) Start a web server to host the static site:
```bash
npx -y http-server dist
npx http-server dist
```
Open a web browser and access the displayed URL ( `http://localhost:8080` ).

@ -56,15 +56,15 @@ This demo was tested in the following environments:
| OS | Device | NS | Date |
|:-----------|:--------------------|:---------|:-----------|
| Android 30 | NVIDIA Shield | `8.9.2` | 2025-05-06 |
| iOS 15.1 | iPad Pro | `8.9.2` | 2025-05-06 |
| Android 30 | NVIDIA Shield | `8.7.2` | 2024-06-09 |
| iOS 15.1 | iPad Pro | `8.7.2` | 2024-06-09 |
**Simulators**
| OS | Device | NS | Dev Platform | Date |
|:-----------|:--------------------|:---------|:-------------|:-----------|
| Android 35 | Pixel 9 Pro XL | `8.9.2` | `darwin-x64` | 2025-05-06 |
| iOS 18.4 | iPhone 16 Pro Max | `8.9.2` | `darwin-x64` | 2025-05-06 |
| Android 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 |
@ -78,15 +78,15 @@ NativeScript 8.6.1 split the telemetry into two parts: "usage" and "error". Both
must be disabled separately:
```bash
npx -y -p nativescript ns usage-reporting disable
npx -y -p nativescript ns error-reporting disable
npx -p nativescript ns usage-reporting disable
npx -p nativescript ns error-reporting disable
```
To verify telemetry was disabled:
```bash
npx -y -p nativescript ns usage-reporting status
npx -y -p nativescript ns error-reporting status
npx -p nativescript ns usage-reporting status
npx -p nativescript ns error-reporting status
```
:::
@ -120,22 +120,6 @@ for accessing data and are subject to change in future platform versions.
<details>
<summary><b>Technical Details</b> (click to show)</summary>
**iOS**
The following key/value pairs must be added to `Info.plist`:
```xml title="App_Resources/iOS/Info.plist (add highlighted lines)"
<dict>
<!-- highlight-start -->
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<!-- highlight-end -->
```
---
**Android**
Android security has evolved over the years. In newer Android versions, the
@ -281,8 +265,8 @@ const wb = read(ab);
0) Disable telemetry:
```bash
npx -y -p nativescript ns usage-reporting disable
npx -y -p nativescript ns error-reporting disable
npx -p nativescript ns usage-reporting disable
npx -p nativescript ns error-reporting disable
```
1) Follow the official Environment Setup instructions[^8].
@ -290,14 +274,14 @@ npx -y -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 -y -p nativescript ns doctor android` clearly stated
The error message from `npx -p nativescript ns doctor android` 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 support the latest API level, an older API
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:
@ -306,14 +290,14 @@ In a previous test run, the following packages were required:
- `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.
Build-Tools for the latest supported API level.ß
:::
2) Test the local system configuration for Android development:
```bash
npx -y -p nativescript ns doctor android
npx -p nativescript ns doctor android
```
In the last macOS test, the following output was displayed:
@ -332,7 +316,7 @@ In the last macOS test, the following output was displayed:
<span {...g}></span> Javac is installed and is configured properly.
<span {...g}></span> The Java Development Kit (JDK) is installed and is configured properly.
<span {...g}></span> Getting NativeScript components versions information...
<span {...g}></span> Component nativescript has 8.9.2 version and is up to date.
<span {...g}></span> Component nativescript has 8.7.2 version and is up to date.
</pre>
</details>
@ -340,7 +324,7 @@ In the last macOS test, the following output was displayed:
3) Test the local system configuration for iOS development (macOS only):
```bash
npx -y -p nativescript ns doctor ios
npx -p nativescript ns doctor ios
```
In the last macOS test, the following output was displayed:
@ -359,9 +343,9 @@ In the last macOS test, the following output was displayed:
<span {...g}></span> CocoaPods are configured properly.
<span {...g}></span> Your current CocoaPods version is newer than 1.0.0.
<span {...g}></span> Python installed and configured correctly.
<span {...g}></span> Xcode version 16.3.0 satisfies minimum required version 10.
<span {...g}></span> Xcode version 15.4.0 satisfies minimum required version 10.
<span {...g}></span> Getting NativeScript components versions information...
<span {...g}></span> Component nativescript has 8.9.2 version and is up to date.
<span {...g}></span> Component nativescript has 8.7.2 version and is up to date.
</pre>
</details>
@ -371,21 +355,21 @@ In the last macOS test, the following output was displayed:
4) Create a skeleton NativeScript + Angular app:
```bash
npx -y -p nativescript ns create SheetJSNS --ng
npx -p nativescript ns create SheetJSNS --ng
```
5) Launch the app in the Android simulator to verify the app:
5) Launch the app in the android simulator to verify the app:
```bash
cd SheetJSNS
npx -y -p nativescript ns run android
npx -p nativescript ns run android
```
(this may take a while)
Once the simulator launches and the test app is displayed, end the script by
selecting the terminal and pressing <kbd>CTRL</kbd>+<kbd>C</kbd>. On Windows, if
prompted to `Terminate batch job`, type <kbd>Y</kbd> and press Enter.
prompted to `Terminate batch job`, type `y` and press Enter.
:::note pass
@ -397,23 +381,6 @@ Emulator start failed with: No emulator image available for device identifier 'u
:::
:::caution pass
In the most recent test, the build failed with an exception:
```
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/sheetjs/.gradle/wrapper/dists/gradle-8.7-bin/bhs2wmbdwecv87pi65oeuq5iu/gradle-8.7/lib/native-platform-0.22-milestone-25.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
```
**The NativeScript Gradle version is incompatible with Java 24!**
It is strongly recommended to roll back to Java 21.
:::
### Add SheetJS
:::note pass
@ -439,10 +406,11 @@ import { Component, OnInit } from '@angular/core'
// ...
export class ItemsComponent implements OnInit {
items: Array<Item>
// highlight-next-line
version = `SheetJS - ${version}`;
itemService = inject(ItemService)
page = inject(Page)
constructor(private itemService: ItemService) {}
// ...
```
@ -460,7 +428,7 @@ in the title of the action bar:
9) End the script and relaunch the app in the Android simulator:
```bash
npx -y -p nativescript ns run android
npx -p nativescript ns run android
```
The title bar should show the version.
@ -481,7 +449,7 @@ The title bar should show the version.
<Button text="Export File" (tap)="export()" style="padding: 10px"></Button>
</StackLayout>
<!-- highlight-end -->
<ListView [items]="itemService.items()">
<ListView [items]="items">
<!-- ... -->
</ListView>
<!-- highlight-next-line -->
@ -490,15 +458,15 @@ The title bar should show the version.
11) Add the `import` and `export` methods in the component script:
```ts title="src/app/item/items.component.ts (add highlighted lines)"
```ts title="src/app/item/items.component.ts"
// highlight-start
import { version, utils, read, write } from 'xlsx';
import { Dialogs, getFileAccess } from '@nativescript/core';
import { Folder, knownFolders, path } from '@nativescript/core/file-system';
// highlight-end
import { Component, NO_ERRORS_SCHEMA, inject } from '@angular/core'
import { NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular'
import { Page } from '@nativescript/core'
import { Component, OnInit } from '@angular/core'
import { Item } from './item'
import { ItemService } from './item.service'
// highlight-start
@ -508,12 +476,19 @@ function get_url_for_filename(filename: string): string {
}
// highlight-end
// ...
@Component({
selector: 'ns-items',
templateUrl: './items.component.html',
})
export class ItemsComponent implements OnInit {
items: Array<Item>
version: string = `SheetJS - ${version}`;
export class ItemsComponent {
version = `SheetJS - ${version}`;
itemService = inject(ItemService)
page = inject(Page)
constructor(private itemService: ItemService) {}
ngOnInit(): void {
this.items = this.itemService.getItems()
}
// highlight-start
/* Import button */
@ -524,14 +499,13 @@ export class ItemsComponent {
async export() {
}
// highlight-end
// ...
}
```
12) End the script and relaunch the app in the Android simulator:
```bash
npx -y -p nativescript ns run android
npx -p nativescript ns run android
```
Two buttons should appear just below the header:
@ -560,7 +534,7 @@ Two buttons should appear just below the header:
const ws = wb.Sheets[wsname];
/* update table */
this.itemService.items.set(utils.sheet_to_json(ws));
this.items = utils.sheet_to_json<Item>(ws);
} catch(e) { await Dialogs.alert(e.message); }
// highlight-end
}
@ -573,7 +547,7 @@ Two buttons should appear just below the header:
try {
/* create worksheet from data */
const ws = utils.json_to_sheet(this.itemService.items());
const ws = utils.json_to_sheet(this.items);
/* create workbook from worksheet */
const wb = utils.book_new();
@ -595,7 +569,7 @@ Two buttons should appear just below the header:
14) Launch the app in the Android Simulator:
```bash
npx -y -p nativescript ns run android
npx -p nativescript ns run android
```
If the app does not automatically launch, manually open the `SheetJSNS` app.
@ -637,8 +611,8 @@ $bytes = [Convert]::FromBase64String($b64)
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)
- 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:
@ -646,6 +620,8 @@ After making the changes, the worksheet should look like the following:
id | name | role
# highlight-next-line
0 | SheetJS | Library
1 | Ter Stegen | Goalkeeper
3 | Piqué | Defender
...
```
@ -695,7 +671,7 @@ Scroll down to ["Fetching Files"](#android-device) for Android device testing.
20) Launch the app in the iOS Simulator:
```bash
npx -y -p nativescript ns run ios
npx -p nativescript ns run ios
```
21) Tap "Export File". A dialog will print where the file was written.
@ -705,8 +681,8 @@ npx -y -p nativescript ns run ios
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)
- 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:
@ -714,6 +690,8 @@ After making the changes, the worksheet should look like the following:
id | name | role
# highlight-next-line
0 | SheetJS | Library
1 | Ter Stegen | Goalkeeper
3 | Piqué | Defender
...
```
@ -726,21 +704,31 @@ The first item in the list will change:
### Fetching Files
25) Replace `item.service.ts` with the following:
25) In `src/app/item/items.component.ts`, make `ngOnInit` asynchronous:
```ts title="src/app/item/items.component.ts (replace existing function)"
async ngOnInit(): Promise<void> {
this.items = await this.itemService.getItems()
}
```
26) Replace `item.service.ts` with the following:
```ts title="src/app/item/item.service.ts"
import { read, utils } from 'xlsx';
import { Injectable, signal, effect } from '@angular/core'
import { knownFolders, path, getFileAccess } from '@nativescript/core';
import { getFile } from '@nativescript/core/http';
import { Item } from './item'
import { Injectable } from '@angular/core'
import { knownFolders, path, getFileAccess } from '@nativescript/core'
import { getFile } from '@nativescript/core/http';
import { read, utils } from 'xlsx';
import { Item } from './item'
interface IPresident { Name: string; Index: number };
@Injectable({ providedIn: 'root' })
export class ItemService {
items = signal<Item[]>([]);
constructor() { effect(() => { (async() => {
private items: Array<Item>;
async getItems(): Promise<Array<Item>> {
/* fetch https://docs.sheetjs.com/pres.xlsx */
const temp: string = path.join(knownFolders.temp().path, "pres.xlsx");
const ab = await getFile("https://docs.sheetjs.com/pres.xlsx", temp)
@ -748,33 +736,32 @@ export class ItemService {
const wb = read(await getFileAccess().readBufferAsync(ab.path));
/* translate the first worksheet to the required Item type */
const data = utils.sheet_to_json<IPresident>(wb.Sheets[wb.SheetNames[0]]);
/* update state */
this.items.set(data.map((pres, id) => ({id, name: pres.Name, role: ""+pres.Index} as Item)));
})(); }); }
return this.items = data.map((pres, id) => ({id, name: pres.Name, role: ""+pres.Index} as Item));
}
getItem(id: number): Item {
return this.items().find((item) => item.id === id)
return this.items.filter((item) => item.id === id)[0]
}
}
```
26) End the script and relaunch the app in the Android simulator:
27) End the script and relaunch the app in the Android simulator:
```bash
npx -y -p nativescript ns run android
npx -p nativescript ns run android
```
The app should show Presidential data.
### Android Device
27) Connect an Android device using a USB cable.
28) Connect an Android device using a USB cable.
If the device asks to allow USB debugging, tap "Allow".
28) Close any Android / iOS emulators.
29) Close any Android / iOS emulators.
29) Enable "Legacy External Storage" in the Android app. The manifest is stored
30) Enable "Legacy External Storage" in the Android app. The manifest is stored
at `App_Resources/Android/src/main/AndroidManifest.xml`:
```xml title="App_Resources/Android/src/main/AndroidManifest.xml (add highlighted line)"
@ -789,13 +776,13 @@ at `App_Resources/Android/src/main/AndroidManifest.xml`:
android:hardwareAccelerated="true">
```
30) Install the `@nativescript-community/perms` dependency:
31) Install the `@nativescript-community/perms` dependency:
```bash
npm i --save @nativescript-community/perms
```
31) Add the highlighted lines to `items.component.ts`:
32) Add the highlighted lines to `items.component.ts`:
- Import `File` from NativeScript core and `request` from the new dependency:
@ -828,10 +815,10 @@ import { Component, OnInit } from '@angular/core'
} catch(e) { await Dialogs.alert(e.message); }
```
32) Build APK and run on device:
33) Build APK and run on device:
```bash
npx -y -p nativescript ns run android
npx -p nativescript ns run android
```
If the Android emulators are closed and an Android device is connected, the last
@ -852,59 +839,22 @@ file named `SheetJSNS.xls`.
### iOS Device
33) Connect an iOS device using a USB cable
34) Connect an iOS device using a USB cable
34) Close any Android / iOS emulators.
35) Close any Android / iOS emulators.
35) Enable developer code signing certificates:
36) Enable developer code signing certificates:
Open `platforms/ios/SheetJSNS.xcodeproj/project.xcworkspace` in Xcode. Select
the "Project Navigator" and select `SheetJSNS`. In the main view, select the
`SheetJSNS` target. Click "Signing & Capabilities". Under "Signing", select a
team in the dropdown menu.
:::caution pass
When this demo was last tested, Xcode repeatedly crashed.
The issue was resolved by cleaning the project:
```bash
npx -y -p nativescript ns platform clean ios
```
:::
36) Add the following key/value pairs to `Info.plist`:
```xml title="App_Resources/iOS/Info.plist (add highlighted lines)"
<dict>
<!-- highlight-start -->
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<!-- highlight-end -->
```
the "Project Navigator" and select the "App" project. In the main view, select
"Signing & Capabilities". Under "Signing", select a team in the dropdown menu.
37) Run on device:
```bash
npx -y -p nativescript ns run ios
npx -p nativescript ns run ios
```
:::info pass
If this is the first time testing an app on a device, the certificate must be
trusted on the device:
Under "Settings" > "General" > "VPN & Device Management", there should be a
"Apple Development" certificate in the "DEVELOPER APP" section. Select the
certificate and confirm that "SheetJSNS" is listed under "APPS". Tap "Trust ..."
and tap "Trust" in the popup.
:::
<details open>
<summary><b>iOS Device Testing</b> (click to hide)</summary>
@ -913,9 +863,8 @@ connected to the Internet, a list of Presidents should be displayed.
Tap "Export File". The app will show an alert. Tap "OK".
Switch to the "Files" app and repeatedly tap "&lt;". In the "Browse" window, tap
"On My iPhone". There should be a new folder named "SheetJSNS". Tap the folder
and look for the file named `SheetJSNS.xls`.
Switch to the "Files" app and open the "Downloads" folder. There should be a new
file named `SheetJSNS.xls`.
</details>

@ -59,7 +59,7 @@ This demo was tested in the following environments:
|:-----------|:------------------|:--------|:---------|:-------------|:-----------|
| Android 35 | Pixel 9 Pro XL | `3.7.2` | `3.29.2` | `darwin-x64` | 2025-03-31 |
| iOS 18.3 | iPhone 16 Pro Max | `3.7.2` | `3.29.2` | `darwin-x64` | 2025-03-31 |
| Android 36 | Pixel 9 Pro XL | `3.7.2` | `3.29.3` | `win11-x64` | 2054-04-28 |
| Android 35 | Pixel 3a | `3.5.0` | `3.24.0` | `win11-x64` | 2024-08-10 |
:::
@ -244,7 +244,7 @@ Run `flutter doctor` and confirm the following items are checked:
<TabItem value="win" label="Windows">
<pre>
<span {...g}>[✓]</span> Android toolchain - develop for Android devices (Android SDK version 35.0.1)
<span {...g}>[✓]</span> Android toolchain - develop for Android devices (Android SDK version 35.0.0)
</pre>
</TabItem>
@ -260,7 +260,7 @@ Run `flutter doctor` and confirm the following items are checked:
On first run, there may be a warning with "Android toolchain":
```
[!] Android toolchain - develop for Android devices (Android SDK version 35.0.1)
[!] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
! Some Android licenses not accepted. To resolve this, run: flutter doctor
--android-licenses
```
@ -386,9 +386,9 @@ Pixel_9_Pro_XL_API_35 • Pixel 9 Pro XL API 35 • Google • android
There should be at least one `android` emulator:
```
Id • Name • Manufacturer • Platform
Id • Name • Manufacturer • Platform
Pixel_9_Pro_XL • Pixel 9 Pro XL • Google • android
Pixel_3a_API_35 • Pixel 3a API 35 • Google • android
```
</TabItem>
@ -445,43 +445,14 @@ emulator -avd Pixel_9_Pro_XL_API_35
:::note pass
If `emulator` cannot be found, the folder must be added to the system path.
<Tabs groupId="os">
<TabItem value="macos" label="macOS">
On macOS, `~/Library/Android/sdk/emulator/` is the typical location for the
`emulator` binary:
`emulator` binary. If it cannot be found, add the folder to `PATH`:
```bash
export PATH="$PATH":~/Library/Android/sdk/emulator
emulator -avd Pixel_9_Pro_XL_API_35
```
</TabItem>
<TabItem value="win" label="Windows">
The Android SDK folder can be found in the SDK manager in Android Studio. It is
typically `%LOCALAPPDATA%\Android\Sdk`.
If it is not assigned, create a User environment variable named `ANDROID_HOME`
with the value set to the Android SDK folder.
---
There are three folders within the Android SDK folder that should be added to
the User `PATH` environment variable. Each folder holds a different tool:
| Folder | Command-line Tool |
|:------------------------------------------|:------------------|
| `%ANDROID_HOME%\emulator` | `emulator` |
| `%ANDROID_HOME%\cmdline-tools\latest\bin` | `avdmanager` |
| `%ANDROID_HOME%\platform-tools` | `adb` |
</TabItem>
</Tabs>
:::
</details>
@ -674,13 +645,6 @@ flutter -v -d emulator-5554 run
</details>
:::caution pass
In some test runs on low-power devices, it took 20 seconds for the app to fetch
and display data!
:::
:::info Troubleshooting
In some demo runs, the build failed with an Android SDK error:

@ -40,22 +40,47 @@ 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.
Electron uses a multi-process architecture, with the main process handling system level operations and I/O, and the renderer process handling UI and web content.
The SheetJS `readFile` and `writeFile` methods will use the Electron `fs` module
where available.
**Renderer Process Limitations**
<details>
<summary><b>Renderer Configuration</b> (click to show)</summary>
The renderer process is sandboxed and cannot run any non-browser code.
Electron 9 and later require the preference `nodeIntegration: true` in order to
`require('xlsx')` in the renderer process.
**Main Process Limitations**
Electron 12 and later also require `worldSafeExecuteJavascript: true` and
`contextIsolation: true`.
The main process can run any NodeJS code, but it cannot access the DOM or any browser APIs.
</details>
To allow communication between the main and renderer processes, Electron recommends building a [context bridge](https://www.electronjs.org/docs/latest/api/context-bridge) to expose low-level system calls and NodeJS APIs to the renderer process. Such as the [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) which we will be using here.
Exposed APIs are available as `SheetJSDemoAPI` on the window object and proxied from the main process.
```js title="preload.js -- contextBridge API"
const { contextBridge, ipcRenderer, shell } = require('electron');
// import nodejs modules we wish to expose APIs from.
const path = require('path');
const XLSX = require('xlsx');
// The contextBridge API allows us to expose APIs to the renderer process.
// highlight-next-line
contextBridge.exposeInMainWorld('SheetJSDemoAPI', {
// request OS file dialogs from the main process
openFile: (filters) => ipcRenderer.invoke('dialog:openFile', filters),
saveFile: (filters) => ipcRenderer.invoke('dialog:saveFile', filters),
message: (msg) => ipcRenderer.invoke('dialog:message', msg),
// open external links in the default browser
openExternal: (url) => shell.openExternal(url),
// listen for file open events from the main process
onFileOpened: (cb) => ipcRenderer.on('file-opened', (_e, fp) => cb(fp)),
// You can use this to expose nodejs APIs to the renderer process.
basename: (p) => path.basename(p),
extname: (p) => path.extname(p),
// Here for example we are exposing the sheetjs package to the renderer process.
// highlight-next-line
xlsx: XLSX,
});
```
### Reading Files
@ -73,7 +98,7 @@ For example, assuming a file input element on the page:
The event handler would process the event as if it were a web event:
```js
```js title="index.js -- renderer process"
async function handleFile(e) {
const file = e.target.files[0];
const data = await file.arrayBuffer();
@ -87,8 +112,9 @@ document.getElementById("xlf").addEventListener("change", handleFile, false);
**Drag and Drop**
The [drag and drop snippet](/docs/solutions/input#example-user-submissions)
applies to DIV elements on the page.
In the demo the [drag and drop snippet](/docs/solutions/input#example-user-submissions)
applies to the entire window via the `document.body` element. However it can easily be
applied to any element on the page.
For example, assuming a DIV on the page:
@ -98,7 +124,9 @@ For example, assuming a DIV on the page:
The event handler would process the event as if it were a web event:
```js
```js title="index.js -- renderer process"
const XLSX = window.SheetJSDemoAPI.xlsx; // use xlsx package from bridge process
async function handleDrop(e) {
e.stopPropagation();
e.preventDefault();
@ -117,88 +145,105 @@ document.getElementById("drop").addEventListener("drop", handleDrop, false);
[`XLSX.readFile`](/docs/api/parse-options) reads workbooks from the file system.
`showOpenDialog` shows a Save As dialog and returns the selected file name.
Unlike the Web APIs, the `showOpenDialog` flow can be initiated by app code:
```js
/* from the renderer thread */
const electron = require('@electron/remote');
We can now use the exposed APIs from our preload script above to show the open dialog and try to parse the workbook from within the renderer process.
```js title="index.js -- renderer process"
// our exposed bridge APIs are available as SheetJSDemoAPI on the window object
const openFile = window.SheetJSDemoAPI.openFile; // request the open file dialog from the main process
// We can also access the SheetJS package from the exposed bridge APIs
// highlight-next-line
const XLSX = window.SheetJSDemoAPI.xlsx;
/* this function will show the open dialog and try to parse the workbook */
async function importFile() {
/* show Save As dialog */
const result = await electron.dialog.showOpenDialog({
title: 'Select a file',
filters: [{
/* show open file dialog */
const result = await openFile([{
name: "Spreadsheets",
extensions: ["xlsx", "xls", "xlsb", /* ... other formats ... */]
}]
});
}]);
/* result.filePaths is an array of selected files */
if(result.filePaths.length == 0) throw new Error("No file was selected!");
// highlight-next-line
return XLSX.readFile(result.filePaths[0]);
}
```
In order to interact with the file system, the `xlsx` package here depends on the Node.js. Which means we need to utilize the Bridge here and make it possible to call these methods from the renderer process. The appropriate IPC event can be found below.
:::note pass
```js title="main.js -- main process"
const { ipcMain, dialog } = require('electron');
`showOpenDialog` originally returned an array of paths:
```js
var dialog = require('electron').remote.dialog;
function importFile(workbook) {
var result = dialog.showOpenDialog({ properties: ['openFile'] });
return XLSX.readFile(result[0]);
}
ipcMain.handle('dialog:openFile', (_e, filters) =>
dialog.showOpenDialog({ title: 'Select a file', filters, properties: ['openFile'] })
);
```
This method was renamed to `showOpenDialogSync` in Electron 6.
:::
### Writing Files
[`XLSX.writeFile`](/docs/api/write-options) writes workbooks to the file system.
`showSaveDialog` shows a Save As dialog and returns the selected file name:
```js
/* from the renderer thread */
const electron = require('@electron/remote');
The implementation for saving files looks very similar to the one above thanks to our bridge API.
```js title="index.js -- renderer process"
// our exposed bridge APIs are available as SheetJSDemoAPI on the window object
const saveFile = window.SheetJSDemoAPI.saveFile; // request the save file dialog from the main process
const XLSX = window.SheetJSDemoAPI.xlsx;
/* this function will show the save dialog and try to write the workbook */
async function exportFile(workbook) {
/* show Save As dialog */
const result = await electron.dialog.showSaveDialog({
title: 'Save file as',
filters: [{
const result = await saveFile([{
name: "Spreadsheets",
extensions: ["xlsx", "xls", "xlsb", /* ... other formats ... */]
}]
});
/* write file */
// highlight-next-line
XLSX.writeFile(workbook, result.filePath);
}]);
if(result.filePaths.length == 0) throw new Error("No file was selected!");
XLSX.writeFile(workbook, result.filePaths[0]);
}
```
And here is the implementation of the `saveFile` event listener in `main.js`:
```js title="main.js -- main process"
const { ipcMain, dialog } = require('electron');
:::note pass
`showSaveDialog` originally returned the selected path:
```js
var dialog = require('electron').remote.dialog;
function exportFile(workbook) {
var result = dialog.showSaveDialog();
XLSX.writeFile(workbook, result);
}
ipcMain.handle('dialog:saveFile', (_e, filters) =>
dialog.showSaveDialog({ title: 'Save file as', filters })
);
```
This method was renamed to `showSaveDialogSync` in Electron 6.
### Working with OS level file open events.
Electron makes it possible to handle OS level file open events, such as the "open with" context menu or `open` CLI command.
The example below shows the configuration required to register your application as a handler supporting such events for all file extensions SheetJS supports.
:::caution
It is also possible to open files using the "open with" context menu without registering the application as a handler for the specified file types. This however, requires manually selecting the application binary as a target to open the file with.
**This action might not be supported by some file managers on Linux based systems.**
:::
```json title="package.json"
{
// ...existing content
"build": {
"appId": "com.sheetjs.electron",
"fileAssociations": [
{
"ext": [ // supported extensions to register with the OS.
"xls","xlsx","xlsm","xlsb","xml","csv","txt","dif",
"sylk","slk","prn","ods","fods","htm","html","numbers"
],
"name": "Spreadsheet / Delimited File",
"description": "Spreadsheets and delimited text files opened by SheetJS-Electron",
"role": "Editor"
}
],
"mac": { "target": "dmg" },
"win": { "target": "nsis" },
"linux": { "target": "deb" }
},
}
```
This makes it possible to generate installers for MacOS, Windows and Linux which will automatically register the application as a handler for the specified file types avoiding manual registration processes that differ across operating systems.
## Complete Example
:::note Tested Deployments
@ -208,29 +253,26 @@ 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 |
| macOS 15.4 | `darwin-arm` | `36.1.0` | 2025-05-03 |
| Windows 11 | `win11-x64` | `36.1.0` | 2025-05-03 |
| 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 |
:::
This demo includes a drag-and-drop box as well as a file input box, mirroring
the [SheetJS Data Preview Live Demo](https://oss.sheetjs.com/sheetjs/)
The core data in this demo is an editable HTML table. The readers build up the
table using `sheet_to_html` (with `editable:true` option) and the writers scrape
the table using `table_to_book`.
The demo project is wired for `electron-forge` to build the standalone binary.
You can also use `electron-builder` to build a packaged installer binary.
1) Download the demo files:
- [`package.json`](pathname:///electron/package.json) : project structure
- [`main.js`](pathname:///electron/main.js) : main process script
- [`index.html`](pathname:///electron/index.html) : window page
- [`index.js`](pathname:///electron/index.js) : script loaded in render context
- [`preload.js`](pathname:///electron/preload.js) : preload script (ContextBridge API worker)
- [`styles.css`](pathname:///electron/styles.css) : stylesheet
:::caution pass
@ -248,6 +290,8 @@ curl -LO https://docs.sheetjs.com/electron/package.json
curl -LO https://docs.sheetjs.com/electron/main.js
curl -LO https://docs.sheetjs.com/electron/index.html
curl -LO https://docs.sheetjs.com/electron/index.js
curl -LO https://docs.sheetjs.com/electron/preload.js
curl -LO https://docs.sheetjs.com/electron/styles.css
```
:::note pass
@ -265,6 +309,8 @@ 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
curl.exe -LO https://docs.sheetjs.com/electron/preload.js
curl.exe -LO https://docs.sheetjs.com/electron/styles.css
```
:::
@ -322,12 +368,12 @@ The program will run on ARM64 Windows.
#### Electron API
7) Click "Click here to select a file from your computer". With the file picker,
7) Click "Click here to select a file. With the file picker,
navigate to the Downloads folder and select `pres.numbers`.
The application should show data in a table.
The application should show a dropdown component for each worksheet contained in your file, clicking on it should display its data within a table.
8) Click "Export Data!" and click "Save" in the popup. By default, it will try
8) Click "Export" and click "Save" in the popup. By default, it will try
to write to `Untitled.xls` in the Downloads folder.
:::note pass
@ -341,27 +387,51 @@ If there is no default name, enter `Untitled.xls` and click "Save".
The app will show a popup once the data is exported. Open the file in a
spreadsheet editor and compare the data to the table shown in the application.
#### Drag and Drop
#### Open with menu
9) Close the application, end the terminal process and re-launch (see step 6)
10) Open the Downloads folder in a file explorer or finder window.
11) Click and drag the `pres.numbers` file from the Downloads folder to the
bordered "Drop a spreadsheet file" box. The file data should be displayed.
11) Right-click the `pres.numbers` file and select "Open with".
#### File Input Element
12) Select your application binary by navigating to the folder where the application was built (see step 4).
12) Close the application, end the terminal process and re-launch (see step 6)
:::info
On some Linux based systems, depending on the file manager in use selecting the binary directly may not be possible.
:::
13) Click "Choose File". With the file picker, navigate to the Downloads folder
The application should show a dropdown component for each worksheet contained in your file, clicking on it should display its data within a table.
#### Drag and Drop
13) Close the application, end the terminal process and re-launch (see step 6)
14) Open the Downloads folder in a file explorer or finder window.
15) Click and drag the `pres.numbers` file from the Downloads folder
into the application window.
The application should show a dropdown component for each worksheet contained in your file, clicking on it should display its data within a table.
:::info
On some Linux based systems, the experience can differ depending on the window manager / desktop environment in use.
:::
#### File Picker Element
16) Close the application, end the terminal process and re-launch (see step 6)
17) Click "Choose File". With the file picker, navigate to the Downloads folder
and select `pres.numbers`.
The application should show a dropdown component for each worksheet contained in your file, clicking on it should display its data within a table.
## 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 required changes for Electron `36.1.0`.
There are no Electron-specific workarounds in the library, but Electron broke
backwards compatibility multiple times. A summary of changes is noted below.
@ -389,4 +459,8 @@ Electron 14 and later must use `@electron/remote` instead of `remote`. An
:::
For demos built on top of Electron 36 and later we isolate the processes entirely and the demo no longer requires `@electron/remote`.
However, `nodeIntegration: false` by default now means that the renderer process no longer has access to NodeJS APIs.
To expose NodeJS APIs to the renderer process, we use the contextBridge API to expose APIs from the main process to the renderer process. [See more](https://www.electronjs.org/docs/latest/api/context-bridge). This has been best practice since Electron 25.
[^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.

@ -39,7 +39,7 @@ This demo was tested in the following deployments:
|:-------------|:---------------|:----------|:----------|:-----------|
| `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` | `5.0.0-beta.4` | `14.15.3` | Pre-built | 2025-05-07 |
| `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 |

@ -38,8 +38,8 @@ 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-05-11 |
| `win11-x64` | `5.8.1` | `18.5.0` | 2025-05-07 |
| `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 |
@ -78,7 +78,7 @@ When this demo was last tested, `pkg` failed with an error referencing `node20`:
> Error! No available node version satisfies 'node20'
```
**`pkg` does not support NodeJS 20 or 22 or 24!**
**`pkg` does not support NodeJS 20 or 22!**
The local NodeJS version must be rolled back to version 18.

@ -32,7 +32,7 @@ This demo was tested in the following deployments:
|:-------------|:--------|:----------|:-----------|
| `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` | 2025-05-07 |
| `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 |
@ -136,7 +136,7 @@ workload, Python 3.11, and NASM[^2].
:::caution pass
In some test runs, the build failed:
When the demo was last tested, the build failed:
```
Not an executable Python program
@ -168,11 +168,8 @@ When the demo was last tested on Windows, the build failed:
error MSB8020: The build tools for Visual Studio 2019 (Platform Toolset = 'v142') cannot be found. To build using the v142 build tools, please install Visual Studio 2019 build tools.
```
This error was fixed by installing the following components from Visual Studio:
- `C++/CLI support for v142 build tools`
- `MSVC v142 - VS 2019 - C++ x64/x86 build tools`
- `MSVC v142 - VS 2019 - C++ x64/x86 Spectre-mitigated libs`
This error was fixed by installing the `v142` build tools through the Visual
Studio installer.
:::

@ -81,7 +81,7 @@ This demo was last tested in the following deployments:
|:-------------|:---------|:-----------|
| `darwin-x64` | `1.2.10` | 2025-04-21 |
| `darwin-arm` | `1.2.8` | 2025-04-03 |
| `win11-x64` | `1.2.13` | 2025-05-07 |
| `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 |

@ -228,7 +228,7 @@ order of todo items in the export matches the list displayed in the webpage.
5) Start a local web server:
```bash
npx -y http-server .
npx http-server .
```
The command will display a URL (typically `http://localhost:8080`) which can be

@ -35,7 +35,7 @@ will be available in the future.
:::note Tested Deployments
This demo was last tested on 2025-05-11.
This demo was last tested on 2024-06-12.
:::
@ -59,8 +59,7 @@ Azure Functions Core Tools (`func`) telemetry is controlled through the
<Tabs groupId="os">
<TabItem value="unix" label="Linux/MacOS">
Add the following line to `.profile`, `.bash_profile`, `.bashrc`, `.zprofile`,
`.zshrc`, and any other configuration files:
Add the following line to `.profile`, `.bashrc` and `.zshrc`:
```bash
export FUNCTIONS_CORE_TOOLS_TELEMETRY_OPTOUT=1
@ -132,7 +131,7 @@ Uploaded files can be pulled into `ArrayBuffer` objects.
This function returns a promise that resolves to an `ArrayBuffer` object:
```js title="Get raw data from an uploaded file"
```js
const { Blob } = require('buffer');
async function get_file_from_request(request, form_field_name) {
@ -159,7 +158,7 @@ SheetJS workbook objects[^3] which can be processed with other API functions.
For example, a handler can use `sheet_to_csv`[^4] to generate CSV text from
user-submitted spreadsheets:
```js title="Function that reads uploaded workbooks and converts to CSV"
```js
const { Blob } = require('buffer');
const { app } = require('@azure/functions');
const XLSX = require('xlsx');
@ -195,7 +194,7 @@ The following example generates a sample worksheet using the `aoa_to_sheet`[^6]
method, generates a sample workbook using worksheet helper methods[^7], writes
the workbook to XLSX format in a Buffer, and sends the Buffer in the response:
```js title="Function that exports data and initiates a download to XLSX"
```js
const { app } = require('@azure/functions');
const XLSX = require('xlsx');
@ -300,20 +299,10 @@ Until the bugs are resolved, JavaScript should be preferred over TypeScript.
npm start
```
The process will display the functions and respective URLs:
```text title="Expected output"
Functions:
SheetJSAzure: [GET,POST] http://localhost:7071/api/SheetJSAzure
(access this URL)------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
7) While the server is running, open a new terminal window and make a GET
request to the URL listed in the previous step:
7) While the server is running, open a new terminal window and make a request:
```bash
curl http://localhost:7071/api/SheetJSAzure
curl -L http://localhost:7071/api/SheetJSAzure
```
The terminal should display `Hello, world!`
@ -344,7 +333,7 @@ npm start
make a POST request to the dev server:
```bash
curl -o pres.numbers https://docs.sheetjs.com/pres.numbers
curl -LO https://docs.sheetjs.com/pres.numbers
curl -X POST -F "upload=@pres.numbers" http://localhost:7071/api/SheetJSAzure
```
@ -365,20 +354,19 @@ Open in Excel or another spreadsheet editor to confirm the file is valid.
15) Click "+ Create"
16) In the "Create Function App" screen, click "Consumption" and click "Select".
16) Select the following options:
17) In the next screen, select the following options:
- "Select a hosting option": "Consumption"
- Look for the "Function App name" input field and type a memorable function
name. When this demo was last tested, the name "sheetjsazure" was chosen.
- Type a memorable "Function Name" ("sheetjsazure" when last tested)
- "Operating System": "Windows"
- "Runtime stack": select `Node.js`
18) Click "Review + create", then click "Create" to create the function.
17) Click "Review + create", then click "Create" to create the function.
The page will redirect to a new page. It will display a status message:
The page will display a status message
> ... Deployment is in progress
@ -386,19 +374,21 @@ When the resources are configured, the status will change to
> Your deployment is complete
19) Click "Go to Resource".
18) Click "Go to Resource".
19) Take note of the URL from the "Essentials" table.
#### Deploy to Azure
20) Sign into Azure from the command line:
20) Sign into Azure:
```bash
```
az login
```
The login flow resumes in the browser.
21) Deploy to Azure. Replace `FUNCTION_NAME` with the name from Step 17:
21) Deploy to Azure. Replace `FUNCTION_NAME` with the name from Step 16:
```bash
func azure functionapp publish FUNCTION_NAME
@ -439,7 +429,7 @@ make a POST request to the production server. Replace `FUNCTION_URL` with the
Invoke URL from Step 21:
```bash
curl -o pres.numbers https://docs.sheetjs.com/pres.numbers
curl -LO https://docs.sheetjs.com/pres.numbers
curl -X POST -F "upload=@pres.numbers" FUNCTION_URL
```
@ -556,9 +546,9 @@ requests and 2000 write requests per month.
- "Redundancy": select LRS (Locally-redundant storage)
5) Click "Review + create", then click "Create" to create the storage.
5) Click "Review", then click "Create" to create the storage.
The page will redirect to a new page. It will display a status message:
The page will display a status message
> ... Deployment is in progress
@ -606,64 +596,11 @@ npm init -y
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @azure/storage-blob`}
</CodeBlock>
14) Save the following codeblock to `SheetJSReadFromAzure.mjs`:
14) Copy the [`SheetJSReadFromAzure.mjs` code block](#downloading-data) and save
to `SheetJSReadFromAzure.mjs`.
```js title="SheetJSReadFromAzure.mjs"
import { BlobServiceClient } from "@azure/storage-blob";
import { read, utils } from "xlsx";
/* replace these constants */
// highlight-start
const connStr = "<REPLACE WITH CONNECTION STRING>";
const containerName = "<REPLACE WITH CONTAINER NAME>";
// highlight-end
/* Blob name */
const blobName = "SheetJSBloblobber.xlsx";
/* get a readable stream*/
const blobServiceClient = BlobServiceClient.fromConnectionString(connStr);
const containerClient = blobServiceClient.getContainerClient(containerName);
const blobClient = containerClient.getBlobClient(blobName);
const response = (await blobClient.download()).readableStreamBody;
/* collect data into a Buffer */
const bufs = [];
for await(const buf of response) bufs.push(buf);
const downloaded = Buffer.concat(bufs);
/* parse downloaded buffer */
const wb = read(downloaded);
/* print first worksheet */
console.log(utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
```
15) Save the following codeblock to `SheetJSWriteToAzure.mjs`:
```js title="SheetJSWriteToAzure.mjs"
import { BlobServiceClient } from "@azure/storage-blob";
import { write, utils } from "xlsx";
/* replace these constants */
// highlight-start
const connStr = "<REPLACE WITH CONNECTION STRING>";
const containerName = "<REPLACE WITH CONTAINER NAME>";
// highlight-end
/* Blob name */
const blobName = "SheetJSBloblobber.xlsx";
/* Create a simple workbook and write XLSX to buffer */
const ws = utils.aoa_to_sheet(["SheetJS".split(""), [5,4,3,3,7,9,5]]);
const wb = utils.book_new(); utils.book_append_sheet(wb, ws, "Sheet1");
const buf = write(wb, {type: "buffer", bookType: "xlsx"});
/* upload buffer */
const blobServiceClient = BlobServiceClient.fromConnectionString(connStr);
const containerClient = blobServiceClient.getContainerClient(containerName);
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
const uploadBlobResponse = await blockBlobClient.upload(buf, buf.length);
```
15) Copy the [`SheetJSWriteToAzure.mjs` code block](#uploading-data) and save
to `SheetJSWriteToAzure.mjs`.
16) Edit both `SheetJSReadFromAzure.mjs` and `SheetJSWriteToAzure.mjs`:
@ -679,7 +616,7 @@ the buffer to a file named `SheetJSBloblobber.xlsx` on Azure Blob Storage.
The read demo fetches `SheetJSBloblobber.xlsx` and displays the data.
```text title="Data in SheetJSBloblobber.xlsx"
```
| A | B | C | D | E | F | G |
---+---|---|---|---|---|---|---|
1 | S | h | e | e | t | J | S |
@ -704,7 +641,7 @@ node SheetJSReadFromAzure.mjs
It will fetch the file created in the previous step and display CSV rows.
```text title="Expected output"
```
S,h,e,e,t,J,S
5,4,3,3,7,9,5
```

@ -111,7 +111,6 @@ const jwt = new google.auth.JWT({
key: creds.private_key,
scopes: [
'https://www.googleapis.com/auth/spreadsheets', // Google Sheets
'https://www.googleapis.com/auth/drive', // Google Drive
'https://www.googleapis.com/auth/drive.file', // Google Drive
]
});
@ -496,7 +495,7 @@ At this point `wb` is a SheetJS workbook object[^10].
:::note Tested Deployments
This demo was last tested on 2025-05-14 using `googleapis` version `148.0.0`.
This demo was last tested on 2024-06-08 using `googleapis` version `140.0.0`.
The demo uses Sheets v4 and Drive v3 APIs.
:::
@ -552,10 +551,10 @@ be a selection box. Click the `▼` icon to show the modal.
If the selection box is missing, expand the browser window.
3) Click "New project" in the top right corner of the modal.
3) Click "NEW PROJECT" in the top right corner of the modal.
4) In the New Project screen, enter "SheetJS Test" in the Project name textbox
and select "No organization" in the Location box. Click "Create".
and select "No organization" in the Location box. Click "CREATE".
A notification will confirm that the project was created:
@ -570,13 +569,14 @@ The goal of this section is to enable Google Sheets API and Google Drive API.
:::
5) Click "Select a project" and select "SheetJS Test" from the Recent tab.
5) Open the Project Selector (`▼` icon) and select "SheetJS Test"
6) In the search bar, type "Enabled" and select "Enabled APIs & services".
6) In the search bar, type "Enabled" and select "Enabled APIs & services". This
item will be in the "PRODUCTS & PAGES" part of the search results.
#### Enable Google Sheets API
7) Near the top of the page, click "+ Enable APIs and services".
7) Near the top of the page, click "+ ENABLE APIS AND SERVICES".
8) In the search bar near the middle of the page (not the search bar at the top),
type "Sheets" and press <kbd>Enter</kbd>.
@ -585,11 +585,11 @@ In the results page, look for "Google Sheets API". Click the card
9) In the Product Details screen, click the blue "ENABLE" button.
10) Click the left arrow (`<-`) next to "API/Service Details".
10) Click the left arrow (`<-`) next to "API/Service details".
#### Enable Google Drive API
11) Near the top of the page, click "+ Enable APIs and services".
11) Near the top of the page, click "+ ENABLE APIS AND SERVICES".
12) In the search bar near the middle of the page (not the search bar at the top),
type "Drive" and press <kbd>Enter</kbd>.
@ -614,13 +614,13 @@ the top bar.
15) Click the Project Selector (`:·` icon) and select "SheetJS Test".
16) In the search bar, type "Credentials" and select the "Credentials" item with
subtitle "APIs & Services":
subtitle "APIs & Services". This item will be in the "PRODUCTS & PAGES" group:
![Credentials](pathname:///gsheet/creds.png)
17) Click "+ Create credentials". In the dropdown, select "Service account"
17) Click "+ CREATE CREDENTIALS". In the dropdown, select "Service Account"
18) Enter "SheetJService" for Service account name. Click "Create and continue".
18) Enter "SheetJService" for Service account name. Click "CREATE AND CONTINUE"
:::note pass
@ -628,24 +628,24 @@ The Service account ID is generated automatically.
:::
19) In Step 2 "Grant this service account access to project", click Continue.
19) In Step 2 "Grant this service account access to project", click CONTINUE
20) In Step 3 click "Done". You will be taken back to the credentials screen
20) In Step 3 click "DONE". You will be taken back to the credentials screen
#### Create JSON Key
21) Look for "SheetJService" in the "Service Accounts" table and click the email
address in the row.
22) Click "Keys" in the horizontal bar near the top of the page.
22) Click "KEYS" in the horizontal bar near the top of the page.
23) Click "Add key" and select "Create new key" in the dropdown.
23) Click "ADD KEY" and select "Create new key" in the dropdown.
24) In the popup, select the "JSON" radio button and click "Create".
24) In the popup, select the "JSON" radio button and click "CREATE".
The page will download a JSON file. If prompted, allow the download.
25) Click "Close"
25) Click "CLOSE"
### Create Document
@ -675,7 +675,7 @@ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz googlea
29) Download [`init.mjs`](pathname:///gsheet/init.mjs):
```bash
curl -o init.mjs https://docs.sheetjs.com/gsheet/init.mjs
curl -LO https://docs.sheetjs.com/gsheet/init.mjs
```
Edit the marked lines near the top of the file:
@ -713,12 +713,11 @@ Shared a-long-string-of-characters with YOUR_ACCOUNT@gmail.com
The long string of characters after "Created Google Workbook" is the ID. Take
note of this ID.
31) Sign into Google Drive and select "Shared with me" from the left sidebar. A
shared document "SheetJS Test" should be displayed in the table. It will be
owned by the service account.
31) Sign into Google Sheets. A shared document "SheetJS Test" should be
displayed in the table. It will be owned by the service account.
32) Click `⋮` next to "SheetJS Test" and select "Open with" > "Google Sheets".
Confirm that the document has two worksheets named "SheetJS1" and "SheetJS2".
32) Open the shared document from step 31 and confirm that the document has two
worksheets named "SheetJS1" and "SheetJS2".
Confirm the worksheet data matches the following screenshots:
@ -787,13 +786,13 @@ NUMBERS file.
34) Download the [test file `pres.numbers`](https://docs.sheetjs.com/pres.numbers):
```bash
curl -o pres.numbers https://docs.sheetjs.com/pres.numbers
curl -LO https://docs.sheetjs.com/pres.numbers
```
35) Download [`load.mjs`](pathname:///gsheet/load.mjs):
```bash
curl -o load.mjs https://docs.sheetjs.com/gsheet/load.mjs
curl -LO https://docs.sheetjs.com/gsheet/load.mjs
```
Edit the marked lines near the top of the file:
@ -831,7 +830,7 @@ The goal of this section is to export the raw data from Google Sheets to XLSB.
38) Download [`dump.mjs`](pathname:///gsheet/dump.mjs):
```bash
curl -o dump.mjs https://docs.sheetjs.com/gsheet/dump.mjs
curl -LO https://docs.sheetjs.com/gsheet/dump.mjs
```
Edit the marked lines near the top of the file:
@ -877,7 +876,7 @@ assign a grid of values
43) Download [`raw.mjs`](pathname:///gsheet/raw.mjs):
```bash
curl -o raw.mjs https://docs.sheetjs.com/gsheet/raw.mjs
curl -LO https://docs.sheetjs.com/gsheet/raw.mjs
```
Edit the marked lines near the top of the file:

@ -266,7 +266,7 @@ function SheetJSEnregistrez() {
:::note Tested Deployments
This demo was last tested on 2025-05-14.
This demo was last tested on 2024-05-27.
:::

@ -1,336 +0,0 @@
---
title: Visualizing Data in VS Code
sidebar_label: Visual Studio Code
description: View Excel files directly in VS Code. Seamlessly browse spreadsheet data without leaving your editor using SheetJS. Navigate between worksheets and pages of data with a responsive
pagination_prev: demos/cloud/index
pagination_next: demos/bigdata/index
sidebar_custom_props:
summary: View Excel files directly within Visual Studio Code
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
[Visual Studio Code](https://code.visualstudio.com) is a popular code editor
that supports JavaScript extensions for customizing and enhancing functionality.
The ["Complete Example"](#complete-example) uses SheetJS in a VS Code extension
to view Excel files directly within the editor. The extension leverages the VS
Code "Custom Editor API"[^2] and "WebView API"[^1] to display spreadsheet data
as HTML tables.
:::tip pass
["SheetJS Spreadsheet Viewer"](https://marketplace.visualstudio.com/items?itemName=asadbek.sheetjs-demo)
is a sample extension based on this demo.
[The source code](https://git.sheetjs.com/asadbek064/sheetjs-vscode-extension)
is available on the SheetJS Git server. Feedback and contributions are welcome!
:::
![Expected output](pathname:///vscode/extension-viewing-xls-file.png)
:::note Tested Deployments
This demo was verified in the following deployments:
| Platform | Architecture | Date |
|:-----------------|:-------------|:-----------|
| VS Code 1.100.0 | `darwin-arm` | 2025-05-15 | TODO
| VSCodium 1.100.0 | `darwin-arm` | 2025-05-15 | TODO
| Cursor | `win11-arm` | 2025-05-15 | TODO
| Windsurf | `win11-arm` | 2025-05-15 | TODO
| Void | `win11-arm` | 2025-05-15 | TODO
:::
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
imported from any component or script in the extension.
:::caution pass
The module must be installed as a development dependency. If the module is
installed as a normal dependency, `vsce`[^5] (Visual Studio Code Extension
Manager) will fail to package or publish your extension correctly.
<Tabs groupId="pm">
<TabItem value="npm" label="npm">
<CodeBlock language="bash">{`\
npm rm --save xlsx
npm i --save-dev https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="pnpm" label="pnpm">
<CodeBlock language="bash">{`\
pnpm rm xlsx
pnpm install -D https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="yarn" label="Yarn" default>
<CodeBlock language="bash">{`\
yarn remove xlsx
yarn add -D https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
</Tabs>
:::
## Extension Architecture
The VS Code Spreadsheet viewer extension has three main components:
- **Extension Entry Point:** Registers the extension with VS Code
- **Custom Editor Provider:** Handles Excel files and converts them to web content
- **WebView Content:** Displays Excel data as HTML tables
The extension uses VS Code's `Custom Editor API`[^2] to register as a handler for Excel files. When a file is opened,
SheetJS parses it and displays the data in a WebView component.
### Extension Entry Point
The main entry point registers the custom editor provider:
```ts title="src/extension.ts"
import * as vscode from 'vscode';
// highlight-start
import { ExcelEditorProvider } from './excelEditorProvider';
// highlight-end
export function activate(context: vscode.ExtensionContext) {
// SheetJS Spreadsheet Viewer extension activating...
// highlight-start
const provider = ExcelEditorProvider.register(context);
context.subscriptions.push(provider);
// highlight-end
}
export function deactivate() {}`}
```
The `custom editor`[^3] is configured to support specific file types, giving us complete control over how each file is
presented to the user. Additionally, `custom document`[^4] enables us to maintain and persist the state of each individual
file that's opened.
<CodeBlock language="typescript" value="typescript" title="src/excelEditorProvider.ts">
{`import * as vscode from 'vscode';
// highlight-start
import * as XLSX from 'xlsx';
import { ExcelDocument } from './excelDocument';
// highlight-end
// A simple class to store document state (one per opened file)
class ExcelDocument implements vscode.CustomDocument {
constructor(public readonly uri: vscode.Uri) {}
dispose() {}
}
export class ExcelEditorProvider implements vscode.CustomReadonlyEditorProvider<ExcelDocument> {
// ...
public static register(context: vscode.ExtensionContext): vscode.Disposable {
return vscode.window.registerCustomEditorProvider(
'excelViewer.spreadsheet',
new ExcelEditorProvider(),
{ webviewOptions: { retainContextWhenHidden: true } } // keep webview state when hidden
);
}
// ...
}`}
</CodeBlock>
### Reading Files
The extension reads Excel files using the VS Code filesystem API and passes
the data to SheetJS for parsing:
<CodeBlock language="typescript" value="typescript" title="src/excelEditorProvider.ts">
{`export class ExcelEditorProvider implements vscode.CustomReadonlyEditorProvider<ExcelDocument> {
// ...
private async loadWorkbook(document: ExcelDocument, webviewPanel: vscode.WebviewPanel): Promise<XLSX.WorkBook> {
const data: Uint8Array = await vscode.workspace.fs.readFile(document.uri);
const options: XLSX.ParsingOptions = {
type: 'array',
cellStyles: true,
cellDates: true,
};
return XLSX.read(new Uint8Array(data), options); // returns a XLSX.WorkBook
}
// This is called when the first time an editor for a given resource is opened
async openCustomDocument(uri: vscode.Uri): Promise<ExcelDocument> {
return new ExcelDocument(uri);
}
// This is called whenever the user opens a new editor
async resolveCustomEditor(document: ExcelDocument, webviewPanel: vscode.WebviewPanel): Promise<void> {
const wb: XLSX.WorkBook = await this.loadWorkbook(document, webviewPanel);
const htmlTable = XLSX.utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
webviewPanel.webview.html = \`<!DOCTYPE html><html><body>\${htmlTable}</body></html>\`;
}
}`}
</CodeBlock>
### Usage Flow
```mermaid
sequenceDiagram
actor User
participant VSCode as VS Code
participant Provider as ExcelEditorProvider
participant SheetJS
participant WebView
User->>VSCode: Open .xlsx file
VSCode->>Provider: openCustomDocument(uri)
Provider-->>VSCode: return ExcelDocument
VSCode->>Provider: resolveCustomEditor(document, webviewPanel)
Provider->>VSCode: workspace.fs.readFile(document.uri)
VSCode-->>Provider: return file data
Provider->>SheetJS: XLSX.read(data, options)
SheetJS-->>Provider: return workbook
Provider->>SheetJS: XLSX.utils.sheet_to_html(sheet)
SheetJS-->>Provider: return HTML
Provider->>WebView: set webview.html
WebView-->>User: Display Excel data
```
## Complete Example
1) Create a new VS Code extension
```bash
npx --package yo --package generator-code -- yo code
```
When prompted, enter the following options:
- `What type of extension do you want to create?`: Select `New Extension (TypeScript)` and press <kbd>Enter</kbd>
- `What's the name of your extension?`: Type `sheetjs-demo` and press <kbd>Enter</kbd>
- `What's the identifier of your extension?`: Press <kbd>Enter</kbd>
- `What's the description of your extension?`: Press <kbd>Enter</kbd>
- `Initialize a git repository?`: Type `n` and press <kbd>Enter</kbd>
- `Which bundler to use?`: Select `webpack` and press <kbd>Enter</kbd>
- `Which package manager to use?`: Select `pnpm` and press <kbd>Enter</kbd>
![Expected output](pathname:///vscode/yeo-code.png)
- `Do you want to open the new folder with Visual Studio Code?`: Press <kbd>Enter</kbd>
2) [Install the dependencies](#integration-details) and start the dev server:
<CodeBlock language="bash">{`\
cd sheetjs-demo
pnpm install -D https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
pnpm run watch
`}
</CodeBlock>
3) Save the following code snippet to `src/excelEditorProvider.ts`:
<CodeBlock language="typescript" value="typescript" title="src/excelEditorProvider.ts">{`\
import * as vscode from 'vscode';
import * as XLSX from 'xlsx';
class ExcelDocument implements vscode.CustomDocument {
constructor(public readonly uri: vscode.Uri) { }
dispose() { }
}
export class ExcelEditorProvider implements vscode.CustomReadonlyEditorProvider<ExcelDocument> {
public static register(context: vscode.ExtensionContext): vscode.Disposable {
return vscode.window.registerCustomEditorProvider(
'excelViewer.spreadsheet',
new ExcelEditorProvider(),
{ webviewOptions: { retainContextWhenHidden: true } } // keep webview state when hidden
);
}
private async loadWorkbook(document: ExcelDocument, webviewPanel: vscode.WebviewPanel): Promise<XLSX.WorkBook> {
const data: Uint8Array = await vscode.workspace.fs.readFile(document.uri);
const options: XLSX.ParsingOptions = {
type: 'array',
cellStyles: true,
cellDates: true,
};
return XLSX.read(new Uint8Array(data), options); // returns a XLSX.WorkBook
}
// This is called when the first time an editor for a given resource is opened
async openCustomDocument(uri: vscode.Uri): Promise<ExcelDocument> {
return new ExcelDocument(uri);
}
// This is called whenever the user opens a new editor
async resolveCustomEditor(document: ExcelDocument, webviewPanel: vscode.WebviewPanel): Promise<void> {
const wb: XLSX.WorkBook = await this.loadWorkbook(document, webviewPanel);
const htmlTable = XLSX.utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
webviewPanel.webview.html = \`<!DOCTYPE html><html><body>\${htmlTable}</body></html>\`;
}
}`}
</CodeBlock>
4) Register the custom editor provider in `src/extension.ts`:
<CodeBlock language="typescript" value="typescript" title="src/extension.ts (replace contents)">{`\
import * as vscode from 'vscode';
import { ExcelEditorProvider } from './excelEditorProvider';
export function activate(context: vscode.ExtensionContext) {
// SheetJS Spreadsheet Viewer extension activating...
const provider = ExcelEditorProvider.register(context);
context.subscriptions.push(provider);
}
export function deactivate() {}`}
</CodeBlock>
5) Register the custom editor in the `contributes` section of `package.json`:
<CodeBlock language="json" value="json" title="package.json (add highlighted lines)">{`\
"main": "./dist/extension.js",
"contributes": {
// highlight-start
"customEditors": [
{
"viewType": "excelViewer.spreadsheet",
"displayName": "SheetJS Demo",
"selector": [
{ "filenamePattern": "*.xlsx" },
{ "filenamePattern": "*.xls" }
]
}
],
// highlight-end
"commands": [
{
"command": "sheetjs-demo.helloWorld",
"title": "Hello World"
}
]
},
`}
</CodeBlock>
6. Inside the editor, open `src/extension.ts` and press <kbd>F5</kbd> or run the command **Debug: Start Debugging**
from the Command Palette (<kbd>⇧⌘P</kbd>). This will compile and run the extension in a new Extension Development Host window.
7. Select the new VSCode Window and open a `.xlsx` or `.xls` file.
---
[^1]: See [`Webview API`](https://code.visualstudio.com/api/extension-guides/webview) for more details.
[^2]: See [`Custom Editor API`](https://code.visualstudio.com/api/extension-guides/custom-editors) documentation for more details.
[^3]: See [`Custom Editor`](https://code.visualstudio.com/api/extension-guides/custom-editors#custom-editor) for more details.
[^4]: See [`CustomDocument`](https://code.visualstudio.com/api/extension-guides/custom-editors#customdocument) for more details.
[^5]: See [`Visual Studio Code Extension Manager`](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) for more details.

@ -55,7 +55,7 @@ This demo was tested in the following deployments:
|:-------------|:------------------|:-----------|
| `darwin-x64` | `18.5` (StataNow) | 2025-01-08 |
| `darwin-arm` | `18.5` (StataNow) | 2025-04-24 |
| `win11-x64` | `18.5` (StataNow) | 2025-04-28 |
| `win11-x64` | `18.5` (StataNow) | 2024-12-19 |
| `win11-arm` | `18.5` (StataNow) | 2025-02-23 |
| `linux-x64` | `18.5` (StataNow) | 2025-01-09 |
@ -257,22 +257,6 @@ curl -LO https://www.stata.com/plugins/stplugin.c
curl -LO https://www.stata.com/plugins/stplugin.h
```
:::danger pass
**When this demo was last tested, `stplugin.h` was removed from the website!**
If the official links do not work, the following files should be used:
- [`stplugin.c`](pathname:///stata/stplugin.c)
- [`stplugin.h`](pathname:///stata/stplugin.h)
```bash
curl -LO https://docs.sheetjs.com/plugins/stplugin.c
curl -LO https://docs.sheetjs.com/plugins/stplugin.h
```
:::
4) Download Duktape. In Windows, the following commands should be run in WSL. In
macOS, the commands should be run in the same Terminal session.
@ -479,32 +463,67 @@ The output will show the import result:
(2 vars, 5 obs)
</pre>
20) List the dataset:
20) Open the Data Editor (in Browse or Edit mode) and compare to the screenshot:
```stata
list Name Index
browse Name Index
```
The result should match the following listing:
```
. list Name Index
+----------------------+
| Name Index |
|----------------------|
1. | Bill Clinton 42 |
2. | GeorgeW Bush 43 |
3. | Barack Obama 44 |
4. | Donald Trump 45 |
5. | Joseph Biden 46 |
+----------------------+
```
In the Stata GUI, the Data Editor should match the following screenshot:
![Data Editor showing data from the file](pathname:///stata/data-editor.png)
:::info pass
In the terminal version of Stata, `browse` does not work:
```
. browse Name Index
command browse is unrecognized
r(199);
```
The `codebook` command will display details.
<details>
<summary><b>Expected Output</b> (click to show)</summary>
```text title="Expected output for 80-column terminal windows"
-------------------------------------------------------------------------------
Name Name
-------------------------------------------------------------------------------
Type: String (str12)
Unique values: 5 Missing "": 0/5
Tabulation: Freq. Value
1 "Barack Obama"
1 "Bill Clinton"
1 "Donald Trump"
1 "GeorgeW Bush"
1 "Joseph Biden"
Warning: Variable has embedded blanks.
-------------------------------------------------------------------------------
Index Index
-------------------------------------------------------------------------------
Type: Numeric (byte)
Range: [42,46] Units: 1
Unique values: 5 Missing .: 0/5
Tabulation: Freq. Value
1 42
1 43
1 44
1 45
1 46
```
</details>
:::
[^1]: Run `help import excel` in Stata or see ["import excel"](https://www.stata.com/manuals/dimportexcel.pdf) in the Stata documentation.
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)

@ -30,7 +30,7 @@ This demo was tested by SheetJS users in the following deployments:
| Architecture | Version | Date |
|:-------------|:--------|:-----------|
| `darwin-x64` | R2024b | 2025-03-31 |
| `win11-x64` | R2024b | 2025-05-10 |
| `win11-x64` | R2024b | 2024-12-21 |
:::
@ -234,14 +234,14 @@ run in the macOS Terminal or Windows PowerShell:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz exit-on-epipe commander@2
curl -o xlsx-cli.js https://docs.sheetjs.com/cli/xlsx-cli.js
curl -LO https://docs.sheetjs.com/cli/xlsx-cli.js
npx -y nexe -t 14.15.3 xlsx-cli.js`}
</CodeBlock>
2) Download https://docs.sheetjs.com/pres.numbers to the workspace folder:
```bash
curl -o pres.numbers https://docs.sheetjs.com/pres.numbers
curl -LO https://docs.sheetjs.com/pres.numbers
```
4) Save the following to `SheetJSMATLAB.m` in the workspace folder:

@ -23,7 +23,7 @@ library to read data from spreadsheets and converts to a Maple-friendly format.
```mermaid
flowchart LR
ofile[(generic\nworkbook)]
ofile[(workbook\nXLSB file)]
nfile[(clean file\nXLSX)]
data[[Maple\nTable]]
ofile --> |Maple Extension\nSheetJS + Duktape| nfile
@ -37,7 +37,7 @@ This demo was tested by SheetJS users in the following deployments:
| Architecture | Version | Date |
|:-------------|:---------|:-----------|
| `darwin-x64` | `2024.0` | 2025-01-10 |
| `win11-x64` | `2024.2` | 2025-05-07 |
| `win11-x64` | `2024.0` | 2024-12-19 |
:::
@ -60,7 +60,7 @@ and generates clean XLSX files that Maple can import.
The extension function ultimately pairs the SheetJS `read`[^2] and `write`[^3]
methods to read data from the old file and write a new file:
```js title="Script that will be run by Maple extension"
```js
var workbook = XLSX.read(original_file_data, { type: "buffer" });
var new_file_data = XLSX.write(workbook, { type: "array", bookType: "xlsx" });
```
@ -84,7 +84,6 @@ flowchart LR
wb --> |SheetJS\n`write`| njbuf
njbuf --> |Duktape\nBuffer Ops| nbuf
nbuf --> |C\nWrite File| nfile
linkStyle 2,3 color:blue,stroke:blue;
```
### C Extensions
@ -129,7 +128,7 @@ file, exports data to `sheetjsw.xlsx` and returns the string `"sheetjsw.xlsx"`.
This can be chained with `Import` from `ExcelTools`:
```maple title="Sample usage of SheetToXLSX extension"
```maple
with(ExcelTools);
Import(SheetToXLSX("pres.numbers"))
```

@ -130,7 +130,7 @@ This demo was tested in the following deployments:
|:-------------|:--------|:-----------|
| `darwin-x64` | `2.7.0` | 2025-03-31 |
| `darwin-arm` | `2.7.0` | 2025-02-13 |
| `win11-x64` | `2.7.0` | 2025-04-28 |
| `win11-x64` | `2.7.0` | 2024-12-20 |
| `win11-arm` | `2.7.0` | 2025-02-23 |
| `linux-x64` | `2.7.0` | 2025-04-21 |
| `linux-arm` | `2.7.0` | 2025-02-15 |
@ -692,7 +692,7 @@ The main Duktape code can be added to the Zig build pipeline.
:::note pass
The following explanation was verified against Zig 0.14.0.
The following explanation was verified against Zig 0.12.0.
:::
@ -710,7 +710,7 @@ folder must be added to the include path list:
```
The `duktape.c` source file must be added to the build sequence. For Zig version
0.14.0, Duktape must be compiled with flags `-std=c99 -fno-sanitize=undefined`
0.12.0, Duktape must be compiled with flags `-std=c99 -fno-sanitize=undefined`
and linked against `libc` and `libm`:
```zig title="build.zig"
@ -786,7 +786,7 @@ This demo was tested in the following deployments:
|:-------------|:--------|:---------|:-----------|
| `darwin-x64` | `2.7.0` | `0.14.0` | 2025-03-31 |
| `darwin-arm` | `2.7.0` | `0.13.0` | 2025-02-13 |
| `win11-x64` | `2.7.0` | `0.14.0` | 2025-04-28 |
| `win11-x64` | `2.7.0` | `0.13.0` | 2024-12-20 |
| `win11-arm` | `2.7.0` | `0.13.0` | 2025-02-23 |
| `linux-x64` | `2.7.0` | `0.14.0` | 2025-04-21 |
| `linux-arm` | `2.7.0` | `0.13.0` | 2025-02-15 |
@ -855,8 +855,8 @@ The following commands should be run within WSL bash.
For X64 Windows:
```bash
curl -LO https://ziglang.org/download/0.14.0/zig-windows-x86_64-0.14.0.zip
unzip zig-windows-x86_64-0.14.0.zip
curl -LO https://ziglang.org/download/0.13.0/zig-windows-x86_64-0.13.0.zip
unzip zig-windows-x86_64-0.13.0.zip
```
For ARM64 Windows:

@ -148,7 +148,7 @@ This demo was tested in the following deployments:
|:--------------|:-------------|:--------------|:-----------------|:-----------|
| `13.7.5` | `darwin-x64` | macOS 15.3.2 | `clang 16.0.0` | 2025-03-31 |
| `13.5.92` | `darwin-arm` | macOS 14.5 | `clang 16.0.0` | 2025-02-15 |
| `13.8.124` | `win11-x64` | Windows 11 | `CL 19.43.34810` | 2025-05-11 |
| `12.7.130` | `win11-x64` | Windows 11 | `CL 19.42.34435` | 2024-12-20 |
| `12.7.130` | `linux-x64` | HoloOS 3.6.20 | `gcc 13.2.1` | 2025-01-02 |
| `13.5.92` | `linux-arm` | Debian 12 | `gcc 12.2.0` | 2025-02-15 |
@ -204,15 +204,13 @@ installation steps.
Using the installer tool, the "Desktop development with C++" workload must be
installed. In the sidebar, verify the following components are checked:
- "C++ ATL for latest ... build tools (x86 & x64)" (`v143` when last tested)
- "C++ ATL for latest ... build tools with Spectre Mitigations (x86 & x64)" (`v143` when last tested)
- "C++ MFC for latest ... build tools (x86 & x64)" (`v143` when last tested)
- "C++ MFC for latest ... build tools with Spectre Mitigations (x86 & x64)" (`v143` when last tested)
- "C++ ATL for latest ... build tools" (`v143` when last tested)
- "C++ MFC for latest ... build tools" (`v143` when last tested)
In the "Individual components" tab, search for "Windows 11 SDK" and verify that
"Windows 11 SDK (10.0.26100.0)" is checked.
"Windows 11 SDK (10.0.22621.0)" is checked.
**Even though newer SDKs may exist, V8 expects specific Windows SDK versions!**
**Even though newer SDKs exist, Windows 11 SDK 10.0.22621.0 must be installed!**
Click "Modify" and allow the installer to finish.
@ -224,7 +222,7 @@ The SDK debugging tools must be installed after the SDK is installed.
available, search for "Installed apps".
2) When the setting panel opens, scroll down to "Windows Software Development
Kit - Windows 10.0.26100" and click "Modify".
Kit - Windows 10.0.22621" and click "Modify".
3) In the new window, select "Change" and click "Next"
@ -268,12 +266,13 @@ sudo chmod 777 /usr/local/lib
</TabItem>
<TabItem value="win" label="Windows">
```bash
cd c:\
mkdir src
cd src
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
```
[The bundle](https://storage.googleapis.com/chrome-infra/depot_tools.zip) is a
ZIP file that should be downloaded and extracted.
The demo was last tested on a NTFS-formatted drive (mounted at `C:\`). The ZIP
was extracted to `C:\src\depot_tools`.
After extracting, verify that the `depot_tools` folder is not read-only.
</TabItem>
</Tabs>
@ -335,22 +334,7 @@ gclient
gclient
```
:::info Troubleshooting
In test runs on fresh machines, `gclient` failed with the following message:
```
Error: client not configured; see 'gclient config'
```
This was fixed by setting `user.name` and `user.email` in the Git configuration:
```bash
git config --global user.name "John Doe"
git config --global user.email "jdoe@email.com"
```
---
:::caution pass
`gclient` may throw errors related to `git` and permissions issues:
@ -365,7 +349,9 @@ To add an exception for this directory, call:
These issues are related to the exFAT file system. They were resolved by running
the recommended commands and re-running `gclient`.
---
:::
:::caution pass
There may be errors pertaining to `gitconfig`:
@ -449,11 +435,11 @@ The recommended fix is to delete the referenced folder and re-run `gclient sync`
</Tabs>
5) Checkout the desired version. The following command pulls `13.8.124`:
5) Checkout the desired version. The following command pulls `13.7.5`:
```bash
git checkout tags/13.8.124 -b sample
git checkout tags/13.7.5 -b sample
```
:::caution pass
@ -461,14 +447,14 @@ git checkout tags/13.8.124 -b sample
The official documentation recommends:
```bash
git checkout refs/tags/13.8.124 -b sample -t
git checkout refs/tags/13.7.5 -b sample -t
```
This command failed in local testing:
```
E:\v8\v8>git checkout refs/tags/13.8.124 -b sample -t
fatal: cannot set up tracking information; starting point 'refs/tags/13.8.124' is not a branch
E:\v8\v8>git checkout refs/tags/13.7.5 -b sample -t
fatal: cannot set up tracking information; starting point 'refs/tags/13.7.5' is not a branch
```
:::
@ -657,24 +643,6 @@ python3 tools\dev\v8gen.py -vv x64.release.sample
ninja -C out.gn\x64.release.sample v8_monolith
```
:::info pass
If the expected Windows SDK version is missing or the debugging tools are not
installed, the build will fail with an error referencing `include`:
```
Exception: Path "C:\Program Files (x86)\Windows Kits\10\\include\10.0.26100.0\\um" from environment variable "include" does not exist. Make sure the necessary SDK is installed.
```
In the error message, the expected Windows SDK version is listed in the path.
For example, in the aforementioned message, `10.0.26100.0` is the SDK version.
The expected version of the Windows SDK should be installed from Visual Studio
Installer. After installing the SDK, the corresponding SDK debugging tools
should be installed using the procedure from Step 0.
:::
:::caution pass
In local testing, the build sometimes failed with a `dbghelp.dll` error:
@ -776,7 +744,7 @@ ld: multiple errors: unknown file type in '/Users/sheetjs/dev/v8/v8/out.gn/x64.r
```bash
g++ -I. -Iinclude samples/hello-world.cc -o hello_world -fno-rtti -lv8_monolith \
-lv8_libbase -lv8_libplatform -ldl -Lout.gn/x64.release.sample/obj/ -pthread \
-std=c++20 -DV8_COMPRESS_POINTERS=1 -DV8_ENABLE_SANDBOX
-std=c++17 -DV8_COMPRESS_POINTERS=1 -DV8_ENABLE_SANDBOX
./hello_world
```
@ -794,32 +762,10 @@ g++ -I. -Iinclude samples/hello-world.cc -o hello_world -fno-rtti -lv8_monolith
<TabItem value="win11-x64" label="Windows">
```bash
cl /I. /Iinclude samples/hello-world.cc /GR- v8_monolith.lib Advapi32.lib Winmm.lib Dbghelp.lib /std:c++20 /DV8_COMPRESS_POINTERS=1 /DV8_ENABLE_SANDBOX /link /out:hello_world.exe /LIBPATH:out.gn\x64.release.sample\obj\
cl /I. /Iinclude samples/hello-world.cc /GR- v8_monolith.lib Advapi32.lib Winmm.lib Dbghelp.lib /std:c++17 /DV8_COMPRESS_POINTERS=1 /DV8_ENABLE_SANDBOX /link /out:hello_world.exe /LIBPATH:out.gn\x64.release.sample\obj\
.\hello_world.exe
```
:::caution pass
When this demo was last tested, the build failed with a `C++` standard error:
```
c:\v8\v8\include\v8config.h(13): fatal error C1189: #error: "C++20 or later required."
```
The `/std:c++20` option sets the `C++` standard in use. The workaround is to
suppress the broken version check:
```c++ title="include\v8config.h (edit highlighted line)"
#if __cplusplus <= 201703L
// highlight-next-line
//#error "C++20 or later required."
#endif
```
After suppressing the error, re-run the build commands.
:::
</TabItem>
</Tabs>
@ -955,7 +901,7 @@ g++ -I. -Iinclude hello-world.cc -o hello_world -fno-rtti -lv8_monolith \
<TabItem value="win" label="Windows">
```bash
cl /MT /I..\v8\v8\ /I..\v8\v8\include hello-world.cc /GR- v8_monolith.lib Advapi32.lib Winmm.lib Dbghelp.lib /std:c++20 /DV8_COMPRESS_POINTERS=1 /DV8_ENABLE_SANDBOX /link /out:hello_world.exe /LIBPATH:..\v8\v8\out.gn\x64.release.sample\obj\
cl /MT /I..\v8\v8\ /I..\v8\v8\include hello-world.cc /GR- v8_monolith.lib Advapi32.lib Winmm.lib Dbghelp.lib /std:c++17 /DV8_COMPRESS_POINTERS=1 /DV8_ENABLE_SANDBOX /link /out:hello_world.exe /LIBPATH:..\v8\v8\out.gn\x64.release.sample\obj\
.\hello_world.exe
```
@ -1028,7 +974,7 @@ g++ -I. -Iinclude sheetjs.v8.cc -o sheetjs.v8 -fno-rtti -lv8_monolith \
<TabItem value="win" label="Windows">
```bash
cl /MT /I..\v8\v8\ /I..\v8\v8\include sheetjs.v8.cc /GR- v8_monolith.lib Advapi32.lib Winmm.lib Dbghelp.lib /std:c++20 /DV8_COMPRESS_POINTERS=1 /DV8_ENABLE_SANDBOX /link /out:sheetjs.v8.exe /LIBPATH:..\v8\v8\out.gn\x64.release.sample\obj\
cl /MT /I..\v8\v8\ /I..\v8\v8\include sheetjs.v8.cc /GR- v8_monolith.lib Advapi32.lib Winmm.lib Dbghelp.lib /std:c++17 /DV8_COMPRESS_POINTERS=1 /DV8_ENABLE_SANDBOX /link /out:sheetjs.v8.exe /LIBPATH:..\v8\v8\out.gn\x64.release.sample\obj\
```
</TabItem>
@ -1090,7 +1036,7 @@ This demo was last tested in the following deployments:
|:-------------|:----------|:-----------|
| `darwin-x64` | `136.0.0` | 2025-04-21 |
| `darwin-arm` | `134.3.0` | 2025-02-13 |
| `win11-x64` | `137.1.0` | 2025-05-11 |
| `win11-x64` | `130.0.2` | 2024-12-20 |
| `linux-x64` | `130.0.7` | 2025-01-09 |
| `linux-arm` | `134.4.0` | 2025-02-15 |
@ -1124,45 +1070,12 @@ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl -LO https://docs.sheetjs.com/pres.numbers`}
</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
curl.exe -LO https://docs.sheetjs.com/pres.numbers`}
</CodeBlock>
:::
4) Download [`main.rs`](pathname:///v8/main.rs) and replace `src/main.rs`:
```bash
curl -L -o src/main.rs https://docs.sheetjs.com/v8/main.rs
```
:::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 -L -o src/main.rs https://docs.sheetjs.com/v8/main.rs
```
:::
:::info pass
There was a breaking change in version `0.102.0` affecting `v8::Context::new`.
@ -1206,7 +1119,7 @@ This demo was last tested in the following deployments:
|:-------------|:--------------|:--------|:----------|:-----------|
| `darwin-x64` | `13.2.152.16` | `4.1.1` | `24.0.1` | 2025-04-21 |
| `darwin-arm` | `13.2.152.16` | `4.1.1` | `17.0.14` | 2025-03-30 |
| `win11-x64` | `13.2.152.16` | `4.1.1` | `17.0.13` | 2025-05-11 |
| `win11-x64` | `12.6.228.13` | `3.1.3` | `21.0.5` | 2024-12-20 |
| `linux-x64` | `13.2.152.16` | `4.1.1` | `21.0.6` | 2025-04-21 |
| `linux-arm` | `13.2.152.16` | `4.1.1` | `17.0.14` | 2025-02-16 |
@ -1263,27 +1176,9 @@ curl -LO https://repo1.maven.org/maven2/com/caoccao/javet/javet-v8-linux-arm64/4
<TabItem value="win" label="Windows">
```bash
curl -LO https://repo1.maven.org/maven2/com/caoccao/javet/javet/4.1.1/javet-4.1.1.jar
curl -LO https://repo1.maven.org/maven2/com/caoccao/javet/javet-v8-windows-x86_64/4.1.1/javet-v8-windows-x86_64-4.1.1.jar
curl -LO https://repo1.maven.org/maven2/com/caoccao/javet/javet/3.1.3/javet-3.1.3.jar
```
:::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://repo1.maven.org/maven2/com/caoccao/javet/javet/4.1.1/javet-4.1.1.jar
curl.exe -LO https://repo1.maven.org/maven2/com/caoccao/javet/javet-v8-windows-x86_64/4.1.1/javet-v8-windows-x86_64-4.1.1.jar
```
:::
</TabItem>
</Tabs>
@ -1300,45 +1195,12 @@ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl -LO https://docs.sheetjs.com/pres.xlsx`}
</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
curl.exe -LO https://docs.sheetjs.com/pres.xlsx`}
</CodeBlock>
:::
4) Download [`SheetJSJavet.java`](pathname:///v8/SheetJSJavet.java):
```bash
curl -LO https://docs.sheetjs.com/v8/SheetJSJavet.java
```
:::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/v8/SheetJSJavet.java
```
:::
5) Build and run the Java application:
<Tabs groupId="os">
@ -1383,8 +1245,8 @@ java -cp ".:javet-4.1.1.jar:javet-v8-linux-arm64-4.1.1.jar" SheetJSJavet pres.xl
<TabItem value="win" label="Windows">
```bash
javac -cp ".;javet-4.1.1.jar;javet-v8-windows-x86_64-4.1.1.jar" SheetJSJavet.java
java -cp ".;javet-4.1.1.jar;javet-v8-windows-x86_64-4.1.1.jar" SheetJSJavet pres.xlsx
javac -cp ".;javet-3.1.3.jar" SheetJSJavet.java
java -cp ".;javet-3.1.3.jar" SheetJSJavet pres.xlsx
```
</TabItem>
@ -1420,7 +1282,7 @@ This demo was last tested in the following deployments:
|:-------------|:--------------|:-----------|
| `darwin-x64` | `13.3.415.23` | 2025-03-31 |
| `darwin-arm` | `13.3.415.23` | 2025-03-31 |
| `win11-x64` | `13.3.415.23` | 2025-05-11 |
| `win11-x64` | `12.3.219.12` | 2024-12-20 |
| `win11-arm` | `12.3.219.12` | 2025-02-23 |
| `linux-x64` | `12.3.219.12` | 2025-01-10 |
| `linux-arm` | `12.3.219.12` | 2025-02-16 |
@ -1514,23 +1376,6 @@ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl -LO https://docs.sheetjs.com/pres.xlsx`}
</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
curl.exe -LO https://docs.sheetjs.com/pres.xlsx`}
</CodeBlock>
:::
6) Replace `Program.cs` with the following:
```csharp title="Program.cs"
@ -1639,7 +1484,6 @@ This demo was last tested in the following deployments:
|:-------------|:--------------|:---------|:-----------|
| `darwin-x64` | `13.1.201.22` | `3.13.1` | 2025-03-31 |
| `darwin-arm` | `13.1.201.22` | `3.13.2` | 2025-04-24 |
| `win11-x64` | `13.1.201.22` | `3.11.9` | 2025-04-28 |
:::
@ -1704,26 +1548,6 @@ python sheetjs-stpyv8.py pres.xlsx
The script will display CSV rows from the first worksheet. It will also create
`SheetJSSTPyV8.xlsb`, a workbook that can be opened with a spreadsheet editor.
:::caution pass
On Windows, this may fail with a `charmap` error:
```
return codecs.charmap_decode(input,self.errors,decoding_table)[0]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnicodeDecodeError: 'charmap' codec can't decode byte 0x90 in position 380: character maps to <undefined>
```
`sheetjs-stpyv8.py` must be altered to read `xlsx.full.min.js` with mode `rb`:
```python title="sheetjs-stpyv8.py (edit highlighted line)"
# Read xlsx.full.min.js
# highlight-next-line
with open("xlsx.full.min.js", "rb") as f:
```
:::
## Snapshots
At a high level, V8 snapshots are raw dumps of the V8 engine state. It is much
@ -1750,7 +1574,7 @@ This demo was last tested in the following deployments:
|:-------------|:--------------|:----------|:-----------|
| `darwin-x64` | `13.5.212.10` | `136.0.0` | 2025-04-21 |
| `darwin-arm` | `13.5.212.10` | `136.0.0` | 2025-04-24 |
| `win11-x64` | `13.5.212.10` | `136.0.0` | 2025-05-11 |
| `win11-x64` | `12.6.228.3` | `0.92.0` | 2024-12-20 |
| `linux-x64` | `12.6.228.3` | `0.92.0` | 2025-01-02 |
| `linux-arm` | `13.4.114.9` | `134.4.0` | 2025-02-15 |

@ -30,23 +30,22 @@ This demo was tested in the following deployments:
| OpenJDK | Rhino | Date |
|:--------|:---------|:-----------|
| 24.0.1 | `1.7.15` | 2025-05-06 |
| 23.0.2 | `1.7.15` | 2025-05-06 |
| 22.0.2 | `1.7.15` | 2025-05-06 |
| 21.0.7 | `1.7.15` | 2025-05-06 |
| 20.0.2 | `1.7.15` | 2025-05-06 |
| 19.0.2 | `1.7.15` | 2025-05-06 |
| 18.0.2 | `1.7.15` | 2025-05-06 |
| 17.0.15 | `1.7.15` | 2025-05-06 |
| 16.0.1 | `1.7.15` | 2025-05-06 |
| 15.0.10 | `1.7.15` | 2025-05-06 |
| 14.0.2 | `1.7.15` | 2025-05-06 |
| 13.0.2 | `1.7.15` | 2025-05-06 |
| 12.0.1 | `1.7.15` | 2025-05-06 |
| 11.0.27 | `1.7.15` | 2025-05-06 |
| 10 | `1.7.15` | 2025-05-06 |
| 9.0.4 | `1.7.15` | 2025-05-06 |
| 1.8.0 | `1.7.15` | 2025-05-06 |
| 23.0.1 | `1.7.15` | 2025-01-10 |
| 22.0.2 | `1.7.15` | 2025-01-10 |
| 21.0.5 | `1.7.15` | 2025-01-10 |
| 20.0.2 | `1.7.15` | 2025-01-10 |
| 19.0.2 | `1.7.15` | 2025-01-10 |
| 18.0.2 | `1.7.15` | 2025-01-10 |
| 17.0.13 | `1.7.15` | 2025-01-10 |
| 16.0.2 | `1.7.15` | 2025-01-10 |
| 15.0.2 | `1.7.15` | 2025-01-10 |
| 14.0.2 | `1.7.15` | 2025-01-10 |
| 13.0.2 | `1.7.15` | 2025-01-10 |
| 12.0.2 | `1.7.15` | 2025-01-10 |
| 11.0.25 | `1.7.15` | 2025-01-10 |
| 10.0.2 | `1.7.15` | 2025-01-10 |
| 9.0.4 | `1.7.15` | 2025-01-10 |
| 1.8.0 | `1.7.15` | 2025-01-10 |
:::

@ -165,7 +165,7 @@ This demo was tested in the following deployments:
|:-------------|:--------|:-----------|
| `darwin-x64` | `4.2.1` | 2025-03-31 |
| `darwin-arm` | `4.2.0` | 2025-02-13 |
| `win11-x64` | `4.2.2` | 2026-04-28 |
| `win11-x64` | `4.1.0` | 2024-12-20 |
| `win11-arm` | `4.2.0` | 2025-02-23 |
| `linux-x64` | `4.1.0` | 2025-01-09 |
| `linux-arm` | `4.2.0` | 2025-02-15 |
@ -248,7 +248,7 @@ dotnet run
```bash
dotnet nuget add source https://www.myget.org/F/jint/api/v3/index.json
dotnet add package Jint --version 4.2.2
dotnet add package Jint --version 4.2.1
```
To verify Jint is installed, replace `Program.cs` with the following:
@ -283,25 +283,6 @@ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl -LO https://docs.sheetjs.com/pres.xlsx`}
</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">{`\
curl.exe -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js
curl.exe -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl.exe -LO https://docs.sheetjs.com/pres.xlsx`}
</CodeBlock>
:::
6) Replace `Program.cs` with the following:
```csharp title="Program.cs"
@ -518,7 +499,7 @@ cp bin/Release/net*/linux-arm64/publish/SheetJSJint .
For Windows 11 x64, the RID is `win-x64` and the command is:
```powershell
copy .\bin\Release\net*\win-x64\publish\SheetJSJint.exe .
copy .\bin\Release\net9.0\win-x64\publish\SheetJSJint.exe .
```
:::caution pass
@ -532,7 +513,7 @@ The system cannot find the path specified.
The correct command was
```powershell
copy .\bin\x64\Release\net*\win-x64\publish\SheetJSJint.exe .
copy .\bin\x64\Release\net9.0\win-x64\publish\SheetJSJint.exe .
```
:::

@ -107,7 +107,7 @@ This demo was tested in the following deployments:
|:-------------|:-----------|:-----------|:-----------|
| `darwin-x64` | `bcd7cc6` | `1.24.1` | 2025-03-31 |
| `darwin-arm` | `5ef83b8` | `1.24.0` | 2025-02-13 |
| `win11-x64` | `bcd7cc6` | `1.24.2` | 2025-04-28 |
| `win11-x64` | `79f3a7e` | `1.23.4` | 2024-12-20 |
| `win11-arm` | `5ef83b8` | `1.24.0` | 2025-02-23 |
| `linux-x64` | `79f3a7e` | `1.22.0` | 2025-01-02 |
| `linux-arm` | `5ef83b8` | `1.19.8` | 2025-02-15 |
@ -141,48 +141,12 @@ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl -LO https://docs.sheetjs.com/pres.numbers`}
</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">{`\
curl.exe -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js
curl.exe -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl.exe -LO https://docs.sheetjs.com/pres.numbers`}
</CodeBlock>
:::
2) Download [`SheetGoja.go`](pathname:///goja/SheetGoja.go):
```bash
curl -LO https://docs.sheetjs.com/goja/SheetGoja.go
```
:::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:
```bash
curl.exe -LO https://docs.sheetjs.com/goja/SheetGoja.go
```
:::
3) Build the standalone `SheetGoja` binary:
```bash

@ -105,23 +105,22 @@ This demo was tested in the following deployments:
| OpenJDK | Nashorn | Date |
|:--------|:----------------|:-----------|
| 24.0.1 | 15.4 standalone | 2025-05-06 |
| 23.0.2 | 15.4 standalone | 2025-05-06 |
| 22.0.2 | 15.4 standalone | 2025-05-06 |
| 21.0.7 | 15.4 standalone | 2025-05-06 |
| 20.0.2 | 15.4 standalone | 2025-05-06 |
| 19.0.2 | 15.4 standalone | 2025-05-06 |
| 18.0.2 | 15.4 standalone | 2025-05-06 |
| 17.0.15 | 15.4 standalone | 2025-05-06 |
| 16.0.1 | 15.4 standalone | 2025-05-06 |
| 15.0.10 | 15.4 standalone | 2025-05-06 |
| 14.0.2 | Built-in | 2025-05-06 |
| 13.0.2 | Built-in | 2025-05-06 |
| 12.0.1 | Built-in | 2025-05-06 |
| 11.0.27 | Built-in | 2025-05-06 |
| 10 | Built-in | 2025-05-06 |
| 9.0.4 | Built-in | 2025-05-06 |
| 1.8.0 | Built-in | 2025-05-06 |
| 23.0.1 | 15.4 standalone | 2024-12-17 |
| 22.0.2 | 15.4 standalone | 2024-12-17 |
| 21.0.5 | 15.4 standalone | 2024-12-17 |
| 20.0.2 | 15.4 standalone | 2024-12-17 |
| 19.0.2 | 15.4 standalone | 2024-12-17 |
| 18.0.2 | 15.4 standalone | 2024-12-17 |
| 17.0.13 | 15.4 standalone | 2024-12-17 |
| 16.0.1 | 15.4 standalone | 2024-12-17 |
| 15.0.10 | 15.4 standalone | 2024-12-17 |
| 14.0.2 | Built-in | 2024-12-17 |
| 13.0.14 | Built-in | 2024-12-17 |
| 12.0.2 | Built-in | 2024-12-17 |
| 11.0.25 | Built-in | 2024-12-17 |
| 10.0.2 | Built-in | 2024-12-17 |
| 9 | Built-in | 2024-12-17 |
| 1.8.0 | Built-in | 2024-12-17 |
:::

@ -374,33 +374,33 @@ fork, which powers React Native for Windows, does have built-in support[^5]
| Architecture | Git Commit | Date |
|:-------------|:-----------|:-----------|
| `win11-x64` | `254fb48` | 2025-04-28 |
| `win11-x64` | `4c64b05` | 2024-12-20 |
| `win11-arm` | `4c64b05` | 2025-02-23 |
The ["Windows Example"](#windows-example) covers `hermes-windows`.
:::
0) Install [dependencies](https://github.com/facebook/hermes/blob/eda3c083a57e9aa3b5b04df12ef8588b2961c02e/doc/BuildingAndRunning.md#dependencies)
0) Install [dependencies](https://hermesengine.dev/docs/building-and-running/#dependencies)
<details>
<summary><b>Installation Notes</b> (click to show)</summary>
On macOS, the Xcode command-line tools ship with `git`. The other dependencies
should be installed with `brew`:
The official guidance[^6] has been verified in macOS and HoloOS (Linux).
On macOS:
```bash
brew install icu4c cmake ninja
```
On HoloOS (and other Arch Linux distros), the dependencies must be installed
from the root user (using `sudo`):
On HoloOS (and other Arch Linux distros):
```bash
sudo pacman -Syu cmake git ninja icu python zip readline
```
On Debian and Ubuntu, `python-is-python3` should be installed:
On Debian and Ubuntu:
```bash
sudo apt install cmake git ninja-build libicu-dev python3 python-is-python3 zip libreadline-dev
@ -408,7 +408,7 @@ sudo apt install cmake git ninja-build libicu-dev python3 python-is-python3 zip
:::note pass
Linux builds require at least 8 GB memory.
When using virtual machines, Linux builds require at least 8 GB memory.
:::
@ -690,7 +690,7 @@ cd sheetjs-hermes
```bash
git clone https://github.com/microsoft/hermes-windows
cd hermes-windows
git checkout 254fb48
git checkout 4c64b05
cd ..
```
@ -849,7 +849,7 @@ This demo was tested in the following deployments:
|:-------------|:---------|:-----------|
| `darwin-x64` | `0.13.0` | 2025-03-31 |
| `darwin-arm` | `0.13.0` | 2025-04-23 |
| `win11-x64` | `0.13.0` | 2025-04-28 |
| `win11-x64` | `0.13.0` | 2024-12-20 |
| `win11-arm` | `0.13.0` | 2025-02-23 |
| `linux-x64` | `0.13.0` | 2025-04-21 |
@ -978,5 +978,6 @@ If successful, the script will print CSV data from the test file.
[^3]: See ["Workbook Object"](/docs/csf/book)
[^4]: See [`sheet_to_csv` in "Utilities"](/docs/api/utilities/csv#csv-output)
[^5]: See [`microsoft/hermes-windows`](https://github.com/microsoft/hermes-windows) on GitHub
[^6]: See ["Dependencies" in "Building and Running"](https://hermesengine.dev/docs/building-and-running/#dependencies) in the Hermes Documentation. If this page redirects to the source repo, [see the following `archive.org` snapshot.](https://web.archive.org/web/20240103234151/http://hermesengine.dev/docs/building-and-running/)
[^7]: See ["Download Python"](https://www.python.org/downloads/) in the Python website. When the demo was last tested, Python 3.11.9 was installed.
[^8]: See [the Visual Studio website](https://visualstudio.microsoft.com/#vs-section) for download links.

@ -89,7 +89,7 @@ This demo was tested in the following deployments:
|:-------------|:---------|:---------|:-----------|
| `darwin-x64` | `2.6.10` | `2.10.0` | 2025-03-31 |
| `darwin-arm` | `2.6.10` | `2.10.0` | 2025-02-13 |
| `win11-x64` | `3.3.8` | `2.10.0` | 2025-04-28 |
| `win11-x64` | `3.3.6` | `2.10.0` | 2024-12-20 |
| `win11-arm` | `3.2.3` | `2.10.0` | 2025-02-23 |
| `linux-x64` | `3.2.3` | `2.10.0` | 2025-04-21 |
| `linux-arm` | `3.1.2` | `2.10.0` | 2025-02-15 |
@ -157,47 +157,12 @@ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl -LO https://docs.sheetjs.com/pres.numbers`}
</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">{`\
curl.exe -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl.exe -LO https://docs.sheetjs.com/pres.numbers`}
</CodeBlock>
:::
3) Download [`ExecSheetJS.rb`](pathname:///execjs/ExecSheetJS.rb):
```bash
curl -LO https://docs.sheetjs.com/execjs/ExecSheetJS.rb
```
:::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:
```bash
curl.exe -LO https://docs.sheetjs.com/execjs/ExecSheetJS.rb
```
:::
4) Run the demo:
```bash

@ -134,7 +134,7 @@ This demo was tested in the following deployments:
|:-------------|:-----------|:-----------|
| `darwin-x64` | `36becec` | 2025-03-31 |
| `darwin-arm` | `e26c81f` | 2025-01-13 |
| `win11-x64` | `36becec` | 2025-04-28 |
| `win11-x64` | `e26c81f` | 2024-12-19 |
| `win11-arm` | `e26c81f` | 2025-02-23 |
| `linux-x64` | `e26c81f` | 2025-01-09 |
@ -575,7 +575,7 @@ If successful, the program will print the contents of the first sheet as CSV.
:::note Tested Deployments
This demo was last tested on 2025-04-28 against `ch` commit `36becec`.
This demo was last tested on 2025-03-31 against `ch` commit `36becec`.
:::

@ -106,7 +106,7 @@ This demo was tested in the following deployments:
|:-------------|:---------|:-----------|
| `darwin-x64` | `0.20.0` | 2025-03-31 |
| `darwin-arm` | `0.20.0` | 2025-02-13 |
| `win11-x64` | `0.20.0` | 2025-04-28 |
| `win11-x64` | `0.20.0` | 2024-12-19 |
| `win11-arm` | `0.20.0` | 2025-02-23 |
| `linux-x64` | `0.20.0` | 2025-04-21 |
| `linux-arm` | `0.20.0` | 2025-02-15 |
@ -152,47 +152,12 @@ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl -LO https://docs.sheetjs.com/pres.xlsx`}
</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">{`\
curl.exe -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
curl.exe -LO https://docs.sheetjs.com/pres.xlsx`}
</CodeBlock>
:::
4) Download [`main.rs`](pathname:///boa/main.rs) and replace `src/main.rs`:
```bash
curl -L -o src/main.rs https://docs.sheetjs.com/boa/main.rs
```
:::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:
```bash
curl.exe -L -o src/main.rs https://docs.sheetjs.com/boa/main.rs
```
:::
5) Build and run the app in release mode:
```bash

@ -54,14 +54,13 @@ This demo was tested in the following deployments:
| OpenJDK | GraalJS | Date |
|:--------|:--------|:-----------|
| 24.0.1 | 24.2.1 | 2025-05-06 |
| 23.0.2 | 24.2.1 | 2025-05-06 |
| 22.0.2 | 24.2.1 | 2025-05-06 |
| 21.0.7 | 24.2.1 | 2025-05-06 |
| 20.0.2 | 24.2.1 | 2025-05-06 |
| 19.0.2 | 24.2.1 | 2025-05-06 |
| 18.0.2 | 24.2.1 | 2025-05-06 |
| 17.0.15 | 24.2.1 | 2025-05-06 |
| 23.0.1 | 24.1.1 | 2024-12-17 |
| 22.0.2 | 24.1.1 | 2024-12-17 |
| 21.0.5 | 24.1.1 | 2024-12-17 |
| 20.0.2 | 24.1.1 | 2024-12-17 |
| 19.0.2 | 24.1.1 | 2024-12-17 |
| 18.0.2 | 24.1.1 | 2024-12-17 |
| 17.0.13 | 24.1.1 | 2024-12-17 |
:::
@ -70,14 +69,14 @@ This demo was tested in the following deployments:
0) Download GraalJS and its dependencies:
```bash
curl -LO "https://repo1.maven.org/maven2/org/graalvm/js/js-scriptengine/24.2.1/js-scriptengine-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/js/js-language/24.2.1/js-language-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/polyglot/polyglot/24.2.1/polyglot-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/sdk/collections/24.2.1/collections-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/truffle/truffle-api/24.2.1/truffle-api-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/sdk/nativeimage/24.2.1/nativeimage-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/shadowed/icu4j/24.2.1/icu4j-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/regex/regex/24.2.1/regex-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/js/js-scriptengine/24.1.1/js-scriptengine-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/js/js-language/24.1.1/js-language-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/polyglot/polyglot/24.1.1/polyglot-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/sdk/collections/24.1.1/collections-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/truffle/truffle-api/24.1.1/truffle-api-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/sdk/nativeimage/24.1.1/nativeimage-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/shadowed/icu4j/24.1.1/icu4j-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/regex/regex/24.1.1/regex-24.1.1.jar"
```
1) Download the SheetJS Standalone script, shim script and test file. Move all
@ -118,14 +117,14 @@ CSV rows from the first worksheet.
<TabItem value="unix" label="Linux/MacOS">
```bash
java -cp ".:js-scriptengine-24.2.1.jar:js-language-24.2.1.jar:polyglot-24.2.1.jar:collections-24.2.1.jar:truffle-api-24.2.1.jar:nativeimage-24.2.1.jar:icu4j-24.2.1.jar:regex-24.2.1.jar" -Dpolyglot.js.nashorn-compat=true SheetJSNashorn pres.xlsx
java -cp ".:js-scriptengine-24.1.1.jar:js-language-24.1.1.jar:polyglot-24.1.1.jar:collections-24.1.1.jar:truffle-api-24.1.1.jar:nativeimage-24.1.1.jar:icu4j-24.1.1.jar:regex-24.1.1.jar" -Dpolyglot.js.nashorn-compat=true SheetJSNashorn pres.xlsx
```
</TabItem>
<TabItem value="win" label="Windows">
```bash
java -cp ".;js-scriptengine-24.2.1.jar;js-language-24.2.1.jar;polyglot-24.2.1.jar;collections-24.2.1.jar;truffle-api-24.2.1.jar;nativeimage-24.2.1.jar;icu4j-24.2.1.jar;regex-24.2.1.jar" -D"polyglot.js.nashorn-compat=true" SheetJSNashorn pres.xlsx
java -cp ".;js-scriptengine-24.1.1.jar;js-language-24.1.1.jar;polyglot-24.1.1.jar;collections-24.1.1.jar;truffle-api-24.1.1.jar;nativeimage-24.1.1.jar;icu4j-24.1.1.jar;regex-24.1.1.jar" -D"polyglot.js.nashorn-compat=true" SheetJSNashorn pres.xlsx
```
</TabItem>
@ -155,14 +154,14 @@ cd sheethorn
<TabItem value="unix" label="Linux/MacOS">
```bash
java -cp ".:js-scriptengine-24.2.1.jar:js-language-24.2.1.jar:polyglot-24.2.1.jar:collections-24.2.1.jar:truffle-api-24.2.1.jar:nativeimage-24.2.1.jar:icu4j-24.2.1.jar:regex-24.2.1.jar:SheetJSNashorn.jar" -Dpolyglot.js.nashorn-compat=true SheetJSNashorn pres.xlsx
java -cp ".:js-scriptengine-24.1.1.jar:js-language-24.1.1.jar:polyglot-24.1.1.jar:collections-24.1.1.jar:truffle-api-24.1.1.jar:nativeimage-24.1.1.jar:icu4j-24.1.1.jar:regex-24.1.1.jar:SheetJSNashorn.jar" -Dpolyglot.js.nashorn-compat=true SheetJSNashorn pres.xlsx
```
</TabItem>
<TabItem value="win" label="Windows">
```bash
java -cp ".;js-scriptengine-24.2.1.jar;js-language-24.2.1.jar;polyglot-24.2.1.jar;collections-24.2.1.jar;truffle-api-24.2.1.jar;nativeimage-24.2.1.jar;icu4j-24.2.1.jar;regex-24.2.1.jar;SheetJSNashorn.jar" -D"polyglot.js.nashorn-compat=true" SheetJSNashorn pres.xlsx
java -cp ".;js-scriptengine-24.1.1.jar;js-language-24.1.1.jar;polyglot-24.1.1.jar;collections-24.1.1.jar;truffle-api-24.1.1.jar;nativeimage-24.1.1.jar;icu4j-24.1.1.jar;regex-24.1.1.jar;SheetJSNashorn.jar" -D"polyglot.js.nashorn-compat=true" SheetJSNashorn pres.xlsx
```
</TabItem>

@ -1,4 +1,4 @@
{
"label": "JavaScript Engines",
"label": "Other Languages",
"position": 42
}

@ -39,7 +39,7 @@ It is strongly recommended to create a separate page listing all embedded open
source software. The "open source disclosure" should be linked from relevant
pages including Terms of Service (ToS) and End User License Agreement (EULA)
[Office 365](https://web.archive.org/web/20250511192103/https://tasks.office.com/license.html)
[Office 365](https://web.archive.org/web/20240412032204/https://tasks.office.com/license.html)
includes the short-form attribution in a special license page.
<details>

@ -367,7 +367,7 @@ This,is,a,Test
The test suite is regularly run against a number of modern and legacy browsers
using [Sauce Labs](https://saucelabs.com/).
The following chart shows test results on 2025-05-15 for version `0.20.3`:
The following chart shows test results on 2024-10-20 for version `0.20.3`:
[![Build Status](pathname:///test/sheetjs.svg)](https://saucelabs.com/u/sheetjs)

@ -15,10 +15,6 @@
},
"overrides": {
"@cmfcmf/docusaurus-search-local": {
"@algolia/autocomplete-theme-classic": "1.19.1",
"@algolia/autocomplete-js": "1.19.1",
"@algolia/client-search": "5.25.0",
"algoliasearch": "5.25.0",
"@docusaurus/core": "3.7.0"
}
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 32 KiB

@ -1,37 +1,43 @@
<!DOCTYPE html>
<!-- sheetjs (C) 2013-present SheetJS https://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https:">
<meta name="robots" content="noindex">
<title>SheetJS Electron Demo</title>
<style>
#drop{
border:2px dashed #bbb;
-moz-border-radius:5px;
-webkit-border-radius:5px;
border-radius:5px;
padding:25px;
text-align:center;
font:20pt bold,"Vollkorn";color:#bbb
}
a { text-decoration: none }
</style>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<pre>
<b><a href="https://sheetjs.com">SheetJS Electron Demo</a></b>
<br />
<button id="readBtn">Click here to select a file from your computer</button><br />
<div id="drop">Drop a spreadsheet file here to see sheet data</div>
<input type="file" name="xlfile" id="readIn" /> ... or click here to select a file
</pre>
<p><input type="submit" value="Export Data!" id="exportBtn" disabled="true"></p>
<div id="htmlout"></div>
<br />
<div id="spinner-overlay" class="spinner-overlay" style="display:none;"><div class="spinner"></div></div>
<header>
<h1 class="text-heading">SheetJS Electron Demo</h1>
<p class="text-muted text-condensed">Load a spreadsheet to view its contents</p>
</header>
<section class="file-upload" id="drop-container">
<div id="drop">
<input type="file" id="readIn" style="display:none" tabindex="0" aria-label="Select spreadsheet file">
<p class="text-muted text-condensed">Drag and drop a file here</p>
<p class="text-muted text-small">or</p>
<button type="button" id="readBtn" tabindex="0" aria-label="Open file picker">Select a file</button>
</div>
</section>
<div id="fileStatus" class="file-status"></div>
<div id="onError"></div>
<section id="htmlout" class="table-responsive"></section>
<section class="export">
<p><input type="submit" value="Export" id="exportBtn" disabled="true" tabindex="0" aria-label="Export spreadsheet"></p>
</section>
</body>
<footer>
<ul>
<li><a href="https://docs.sheetjs.com/docs/" class="text-condensed"><img src="https://git.sheetjs.com/assets/img/logo.svg" alt="SheetJS" width="20" height="20"> SheetJS CE Docs</a></li>
<li><a href="https://www.electronjs.org/docs" class="text-condensed"><img src="https://www.electronjs.org/assets/img/logo.svg" alt="Electron" width="20" height="20"> Electron Docs</a></li>
</ul>
</footer>
<script src="index.js"></script>
</body>
</html>

@ -1,73 +1,232 @@
/* sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */
const XLSX = require('xlsx');
const electron = require('@electron/remote');
const XLSX = window.SheetJSDemoAPI.xlsx;
const basename = window.SheetJSDemoAPI.basename;
const extname = window.SheetJSDemoAPI.extname;
const onFileOpened = window.SheetJSDemoAPI.onFileOpened;
/* list of supported extensions */
const EXTENSIONS = "xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html|numbers".split("|");
// ---------------------------------------------------------------------------
// Supported file extensions
// ---------------------------------------------------------------------------
const EXTENSIONS =
"xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html|numbers".split(
"|"
);
/* write file with Electron API */
async function exportFile() {
const HTMLOUT = document.getElementById('htmlout');
const wb = XLSX.utils.table_to_book(HTMLOUT.getElementsByTagName("TABLE")[0]);
const o = await electron.dialog.showSaveDialog({
title: 'Save file as',
filters: [{
// ---------------------------------------------------------------------------
// DOM references
// ---------------------------------------------------------------------------
const dropContainer = document.getElementById("drop-container");
const fileStatus = document.getElementById("fileStatus");
const exportBtn = document.getElementById("exportBtn");
const spinnerOverlay = document.getElementById("spinner-overlay");
const htmlout = document.getElementById("htmlout");
const onError = document.getElementById("onError");
// ---------------------------------------------------------------------------
// State & helpers
// ---------------------------------------------------------------------------
let currentWorkbook = null; // SheetJS workbook in memory
const isSpreadsheet = (ext) => EXTENSIONS.includes(ext.toLowerCase());
const nextPaint = () => new Promise(requestAnimationFrame);
// ---------------------------------------------------------------------------
// Open external links in default browser (security)
// ---------------------------------------------------------------------------
document.addEventListener("click", (e) => {
if (e.target.tagName === "A" && e.target.href.startsWith("http")) {
e.preventDefault();
window.SheetJSDemoAPI.openExternal(e.target.href);
}
});
// ---------------------------------------------------------------------------
// Export logic uses cached workbook (no DOM traversal)
// ---------------------------------------------------------------------------
async function exportWorkbookAsFile() {
if (!currentWorkbook) return displayError("No workbook loaded!");
// -- 1. use electron save as dialog to get file path
const { filePath, canceled } = await window.SheetJSDemoAPI.saveFile([
{
name: "Spreadsheets",
extensions: EXTENSIONS
}]
});
XLSX.writeFile(wb, o.filePath);
electron.dialog.showMessageBox({ message: "Exported data to " + o.filePath, buttons: ["OK"] });
extensions: EXTENSIONS,
},
]);
if (canceled || !filePath) return;
// -- 2. write workbook to file
try {
XLSX.writeFile(currentWorkbook, filePath);
window.SheetJSDemoAPI.message(`Exported to ${filePath}`);
} catch (err) {
displayError(`Failed to export: ${err.message}`);
}
}
document.getElementById('exportBtn').addEventListener('click', exportFile, false);
exportBtn.addEventListener("click", exportWorkbookAsFile);
// ---------------------------------------------------------------------------
// Render workbook --> HTML tables
// ---------------------------------------------------------------------------
function renderWorkbookToTables(wb) {
// -- 1. map through each sheet
const html = wb.SheetNames.map((name) => {
const sheet = wb.Sheets[name];
// -- 2. convert sheet to HTML
const table = XLSX.utils.sheet_to_html(sheet, { id: `${name}-tbl` });
return `<details class="sheetjs-sheet-container">
<summary class="sheetjs-sheet-name">${name}</summary>
<div class="sheetjs-tab-content">${table}</div>
</details>`;
}).join("");
// CAUTION!: in production environments please sanitize the HTML output to prevent XSS attacks from maliciously crafted spreadsheets.
htmlout.innerHTML = html; // single write → single reflow of the DOM
/* common handler to create HTML tables on the page */
function process_wb(wb) {
const HTMLOUT = document.getElementById('htmlout');
const XPORT = document.getElementById('exportBtn');
XPORT.disabled = false;
HTMLOUT.innerHTML = "";
wb.SheetNames.forEach(function(sheetName) {
const htmlstr = XLSX.utils.sheet_to_html(wb.Sheets[sheetName],{editable:true});
HTMLOUT.innerHTML += htmlstr;
});
}
/* read file with Electron API */
// ---------------------------------------------------------------------------
// Generic UI helpers
// ---------------------------------------------------------------------------
const displayError = (msg) =>
onError
? ((onError.textContent = msg), (onError.hidden = false))
: console.error(msg);
const hideDropUI = () =>
dropContainer && (dropContainer.style.display = "none");
const showDropUI = () =>
dropContainer && (dropContainer.style.display = "block");
const hideExportBtn = () => (exportBtn.disabled = true);
const showExportBtn = () => (exportBtn.disabled = false);
const showSpinner = () => (spinnerOverlay.style.display = "flex");
const hideSpinner = () => (spinnerOverlay.style.display = "none");
const hideOutputUI = () => (htmlout.innerHTML = "");
const hideLoadedFileUI = () => (fileStatus.innerHTML = "");
const getLoadedFileUI = (fileName) => `<div class="file-loaded">
<span class="file-name text-muted text-small">${fileName}</span>
<button type="button" class="unload-btn">Unload</button>
</div>`;
function showLoadedFileUI(fileName) {
fileStatus.innerHTML = getLoadedFileUI(fileName);
hideDropUI();
showExportBtn();
}
// ---------------------------------------------------------------------------
// Event delegation for unload button avoids perrender listener leaks
// ---------------------------------------------------------------------------
fileStatus.addEventListener("click", (e) => {
if (e.target.classList.contains("unload-btn")) {
hideLoadedFileUI();
hideExportBtn();
showDropUI();
hideOutputUI();
currentWorkbook = null;
}
});
// ---------------------------------------------------------------------------
// Fileopen dialog handler
// ---------------------------------------------------------------------------
async function handleReadBtn() {
const o = await electron.dialog.showOpenDialog({
title: 'Select a file',
filters: [{
// -- 1. show file open dialog to get the file path
const { filePaths, canceled } = await window.SheetJSDemoAPI.openFile([
{
name: "Spreadsheets",
extensions: EXTENSIONS
}],
properties: ['openFile']
extensions: EXTENSIONS,
},
]);
if (canceled || !filePaths.length) return;
if (filePaths.length !== 1)
return displayError("Please choose a single file.");
showSpinner();
await nextPaint(); // ensure spinner paints
try {
const filePath = filePaths[0];
// -- 2. read the first selected file
currentWorkbook = XLSX.readFile(filePath);
renderWorkbookToTables(currentWorkbook);
showLoadedFileUI(basename(filePath));
} finally {
hideSpinner();
hideDropUI();
onError && (onError.hidden = true);
}
}
// ---------------------------------------------------------------------------
// Draganddrop + file input
// ---------------------------------------------------------------------------
function addListener(id, evt, fn) {
const el = document.getElementById(id);
if (el) el.addEventListener(evt, fn);
}
function attachFileListeners() {
// file input element
addListener("readIn", "change", (e) => {
showSpinner();
nextPaint().then(() => readFile(e.target.files));
});
if(o.filePaths.length == 0) throw new Error("No file was selected!");
process_wb(XLSX.readFile(o.filePaths[0]));
}
document.getElementById('readBtn').addEventListener('click', handleReadBtn, false);
addListener("readBtn", "click", handleReadBtn);
/* read file with Web APIs */
// draganddrop (applied to whole window for simplicity)
const onDrag = (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = "copy";
};
["dragenter", "dragover"].forEach((t) =>
document.body.addEventListener(t, onDrag, { passive: false })
);
document.body.addEventListener(
"drop",
(e) => {
e.preventDefault();
readFile(e.dataTransfer.files).catch((err) => displayError(err.message));
},
{ passive: false }
);
}
// ---------------------------------------------------------------------------
// Read File from input or DnD
// ---------------------------------------------------------------------------
async function readFile(files) {
const f = files[0];
const data = await f.arrayBuffer();
process_wb(XLSX.read(data));
// -- 1. if no files, return
if (!files || !files.length) return;
// -- 2. get the first file
const file = files[0];
// -- 3. if not a spreadsheet, return error
const ext = extname(file.name).slice(1);
if (!isSpreadsheet(ext)) return displayError(`Unsupported file type .${ext}`);
showSpinner();
try {
// -- 4. read the file
const data = await file.arrayBuffer();
currentWorkbook = XLSX.read(data);
// -- 5. render the workbook to tables
renderWorkbookToTables(currentWorkbook);
// -- 6. show the loaded file UI
showLoadedFileUI(file.name);
} finally {
hideSpinner();
// reset error UI state
onError && (onError.hidden = true);
}
}
// file input element
document.getElementById('readIn').addEventListener('change', (e) => { readFile(e.target.files); }, false);
// drag and drop
const drop = document.getElementById('drop');
drop.addEventListener('drop', (e) => {
e.stopPropagation(); e.preventDefault();
readFile(e.dataTransfer.files);
}, false);
const handleDrag = (e) => {
e.stopPropagation(); e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
};
drop.addEventListener('dragenter', handleDrag, false);
drop.addEventListener('dragover', handleDrag, false);
// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------
attachFileListeners();
// the file-opened event is sent from the main process when a file is opened using "open with"
onFileOpened(async (_e, filePath) => {
showSpinner();
await nextPaint(); // ensure spinner paints
currentWorkbook = XLSX.readFile(filePath);
renderWorkbookToTables(currentWorkbook);
showLoadedFileUI(path.basename(filePath));
hideSpinner();
hideDropUI();
showExportBtn();
});

@ -1,29 +1,120 @@
/* sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */
var electron = require('electron');
var XLSX = require('xlsx');
var app = electron.app;
require('@electron/remote/main').initialize(); // required for Electron 14+
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const path = require('path');
const XLSX = require('xlsx');
var win = null;
const EXT_REGEX = /\.(xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html|numbers)$/i;
const pendingPaths = []; // paths of files that were opened before the window was created. These are queued and loaded once the window is created.
let win = null; // reference to the main window, to make sure we don't create multiple windows.
/* In electron, the main process is the only process that can directly interface with the operating system.
The renderer process is sandboxed and cannot run any non-browser code.
To allow the renderer process to interface with the operating system, we use the contextBridge API to expose the API to the renderer process.
https://www.electronjs.org/docs/latest/api/context-bridge
*/
/* ----------------------------------------------------------------------------- */
/* IPC handlers that allow communication between main and renderer processes */
/* ----------------------------------------------------------------------------- */
/* These three functions can be used to interface with the operating system from the renderer process.
the ipcMain.handle() function is used to register a handler for a specific event.
when the renderer process calls the corresponding function, the main process will receive the event and execute the handler.
In this case, we are listening to events which allow the renderer process to open/save file dialogs and show modal messages.
*/
ipcMain.handle('dialog:openFile', (_e, filters) =>
dialog.showOpenDialog({ title: 'Select a file', filters, properties: ['openFile'] })
);
ipcMain.handle('dialog:saveFile', (_e, filters) =>
dialog.showSaveDialog({ title: 'Save file as', filters })
);
ipcMain.handle('dialog:message', (_e, msg) =>
dialog.showMessageBox({ message: msg, buttons: ['OK'] })
);
/* ----------------------------------------------------------------------------- */
/* Utility functions */
/* ----------------------------------------------------------------------------- */
function sendToRenderer(fp) {
if (win && win.webContents) win.webContents.send('file-opened', fp);
else pendingPaths.push(fp);
}
/*
On Windows and Linux, opening a file using the "open with" menu option or `open` command will pass the file path as a startup argument to the app.
We need to parse it, and test if it is a spreadsheet file.
*/
function firstSpreadsheetFromArgv() {
const args = process.defaultApp ? process.argv.slice(2) : process.argv.slice(1);
return args.find((a) => EXT_REGEX.test(a));
}
/* ----------------------------------------------------------------------------- */
/* Single-instance guard */
/* ----------------------------------------------------------------------------- */
// Windows and Linux only: If the app is already running, we need to prevent a new instance from launching when opening a file via the "open with" menu option or `open` command.
if (!app.requestSingleInstanceLock()) app.quit();
else {
app.on('second-instance', (_e, argv) => {
const fp = argv.find((a) => EXT_REGEX.test(a));
if (fp) sendToRenderer(fp);
if (win) { win.show(); win.focus(); }
});
}
// macOS file / url events
app.on('open-file', (evt, fp) => { evt.preventDefault(); sendToRenderer(fp); });
app.on('open-url', (evt, url) => { evt.preventDefault(); sendToRenderer(url.replace('file://', '')); });
/* ----------------------------------------------------------------------------- */
/* Create the window */
/* ----------------------------------------------------------------------------- */
function createWindow() {
if (win) return;
win = new electron.BrowserWindow({
width: 800, height: 600,
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
worldSafeExecuteJavaScript: true, // required for Electron 12+
contextIsolation: false, // required for Electron 12+
nodeIntegration: true,
enableRemoteModule: true
preload: path.join(__dirname, './preload.js'), // preload script that will be executed in the renderer process before the page is loaded and act as a bridge between the main and renderer processes within a worker thread.
contextIsolation: true, // isolate and enable bridge, keeping the renderer process sandboxed and separated from the main process.
nodeIntegration: false, // no Node.js in renderer process.
nodeIntegrationInWorker: true, // enable Node.js in worker threads.
worldSafeExecuteJavaScript: true
}
});
win.loadURL("file://" + __dirname + "/index.html");
require('@electron/remote/main').enable(win.webContents); // required for Electron 14+
win.webContents.openDevTools();
win.on('closed', function () { win = null; });
win.loadFile('index.html');
if (process.env.NODE_ENV === 'development') win.webContents.openDevTools();
win.on('closed', () => { win = null; });
win.webContents.once('did-finish-load', () => {
pendingPaths.splice(0).forEach(sendToRenderer);
});
}
if (app.setAboutPanelOptions) app.setAboutPanelOptions({ applicationName: 'sheetjs-electron', applicationVersion: "XLSX " + XLSX.version, copyright: "(C) 2017-present SheetJS LLC" });
app.on('open-file', function () { console.log(arguments); });
app.on('ready', createWindow);
/* ----------------------------------------------------------------------------- */
/* App lifecycle */
/* ----------------------------------------------------------------------------- */
app.whenReady().then(() => {
const fp = firstSpreadsheetFromArgv();
if (fp) pendingPaths.push(fp);
createWindow();
});
if (app.setAboutPanelOptions) {
app.setAboutPanelOptions({
applicationName: 'sheetjs-electron',
applicationVersion: `XLSX ${XLSX.version}`,
copyright: '(C) 2017present SheetJS LLC'
});
}
app.on('activate', createWindow);
app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit(); });
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });

@ -6,13 +6,13 @@
"version": "0.0.0",
"main": "main.js",
"dependencies": {
"@electron/remote": "2.1.2",
"xlsx": "https://sheet.lol/balls/xlsx-0.20.3.tgz"
},
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make"
"make": "electron-forge make",
"dist": "electron-builder"
},
"devDependencies": {
"@electron-forge/cli": "7.8.0",
@ -20,8 +20,28 @@
"@electron-forge/maker-rpm": "7.8.0",
"@electron-forge/maker-squirrel": "7.8.0",
"@electron-forge/maker-zip": "7.8.0",
"electron": "35.1.2"
"electron": "36.1.0",
"electron-builder": "^26.0.12"
},
"build": {
"appId": "com.sheetjs.electron",
"fileAssociations": [
{
"ext": [
"xls","xlsx","xlsm","xlsb","xml","csv","txt","dif",
"sylk","slk","prn","ods","fods","htm","html","numbers"
],
"name": "Spreadsheet / Delimited File",
"description": "Spreadsheets and delimited text files opened by SheetJS-Electron",
"role": "Editor"
}
],
"mac": { "target": "dmg" },
"win": { "target": "nsis" },
"linux": { "target": "deb" }
},
"config": {
"forge": {
"packagerConfig": {},
@ -49,4 +69,4 @@
]
}
}
}
}

@ -0,0 +1,20 @@
const { contextBridge, ipcRenderer, shell } = require('electron');
const path = require('path');
const XLSX = require('xlsx');
// Because the main process is sandboxed, we need to use the contextBridge API to expose the API to the renderer process.
// https://www.electronjs.org/docs/latest/api/context-bridge
contextBridge.exposeInMainWorld('SheetJSDemoAPI', {
openFile: (filters) => ipcRenderer.invoke('dialog:openFile', filters),
saveFile: (filters) => ipcRenderer.invoke('dialog:saveFile', filters),
message: (msg) => ipcRenderer.invoke('dialog:message', msg),
openExternal: (url) => shell.openExternal(url),
// expose file-opened event
onFileOpened: (cb) => ipcRenderer.on('file-opened', (_e, fp) => cb(fp)),
// expose basename from path package
basename: (p) => path.basename(p),
// expose extname from path package
extname: (p) => path.extname(p),
// expose sheetjs package functions
xlsx: XLSX,
});

@ -0,0 +1,363 @@
/* =====================
Root Variables
===================== */
:root {
--text-base: #212529;
--text-muted: #666363;
--text-accent: #0c9244;
--button-primary: #212529;
--button-text: #fff;
--button-primary-hover: #0c9244;
--button-primary-active: #075025;
--button-primary-disabled: #6c757d;
--white: #fff;
--black: #000;
--table-even: #f6f6f6;
--table-hover: #88dda98f;
--table-head: #f8f9faaf;
--danger: #c0392b;
}
/* =====================
Global Styles & Reset
===================== */
body, html {
width: 100%;
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
width: 100%;
margin: 0;
box-sizing: border-box;
font-family: "Roboto", sans-serif;
font-optical-sizing: auto;
font-style: normal;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
}
li {
list-style: none;
}
/* =====================
Typography
===================== */
h1, p {
margin: 0;
color: var(--text-base);
text-align: center;
}
.text-muted {
color: var(--text-muted) !important;
}
.text-condensed {
font-family: "Roboto Condensed", sans-serif;
font-optical-sizing: auto;
font-style: normal;
}
.text-small {
font-size: 0.875rem !important;
}
/* =====================
Links
===================== */
a {
text-decoration: none;
color: var(--text-base);
}
a:hover {
text-decoration: underline;
color: var(--text-accent);
}
/* =====================
Header
===================== */
header {
margin-top: 2rem;
}
/* =====================
Buttons & Inputs
===================== */
button, input[type="submit"] {
background-color: var(--button-primary);
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease;
color: var(--button-text);
}
button:hover, input[type="submit"]:hover {
background-color: var(--button-primary-hover);
}
button:active, input[type="submit"]:active {
background-color: var(--button-primary-active);
}
button:disabled, input[type="submit"]:disabled {
background-color: var(--button-primary-disabled);
cursor: not-allowed;
opacity: 0.3;
}
input[type="file"] {
display: none;
}
button:focus, input[type="submit"]:focus, input[type="file"]:focus {
outline: 3px solid var(--text-accent);
outline-offset: 2px;
box-shadow: 0 0 0 2px var(--text-accent);
z-index: 2;
}
/* =====================
File Upload
===================== */
.file-upload {
display: flex;
margin: 1rem auto;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1rem;
width: 75%;
max-width: 600px;
border-radius: 4px;
background-color: var(--white);
}
.file-upload input[type="file"] {
display: none;
}
#drop {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
/* =====================
Export Section
===================== */
.export {
margin: 2rem 0;
flex-grow: 1;
}
/* =====================
Footer
===================== */
footer {
padding: 1rem;
display: flex;
justify-content: center;
align-items: center;
position: relative;
bottom: 0;
}
footer ul {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
width: 100%;
}
footer li {
list-style: none;
}
footer li a {
display: flex;
align-items: center;
gap: 0.5rem;
}
/* =====================
Responsive Table Container
===================== */
.table-responsive {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* =====================
SheetJS Table Styles
===================== */
table {
width: 80%;
padding: 1rem;
max-width: 900px;
border-collapse: collapse;
font-family: "Roboto", "Roboto Condensed", sans-serif;
overflow: hidden;
}
table caption {
caption-side: top;
font-family: "Roboto Condensed", sans-serif;
font-size: 1.2rem;
color: var(--text-accent);
padding: 0.5rem;
letter-spacing: 0.05em;
}
table thead {
background: var(--table-head);
}
table th, table td {
padding: 0.25rem 1rem;
border: 1px solid var(--text-muted);
text-align: left;
font-size: 1rem;
}
th {
color: var(--text-base);
font-family: "Roboto Condensed", sans-serif;
font-weight: 600;
letter-spacing: 0.03em;
}
table tbody tr:nth-child(even) {
background: var(--table-even);
}
table tbody tr:hover {
background: var(--table-hover);
}
.sheetjs-sheet-name {
font-family: "Roboto Condensed", sans-serif;
padding: 0.5rem;
font-weight: 600;
}
.sheetjs-sheet-container {
margin: 1rem auto;
width: 90%;
max-width: 900px;
cursor: pointer;
background-color: #eee;
border-radius: 4px;
}
details:focus-within {
outline: 3px solid var(--text-accent);
outline-offset: 2px;
box-shadow: 0 0 0 2px var(--text-accent);
}
a:focus {
outline: 3px solid var(--text-accent);
outline-offset: 2px;
box-shadow: 0 0 0 2px var(--text-accent);
}
summary:focus-within {
outline: none;
}
.sheetjs-tab-content {
cursor: pointer;
padding: 1rem;
overflow-x: auto;
}
/* =====================
File Status/Loaded/Unload
===================== */
.file-status {
margin-top: 10px;
font-size: 1rem;
min-height: 1.5em;
transition: color 0.2s;
}
.file-loaded {
display: flex;
gap: 1rem;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
}
.unload-btn:hover {
background: var(--danger);
color: var(--white);
}
/* Spinner Styles */
.spinner-overlay {
position: fixed;
top: 0; left: 0;
width: 100vw;
height: 100vh;
background: rgba(255,255,255,0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
}
.spinner {
border: 6px solid #f3f3f3;
border-top: 6px solid var(--text-accent);
border-radius: 50%;
width: 48px;
height: 48px;
animation: spin 1s linear infinite;
}
#onError {
color: var(--danger);
width: 100%;
text-align: center;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* =====================
Media Queries
===================== */
@media (max-width: 700px) {
table th, table td {
padding: 0.25rem 0.5rem;
font-size: 0.9rem;
}
table caption {
font-size: 1rem;
padding: 0.25rem;
}
}

@ -1,4 +1,4 @@
import SheetJSModule from './sheetmodule';
import SheetJSModule from './sheetmodule'
export default defineNuxtConfig({
// @ts-ignore

@ -2,7 +2,7 @@
import { defineTransformer } from "@nuxt/content/transformers/utils";
import { read, utils } from "xlsx";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { resolve } from 'node:path';
export default defineTransformer({
name: 'sheetformer',

@ -1,14 +0,0 @@
/*
stplugin.c, version 3.0
copyright (c) 2003, 2006, 2015 StataCorp LP
*/
#include "stplugin.h"
ST_plugin *_stata_ ;
STDLL pginit(ST_plugin *p)
{
_stata_ = p ;
return(SD_PLUGINVER) ;
}

@ -1,246 +0,0 @@
/*
stplugin.h, version 3.0.0
copyright (c) 2003, 2004, 2006, 2015 StataCorp LP
*/
#if !defined(STPLUGIN_H)
#define STPLUGIN_H
#if !defined(SD_FASTMODE)
#define SD_SAFEMODE
#endif
#define HP9000 1
#define OPUNIX 2
#define APPLEMAC 3
#define STWIN32 4
#ifndef SYSTEM
#define SYSTEM STWIN32
#endif
typedef signed char ST_sbyte ;
typedef unsigned char ST_ubyte ;
typedef int ST_int ;
typedef unsigned ST_unsigned ;
typedef short int ST_int2 ;
typedef int ST_int4 ;
typedef long ST_long ;
typedef unsigned int ST_uint4 ;
typedef float ST_float ;
typedef double ST_double ;
typedef unsigned char ST_boolean ;
typedef int ST_retcode ;
typedef double * ST_dmkey ;
#if !defined(bTrue)
#define bTrue 1
#define bFalse 0
#endif
#define SF_HIWORD(x) ((ST_int2)((ST_int4)(x)>>16))
#define SF_LOWORD(x) ((ST_int2)(x))
#define SF_MAKELONG(x,y) ((ST_int4)(((ST_int2)(x))|((ST_int4)((ST_int2)(y)))<<16))
#if SYSTEM==STWIN32
#if __cplusplus
#define STDLL extern "C" __declspec(dllexport) ST_retcode
#else
#define STDLL extern __declspec(dllexport) ST_retcode
#endif
#endif
#if SYSTEM!=STWIN32
#if SYSTEM==HP9000
#include <dl.h>
#endif
#if SYSTEM==OPUNIX
#include <dlfcn.h>
#endif
#if __cplusplus
#define STDLL extern "C" ST_retcode
#else
#define STDLL ST_retcode
#endif
#define LPSTR char *
#endif
typedef struct {
ST_int type ;
ST_int nel ;
ST_int m ;
ST_int n ;
} ST_matinfo ;
#define SD_PLUGINMAJ 3
#define SD_PLUGINMIN 0
#define SD_PLUGINVER SF_MAKELONG(SD_PLUGINMAJ,SD_PLUGINMIN)
typedef void (* ST_VV) (void) ;
typedef ST_int (* ST_IV) (void) ;
typedef ST_int (* ST_IS) (char *) ;
typedef void (* ST_VU) (ST_ubyte) ;
typedef ST_boolean (* ST_BI) (ST_int) ;
typedef ST_boolean (* ST_BII) (ST_int,ST_int) ;
typedef ST_boolean (* ST_BD) (ST_double) ;
typedef ST_int (* ST_III) (ST_int,ST_int) ;
typedef ST_double (* ST_DII) (ST_int,ST_int) ;
typedef ST_double (* ST_DV) (void) ;
typedef ST_double (* ST_DD) (ST_double) ;
typedef ST_double (* ST_DDD) (ST_double,ST_double) ;
typedef ST_int (* ST_ISS) (char *,char *) ;
typedef ST_int (* ST_ISI) (char *,ST_int) ;
typedef ST_int (* ST_ISSI) (char *,char *,ST_int) ;
typedef void (* ST_VSD) (char *,ST_double) ;
typedef ST_int (* ST_ISD) (char *, ST_double) ;
typedef ST_int (* ST_ISDp) (char *,ST_double *) ;
typedef ST_int (* ST_ISDpIIIII) (char *,ST_int,ST_double *,ST_int,ST_int,ST_int,ST_int,ST_int) ;
typedef ST_int (* ST_ISIID) (char *, ST_int, ST_int, ST_double) ;
typedef ST_int (* ST_ISIIDp) (char *,ST_int,ST_int,ST_double *) ;
typedef ST_int (* ST_ISDpI) (char *,ST_double *,ST_int) ;
typedef void (* ST_VSMip) (char *,ST_matinfo *) ;
typedef ST_int (* ST_IIIDp) (ST_int, ST_int, ST_double *) ;
typedef ST_int (* ST_IIID) (ST_int, ST_int, ST_double) ;
typedef char * (* ST_SSI) (char *,ST_int) ;
typedef char * (* ST_SSSD) (char *,char *,ST_double) ;
typedef char * (* ST_SSSDM) (char *,char *,ST_double, ST_dmkey) ;
typedef ST_int (* ST_IIIS) (ST_int, ST_int, char *) ;
typedef ST_int (* ST_IIISI) (ST_int, ST_int, char *, ST_int) ;
typedef struct {
ST_IS spoutsml ;
ST_IS spoutnosml ;
ST_VV spoutflush ;
ST_VU set_outputlevel ;
ST_ISI get_input ;
ST_IV pollstd ;
ST_IV pollnow ;
ST_SSSD safereforms ;
ST_SSSDM safereforml ;
ST_SSI gettok ;
ST_ISS macresave ;
ST_ISSI macuse ;
ST_ISDp scalaruse ;
ST_ISDp scalarsave ;
ST_ISDpIIIII matrixstore ;
ST_ISDpI matrixload ;
ST_VSMip matrixinfo ;
ST_ISIIDp matrixel ;
ST_int matsize ;
ST_DII data, safedata ;
ST_IV nobs ;
ST_IV nvar ;
ST_double missval ;
ST_BD ismissing ;
ST_ISI stfindvar ;
ST_BI isstr ;
ST_VSD abvarfcn ;
ST_int *stopflag ;
ST_DDD stround ;
ST_DD stsqrt ;
ST_DDD stpow ;
ST_DD stlog ;
ST_DD stexp ;
ST_DV strandom ;
ST_IIID store ;
ST_IIID safestore ;
ST_IIIS sstore ;
ST_BI selobs ;
ST_IV nobs1 ;
ST_IV nobs2 ;
ST_IV nvars ;
ST_IS spouterr ;
ST_ISIIDp safematel ;
ST_ISIID safematstore ;
ST_ISIIDp matel ;
ST_ISIID matstore ;
ST_IIIDp safevdata ;
ST_IIIDp vdata ;
ST_IS colsof ;
ST_IS rowsof ;
ST_ISD scalsave ;
ST_IIIS sdata ;
ST_int2 major ;
ST_int2 minor ;
ST_BI isstrl ;
ST_BII isbinary ;
ST_III sdatalen ;
ST_IIISI strldata ;
} ST_plugin ;
#if __cplusplus
extern "C" ST_plugin *_stata_ ;
#else
extern ST_plugin *_stata_ ;
#endif
STDLL pginit(ST_plugin *p) ;
#define SF_display(a) ((_stata_)->spoutsml((a)))
#define SF_error(a) ((_stata_)->spouterr((a)))
#define SF_poll ((_stata_)->pollstd)
#define SW_stopflag (*((_stata_)->stopflag))
#define SF_macro_save(m,t) ((_stata_)->macresave((m),(t)))
#define SF_macro_use(m,d,l) ((_stata_)->macuse((m),(d),(l)))
#define SF_scal_use(s,d) ((_stata_)->scalaruse((s),(d)))
#define SF_scal_save(s,d) ((_stata_)->scalsave((s),(d)))
#if defined(SD_SAFEMODE)
#define SF_mat_el(s,r,c,d) ((_stata_)->safematel((s),(r),(c),(d)))
#define SF_mat_store(s,r,c,d) ((_stata_)->safematstore((s),(r),(c),(d)))
#else
#define SF_mat_el(s,r,c,d) ((_stata_)->matel((s),(r),(c),(d)))
#define SF_mat_store(s,r,c,d) ((_stata_)->matstore((s),(r),(c),(d)))
#endif
#define SV_matsize ((_stata_)->matsize)
#define SF_col(s) ((_stata_)->colsof((s)))
#define SF_row(s) ((_stata_)->rowsof((s)))
#if defined(SD_SAFEMODE)
#define SF_vdata(i,j,d) ((_stata_)->safevdata((i),(j),(d)))
#define SF_vstore(i,j,v) ((_stata_)->safestore((i),(j),(v)))
#else
#define SF_vdata(i,j,d) ((_stata_)->vdata((i),(j),(d)))
#define SF_vstore(i,j,v) ((_stata_)->store((i),(j),(v)))
#endif
#define SF_nobs ((_stata_)->nobs)
#define SF_in1 ((_stata_)->nobs1)
#define SF_in2 ((_stata_)->nobs2)
#define SF_nvar ((_stata_)->nvar)
#define SF_nvars ((_stata_)->nvars)
#define SF_sstore(i,j,s) ((_stata_)->sstore((i),(j),(s)))
#define SF_sdata(i,j,s) ((_stata_)->sdata((i),(j),(s)))
#define SF_strldata(i,j,s,l) ((_stata_)->strldata((i),(j),(s),(l)))
#define SF_sdatalen(i,j) ((_stata_)->sdatalen((i),(j)))
#define SF_var_is_string(a) ((_stata_)->isstr(a))
#define SF_var_is_strl(a) ((_stata_)->isstrl(a))
#define SF_var_is_binary(i,j) ((_stata_)->isbinary((i),(j)))
#define SV_missval ((_stata_)->missval)
#define SF_is_missing(z) ((_stata_)->ismissing(z))
#define SF_ifobs(z) ((_stata_)->selobs(z))
#endif

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="890" height="480" data-created="2025-05-16T06:48:51.357946">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="890" height="480" data-created="2024-10-20T20:28:10.455635">
<defs>
<style>
@font-face {
@ -114,7 +114,7 @@
<svg x="5" y="68" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">138</text>
<text x="7" y="22" text-anchor="left" class="browser_version">131</text>
<use xlink:href="#windows" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10</text>
<use xlink:href="#passing" x="90" width="10"></use>
@ -123,7 +123,7 @@
</svg>
<svg x="220" y="0" width="119" height="315">
<svg x="220" y="0" width="119" height="280">
<use x="12" y="7" width="20" height="20" xlink:href="#chrome" fill="#333f4b"></use>
<text x="42" y="24" text-anchor="left" class="head">Chrome</text>
@ -197,16 +197,6 @@
</svg>
<svg x="5" y="272" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">136</text>
<use xlink:href="#windows" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
</svg>
<svg x="330" y="0" width="119" height="105">
@ -235,13 +225,33 @@
</svg>
<svg x="440" y="0" width="119" height="70">
<svg x="440" y="0" width="119" height="140">
<use x="12" y="7" width="20" height="20" xlink:href="#ios" fill="#333f4b"></use>
<text x="42" y="24" text-anchor="left" class="head">iPad</text>
<svg x="5" y="34" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">11</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.13</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="68" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">13</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.15</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="102" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">15</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">12</text>
@ -251,16 +261,36 @@
</svg>
<svg x="550" y="0" width="119" height="70">
<svg x="550" y="0" width="119" height="140">
<use x="12" y="7" width="20" height="20" xlink:href="#ios" fill="#333f4b"></use>
<text x="42" y="24" text-anchor="left" class="head">iPhone</text>
<svg x="5" y="34" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">15</text>
<text x="7" y="22" text-anchor="left" class="browser_version">10</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">12</text>
<text x="53" y="22" text-anchor="left" class="platform_version">10.12</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="68" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">12</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.15</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="102" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">14</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">11</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
@ -384,7 +414,7 @@
<svg x="5" y="408" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">135</text>
<text x="7" y="22" text-anchor="left" class="browser_version">130</text>
<use xlink:href="#windows" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10</text>
<use xlink:href="#passing" x="90" width="10"></use>
@ -393,13 +423,83 @@
</svg>
<svg x="770" y="0" width="119" height="105">
<svg x="770" y="0" width="119" height="350">
<use x="12" y="7" width="20" height="20" xlink:href="#safari" fill="#333f4b"></use>
<text x="42" y="24" text-anchor="left" class="head">Safari</text>
<svg x="5" y="34" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">8</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.10</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="68" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">9</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.11</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="102" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">10</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.12</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="136" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">11</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.13</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="170" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">12</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.13</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="204" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">13</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.15</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="238" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">14</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">11</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="272" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">15</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">12</text>
@ -408,9 +508,9 @@
<svg x="5" y="68" width="109" height="33" viewBox="0 0 109 33">
<svg x="5" y="306" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">17</text>
<text x="7" y="22" text-anchor="left" class="browser_version">16</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">13</text>
<use xlink:href="#passing" x="90" width="10"></use>

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

@ -83,11 +83,11 @@ app.listen(7262, async() => {
});
EOF
npm i --save puppeteer express@4
npm i --save puppeteer express
node -e 'var pjson = JSON.parse(fs.readFileSync("./package.json")); console.log(pjson); delete pjson.main; fs.writeFileSync("package.json", JSON.stringify(pjson))'
for n in 2.14.4 1.12.3; do
for n in 1.12.3 2.13.3; do
npm i --save parcel@$n
npx -y parcel@$n build index.html
node test.js

@ -13,7 +13,7 @@ npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz exit-on-epipe c
## NOTE: must downgrade to node 18
npx -y pkg -t 'node18-win-arm64,node18-linux-arm64,node18-macos-arm64' xlsx-cli.js
npx -y pkg xlsx-cli.js
## NOTE: these steps are for darwin

@ -1,17 +0,0 @@
#!/bin/bash
# https://docs.sheetjs.com/docs/demos/net/email/pst
cd /tmp
rm -rf sheetjs-pst
mkdir sheetjs-pst
cd sheetjs-pst
npm init -y
curl -LO https://docs.sheetjs.com/pst/SheetJSPST.js
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz pst-extractor@1.11.0
node --version
node SheetJSPST.js
bun --version
bun SheetJSPST.js

@ -5,14 +5,14 @@ rm -rf sheetjs-graaljs
mkdir -p sheetjs-graaljs
cd sheetjs-graaljs
curl -LO "https://repo1.maven.org/maven2/org/graalvm/js/js-scriptengine/24.2.1/js-scriptengine-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/js/js-language/24.2.1/js-language-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/polyglot/polyglot/24.2.1/polyglot-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/sdk/collections/24.2.1/collections-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/truffle/truffle-api/24.2.1/truffle-api-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/sdk/nativeimage/24.2.1/nativeimage-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/shadowed/icu4j/24.2.1/icu4j-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/regex/regex/24.2.1/regex-24.2.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/js/js-scriptengine/24.1.1/js-scriptengine-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/js/js-language/24.1.1/js-language-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/polyglot/polyglot/24.1.1/polyglot-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/sdk/collections/24.1.1/collections-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/truffle/truffle-api/24.1.1/truffle-api-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/sdk/nativeimage/24.1.1/nativeimage-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/shadowed/icu4j/24.1.1/icu4j-24.1.1.jar"
curl -LO "https://repo1.maven.org/maven2/org/graalvm/regex/regex/24.1.1/regex-24.1.1.jar"
curl -LO https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js
curl -LO https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js
@ -20,19 +20,19 @@ curl -LO https://sheetjs.com/pres.xlsx
curl -LO https://docs.sheetjs.com/nashorn/SheetJSNashorn.java
for n in {17..24}; do
for n in {17..23}; do
export JAVA_HOME=`/usr/libexec/java_home -v $n`
java -version
rm -fr SheetJSNashorn.class SheetJSNashorn.jar sheethorn
javac SheetJSNashorn.java
java -cp ".:js-scriptengine-24.2.1.jar:js-language-24.2.1.jar:polyglot-24.2.1.jar:collections-24.2.1.jar:truffle-api-24.2.1.jar:nativeimage-24.2.1.jar:icu4j-24.2.1.jar:regex-24.2.1.jar" -Dpolyglot.js.nashorn-compat=true SheetJSNashorn pres.xlsx
java -cp ".:js-scriptengine-24.1.1.jar:js-language-24.1.1.jar:polyglot-24.1.1.jar:collections-24.1.1.jar:truffle-api-24.1.1.jar:nativeimage-24.1.1.jar:icu4j-24.1.1.jar:regex-24.1.1.jar" -Dpolyglot.js.nashorn-compat=true SheetJSNashorn pres.xlsx
jar -cf SheetJSNashorn.jar SheetJSNashorn.class xlsx.full.min.js shim.min.js
mkdir -p sheethorn
cp *.jar pres.xlsx sheethorn
cd sheethorn
java -cp ".:js-scriptengine-24.2.1.jar:js-language-24.2.1.jar:polyglot-24.2.1.jar:collections-24.2.1.jar:truffle-api-24.2.1.jar:nativeimage-24.2.1.jar:icu4j-24.2.1.jar:regex-24.2.1.jar:SheetJSNashorn.jar" -Dpolyglot.js.nashorn-compat=true SheetJSNashorn pres.xlsx
java -cp ".:js-scriptengine-24.1.1.jar:js-language-24.1.1.jar:polyglot-24.1.1.jar:collections-24.1.1.jar:truffle-api-24.1.1.jar:nativeimage-24.1.1.jar:icu4j-24.1.1.jar:regex-24.1.1.jar:SheetJSNashorn.jar" -Dpolyglot.js.nashorn-compat=true SheetJSNashorn pres.xlsx
cd -
done

@ -18,7 +18,7 @@ curl -L -o asm-analysis-9.5.jar "https://search.maven.org/remotecontent?filepath
curl -L -o asm-util-9.5.jar "https://search.maven.org/remotecontent?filepath=org/ow2/asm/asm-util/9.5/asm-util-9.5.jar"
# Standalone Nashorn
for n in {15..24}; do
for n in {15..23}; do
echo $n
export JAVA_HOME=`/usr/libexec/java_home -v $n`
echo $JAVA_HOME

@ -42,7 +42,7 @@ public class SheetJSRhino {
}
EOF
for n in 1.8 {9..24}; do
for n in 1.8 {9..23}; do
export JAVA_HOME=`/usr/libexec/java_home -v $n`
java -version
find . -name \*.class | while read x; do rm $x; done

@ -13,7 +13,7 @@ curl -Lo _data/pres.xlsx https://docs.sheetjs.com/pres.xlsx
curl -L -o .eleventy.js https://docs.sheetjs.com/eleventy/_eleventy.js
curl -LO https://docs.sheetjs.com/eleventy/index.njk
for n in 2.0.1 3.0.0 3.1.0-beta.1; do
for n in 2.0.1 3.0.0; do
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz @11ty/eleventy@$n
npx @11ty/eleventy@$n