15 KiB
title | sidebar_label | pagination_prev | pagination_next | sidebar_position | sidebar_custom_props | ||
---|---|---|---|---|---|---|---|
Electrified Sheets with Electron | Electron | demos/mobile/index | demos/cli/index | 1 |
|
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
Electron is a modern toolkit for building desktop apps. Electron apps use the same technologies powering Chromium and NodeJS.
SheetJS is a JavaScript library for reading and writing data from spreadsheets.
The "Complete Example" section covers a complete desktop app to read and write workbooks. The app will look like the screenshots below:
Windows | macOS | Linux |
---|---|---|
Integration Details
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.
Renderer Process Limitations
The renderer process is sandboxed and cannot run any non-browser code.
Main Process Limitations
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 to expose low-level system calls and NodeJS APIs to the renderer process.
// 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,
});
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:
<input type="file" name="xlfile" id="xlf" />
The event handler would process the event as if it were a web event:
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
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:
<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:
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
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.
// 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;
/* this function will show the open dialog and try to parse the workbook */
async function importFile() {
/* 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]);
}
The actual implementation of the openFile
function is handled within the main process in main.js
.
// main.js - main process
const { ipcMain, dialog } = require('electron');
ipcMain.handle('dialog:openFile', (_e, filters) =>
dialog.showOpenDialog({ title: 'Select a file', filters, properties: ['openFile'] })
);
Writing Files
XLSX.writeFile
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.
// 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;
async function exportFile(workbook) {
const result = await saveFile([{
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]);
}
And here is the implementation of the saveFile
function in main.js
:
// main.js - main process
const { ipcMain, dialog } = require('electron');
ipcMain.handle('dialog:saveFile', (_e, filters) =>
dialog.showSaveDialog({ title: 'Save file as', filters })
);
Complete Example
:::note Tested Deployments
This demo was tested in the following environments:
OS and Version | Architecture | Electron | Date |
---|---|---|---|
macOS 15.3 | darwin-x64 |
35.1.2 |
2025-03-31 |
macOS 14.5 | darwin-arm |
35.1.2 |
2025-03-30 |
Windows 11 | win11-x64 |
33.2.1 |
2025-02-11 |
Windows 11 | win11-arm |
33.2.1 |
2025-02-23 |
Linux (HoloOS) | linux-x64 |
33.2.1 |
2025-01-02 |
Linux (Debian) | linux-arm |
33.2.1 |
2025-02-16 |
:::
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.
- Download the demo files:
package.json
: project structuremain.js
: main process scriptindex.html
: window pageindex.js
: script loaded in render contextpreload.js
: preload script (ContextBridge API worker)styles.css
: stylesheet
:::caution pass
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.
:::
These instructions can be run in a Terminal (bash) or Command Prompt window:
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
:::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:
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
:::
- Install dependencies:
npm install
- To verify the app works, run in the test environment:
npx -y electron .
The app will run.
- To build a standalone app, run the builder:
npm run make
or
npm run dist
if you want to generate an installer binary.
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
.
:::caution pass
On Linux, the packaging step may require additional dependencies1
:::
:::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.
// ...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.
:::
Testing
-
Download the test file
pres.numbers
-
Launch the generated application:
Architecture | Command |
---|---|
darwin-x64 |
open ./out/sheetjs-electron-darwin-x64/sheetjs-electron.app |
darwin-arm |
open ./out/sheetjs-electron-darwin-arm64/sheetjs-electron.app |
win11-x64 |
.\out\sheetjs-electron-win32-x64\sheetjs-electron.exe |
win11-arm |
.\out\sheetjs-electron-win32-x64\sheetjs-electron.exe |
linux-x64 |
./out/sheetjs-electron-linux-x64/sheetjs-electron |
linux-arm |
./out/sheetjs-electron-linux-arm64/sheetjs-electron |
Electron API
- 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.
- Click "Export" and click "Save" in the popup. By default, it will try
to write to
Untitled.xls
in the Downloads folder.
:::note pass
In some tests, the dialog did not have a default name.
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.
Open with menu
-
Close the application, end the terminal process and re-launch (see step 6)
-
Open the Downloads folder in a file explorer or finder window.
-
Right-click the
pres.numbers
file and select "Open with". -
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
-
Close the application, end the terminal process and re-launch (see step 6)
-
Open the Downloads folder in a file explorer or finder window.
-
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.
File Input Element
-
Close the application, end the terminal process and re-launch (see step 6)
-
Click "Choose File". With the file picker, navigate to the Downloads folder and select
pres.numbers
.
Electron Breaking Changes
The first version of this demo used Electron 1.7.5
. The current demo includes
the required changes for Electron 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.
:::caution pass
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 |
This change was not properly documented!
Electron 9 and later require the preference nodeIntegration: true
in order to
require('xlsx')
in the renderer process.
Electron 12 and later also require worldSafeExecuteJavascript: true
and
contextIsolation: true
.
Electron 14 and later must use @electron/remote
instead of remote
. An
initialize
call is required to enable Developer Tools in the window.
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. This has been best practice since Electron 25.
:::