docs.sheetjs.com/docz/docs/03-demos/19-desktop/01-electron.md

454 lines
15 KiB
Markdown
Raw Normal View History

2023-01-05 03:57:48 +00:00
---
2025-01-23 18:52:41 +00:00
title: Electrified Sheets with Electron
sidebar_label: Electron
2023-01-05 23:33:49 +00:00
pagination_prev: demos/mobile/index
2024-03-18 08:24:41 +00:00
pagination_next: demos/cli/index
2023-01-05 03:57:48 +00:00
sidebar_position: 1
sidebar_custom_props:
summary: Embedded NodeJS + Chromium
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
2025-01-23 18:52:41 +00:00
[Electron](https://www.electronjs.org/) is a modern toolkit for building desktop
apps. Electron apps use the same technologies powering Chromium and NodeJS.
2023-01-05 03:57:48 +00:00
2025-01-23 18:52:41 +00:00
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
The ["Complete Example"](#complete-example) section covers a complete desktop
app to read and write workbooks. The app will look like the screenshots below:
2023-01-05 03:57:48 +00:00
2023-02-09 08:33:59 +00:00
<table><thead><tr>
2023-12-02 08:39:35 +00:00
<th><a href="#complete-example">Windows</a></th>
2023-02-09 08:33:59 +00:00
<th><a href="#complete-example">macOS</a></th>
<th><a href="#complete-example">Linux</a></th>
</tr></thead><tbody><tr><td>
2023-01-05 03:57:48 +00:00
2023-12-02 08:39:35 +00:00
![Windows screenshot](pathname:///electron/ew.png)
2023-02-21 01:04:05 +00:00
</td><td>
2023-02-09 08:33:59 +00:00
![macOS screenshot](pathname:///electron/em.png)
2023-01-05 03:57:48 +00:00
2023-02-09 08:33:59 +00:00
</td><td>
2023-01-05 03:57:48 +00:00
2023-02-09 08:33:59 +00:00
![Linux screenshot](pathname:///electron/el.png)
2023-01-05 03:57:48 +00:00
2023-02-09 08:33:59 +00:00
</td></tr></tbody></table>
2023-01-05 03:57:48 +00:00
2023-02-09 08:33:59 +00:00
## Integration Details
2023-01-05 03:57:48 +00:00
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.
2025-01-23 18:52:41 +00:00
**Renderer Process Limitations**
2025-01-23 18:52:41 +00:00
The renderer process is sandboxed and cannot run any non-browser code.
2025-01-23 18:52:41 +00:00
**Main Process Limitations**
2025-01-23 18:52:41 +00:00
The main process can run any NodeJS code, but it cannot access the DOM or any browser APIs.
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.
2025-01-23 18:52:41 +00:00
```js
// preload.js
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.
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.
xlsx: XLSX,
});
```
2023-01-05 03:57:48 +00:00
### Reading Files
Electron offers 3 different ways to read files, two of which use Web APIs.
**File Input Element**
File input elements automatically map to standard Web APIs.
For example, assuming a file input element on the page:
```html
<input type="file" name="xlfile" id="xlf" />
```
The event handler would process the event as if it were a web event:
```js
async function handleFile(e) {
const file = e.target.files[0];
const data = await file.arrayBuffer();
/* data is an ArrayBuffer */
const workbook = XLSX.read(data);
/* DO SOMETHING WITH workbook HERE */
}
document.getElementById("xlf").addEventListener("change", handleFile, false);
```
**Drag and Drop**
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.
2023-01-05 03:57:48 +00:00
For example, assuming a DIV on the page:
```html
<div id="drop">Drop a spreadsheet file here to see sheet data</div>
```
The event handler would process the event as if it were a web event:
```js
async function handleDrop(e) {
e.stopPropagation();
e.preventDefault();
const file = e.dataTransfer.files[0];
const data = await file.arrayBuffer();
/* data is an ArrayBuffer */
const workbook = XLSX.read(data);
/* DO SOMETHING WITH workbook HERE */
}
document.getElementById("drop").addEventListener("drop", handleDrop, false);
```
**Electron API**
[`XLSX.readFile`](/docs/api/parse-options) reads workbooks from the file system.
`showOpenDialog` shows a Save As dialog and returns the selected file name.
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.
2023-01-05 03:57:48 +00:00
```js
// index.js - renderer process
// our exposed bridge APIs are available as SheetJSDemoAPI on the window object
const openFile = SheetJSDemoAPI.openFile; // request the open file dialog from the main process
// We can also access the SheetJS package from the exposed bridge APIs
const XLSX = SheetJSDemoAPI.xlsx;
2023-01-05 03:57:48 +00:00
/* this function will show the open dialog and try to parse the workbook */
async function importFile() {
/* show open file dialog */
const result = await openFile([{
2023-01-05 03:57:48 +00:00
name: "Spreadsheets",
extensions: ["xlsx", "xls", "xlsb", /* ... other formats ... */]
}]);
2023-01-05 03:57:48 +00:00
/* 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]);
}
```
The actual implementation of the `openFile` function is handled within the main process in `main.js`.
2023-01-05 03:57:48 +00:00
```js
// main.js - main process
const { ipcMain, dialog } = require('electron');
2023-01-05 03:57:48 +00:00
ipcMain.handle('dialog:openFile', (_e, filters) =>
dialog.showOpenDialog({ title: 'Select a file', filters, properties: ['openFile'] })
);
2023-01-05 03:57:48 +00:00
```
2023-02-12 08:15:17 +00:00
### 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:
The implementation for saving files looks very similar to the one above thanks to our bridge API.
2023-02-12 08:15:17 +00:00
```js
// 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;
2023-02-12 08:15:17 +00:00
async function exportFile(workbook) {
const result = await saveFile([{
2023-02-12 08:15:17 +00:00
name: "Spreadsheets",
extensions: ["xlsx", "xls", "xlsb", /* ... other formats ... */]
}]);
if(result.filePaths.length == 0) throw new Error("No file was selected!");
XLSX.writeFile(workbook, result.filePaths[0]);
2023-02-12 08:15:17 +00:00
}
```
And here is the implementation of the `saveFile` function in `main.js`:
2023-02-12 08:15:17 +00:00
```js
// main.js - main process
const { ipcMain, dialog } = require('electron');
2023-02-12 08:15:17 +00:00
ipcMain.handle('dialog:saveFile', (_e, filters) =>
dialog.showSaveDialog({ title: 'Save file as', filters })
);
2023-02-12 08:15:17 +00:00
```
2023-02-09 08:33:59 +00:00
## Complete Example
2023-12-02 08:39:35 +00:00
:::note Tested Deployments
2023-02-09 08:33:59 +00:00
2023-09-05 18:04:23 +00:00
This demo was tested in the following environments:
2023-12-02 08:39:35 +00:00
| OS and Version | Architecture | Electron | Date |
|:---------------|:-------------|:---------|:-----------|
2025-04-01 02:57:45 +00:00
| macOS 15.3 | `darwin-x64` | `35.1.2` | 2025-03-31 |
| macOS 14.5 | `darwin-arm` | `35.1.2` | 2025-03-30 |
2025-02-17 04:49:35 +00:00
| Windows 11 | `win11-x64` | `33.2.1` | 2025-02-11 |
2025-02-24 01:17:05 +00:00
| Windows 11 | `win11-arm` | `33.2.1` | 2025-02-23 |
2025-01-06 02:51:20 +00:00
| Linux (HoloOS) | `linux-x64` | `33.2.1` | 2025-01-02 |
2025-02-17 04:49:35 +00:00
| Linux (Debian) | `linux-arm` | `33.2.1` | 2025-02-16 |
2023-02-09 08:33:59 +00:00
:::
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.
2023-02-09 08:33:59 +00:00
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
2023-02-09 08:33:59 +00:00
2023-09-24 03:59:48 +00:00
:::caution pass
2023-02-09 08:33:59 +00:00
Right-click each link and select "Save Link As...". Left-clicking a link will
try to load the page in your browser. The goal is to save the file contents.
:::
2023-12-02 08:39:35 +00:00
These instructions can be run in a Terminal (bash) or Command Prompt window:
2023-02-09 08:33:59 +00:00
```bash
mkdir sheetjs-electron
cd sheetjs-electron
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
2023-02-09 08:33:59 +00:00
```
2025-01-23 18:52:41 +00:00
:::note pass
In PowerShell, the command may fail with a parameter error:
```
Invoke-WebRequest : A parameter cannot be found that matches parameter name 'LO'.
```
`curl.exe` must be invoked directly:
```bash
curl.exe -LO https://docs.sheetjs.com/electron/package.json
curl.exe -LO https://docs.sheetjs.com/electron/main.js
curl.exe -LO https://docs.sheetjs.com/electron/index.html
curl.exe -LO https://docs.sheetjs.com/electron/index.js
curl.exe -LO https://docs.sheetjs.com/electron/preload.js
curl.exe -LO https://docs.sheetjs.com/electron/styles.css
2025-01-23 18:52:41 +00:00
```
:::
2023-09-05 18:04:23 +00:00
2) Install dependencies:
```bash
npm install
```
2023-02-09 08:33:59 +00:00
3) To verify the app works, run in the test environment:
```bash
npx -y electron .
```
2024-07-14 07:17:31 +00:00
The app will run.
2023-02-09 08:33:59 +00:00
4) To build a standalone app, run the builder:
```bash
npm run make
```
or
```bash
npm run dist
```
if you want to generate an installer binary.
2023-02-09 08:33:59 +00:00
This will create a package in the `out\make` folder and a standalone binary, or an installer binary in `/dist` if you used `npm run dist`.
2023-09-05 18:04:23 +00:00
:::caution pass
On Linux, the packaging step may require additional dependencies[^1]
:::
2023-09-25 07:30:54 +00:00
:::info pass
When the demo was last tested on Windows ARM, the generated binary targeted x64.
The program will run on ARM64 Windows.
:::
### Working with OS level file open events.
The demo has been preconfigured to handle OS level file open events, such as the "open with" context menu or `open` CLI command for all file types SheetJS supports.
In order to pre-register your application as a handler for any other file types, it is necessary to modify the `package.json` file as such.
```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 snippet 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.
:::info pass
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.
:::
2023-12-02 08:39:35 +00:00
### Testing
2023-09-05 18:04:23 +00:00
2024-04-26 04:16:13 +00:00
5) Download [the test file `pres.numbers`](https://docs.sheetjs.com/pres.numbers)
2023-09-05 18:04:23 +00:00
2024-01-03 06:47:00 +00:00
6) Launch the generated application:
2023-09-05 18:04:23 +00:00
2024-01-03 06:47:00 +00:00
| Architecture | Command |
|:-------------|:--------------------------------------------------------------|
2024-05-28 05:20:05 +00:00
| `darwin-x64` |`open ./out/sheetjs-electron-darwin-x64/sheetjs-electron.app` |
| `darwin-arm` |`open ./out/sheetjs-electron-darwin-arm64/sheetjs-electron.app`|
| `win11-x64` |`.\out\sheetjs-electron-win32-x64\sheetjs-electron.exe` |
2024-05-28 05:20:05 +00:00
| `win11-arm` |`.\out\sheetjs-electron-win32-x64\sheetjs-electron.exe` |
| `linux-x64` |`./out/sheetjs-electron-linux-x64/sheetjs-electron` |
2024-07-08 08:18:18 +00:00
| `linux-arm` |`./out/sheetjs-electron-linux-arm64/sheetjs-electron` |
2023-09-05 18:04:23 +00:00
2023-12-02 08:39:35 +00:00
#### Electron API
2023-09-05 18:04:23 +00:00
7) Click "Click here to select a file from your computer". With the file picker,
navigate to the Downloads folder and select `pres.numbers`.
The application should show data in a table.
8) Click "Export" and click "Save" in the popup. By default, it will try
2023-09-05 18:04:23 +00:00
to write to `Untitled.xls` in the Downloads folder.
2023-12-02 08:39:35 +00:00
:::note pass
2024-01-03 06:47:00 +00:00
In some tests, the dialog did not have a default name.
2023-12-02 08:39:35 +00:00
If there is no default name, enter `Untitled.xls` and click "Save".
:::
2023-09-05 18:04:23 +00:00
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.
#### Open with menu
2023-09-05 18:04:23 +00:00
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) Right-click the `pres.numbers` file and select "Open with".
12) Select your application binary by navigating to the folder where the application was built (see step 4).
The application should show data in 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 to the
2023-09-05 18:04:23 +00:00
bordered "Drop a spreadsheet file" box. The file data should be displayed.
2023-12-02 08:39:35 +00:00
#### File Input Element
2023-09-05 18:04:23 +00:00
16) Close the application, end the terminal process and re-launch (see step 6)
2023-09-05 18:04:23 +00:00
17) Click "Choose File". With the file picker, navigate to the Downloads folder
2023-09-05 18:04:23 +00:00
and select `pres.numbers`.
2023-02-09 08:33:59 +00:00
## Electron Breaking Changes
2023-01-05 03:57:48 +00:00
2025-01-06 02:51:20 +00:00
The first version of this demo used Electron `1.7.5`. The current demo includes
the required changes for Electron `36.1.0`.
2023-01-05 03:57:48 +00:00
There are no Electron-specific workarounds in the library, but Electron broke
backwards compatibility multiple times. A summary of changes is noted below.
2023-09-24 03:59:48 +00:00
:::caution pass
2023-01-05 03:57:48 +00:00
2024-01-29 03:29:45 +00:00
Electron 6 changed the return types of `dialog` API methods. The old `dialog`
methods have been renamed:
| Electron 1 - 5 | Electron 6 |
|:-----------------|:---------------------|
| `showOpenDialog` | `showOpenDialogSync` |
| `showSaveDialog` | `showSaveDialogSync` |
2025-01-06 02:51:20 +00:00
2024-01-29 03:29:45 +00:00
**This change was not properly documented!**
2023-01-05 03:57:48 +00:00
2024-01-29 03:29:45 +00:00
Electron 9 and later require the preference `nodeIntegration: true` in order to
`require('xlsx')` in the renderer process.
2023-01-05 03:57:48 +00:00
2024-01-29 03:29:45 +00:00
Electron 12 and later also require `worldSafeExecuteJavascript: true` and
2023-01-05 03:57:48 +00:00
`contextIsolation: true`.
2024-01-29 03:29:45 +00:00
Electron 14 and later must use `@electron/remote` instead of `remote`. An
`initialize` call is required to enable Developer Tools in the window.
2023-01-05 03:57:48 +00:00
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.
2023-01-05 03:57:48 +00:00
:::
2023-09-05 18:04:23 +00:00
2025-02-17 04:49:35 +00:00
[^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.