chore/docs: [Electron Demo] - improve code comments and documentation.

This commit is contained in:
syntaxbullet 2025-05-03 15:53:11 +02:00
parent 418f16a872
commit 8c4bd369c4
2 changed files with 54 additions and 60 deletions

@ -52,8 +52,9 @@ The main process can run any NodeJS code, but it cannot access the DOM or any br
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.
```js
// preload.js
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');
@ -95,7 +96,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();
@ -121,7 +122,7 @@ 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"
async function handleDrop(e) {
e.stopPropagation();
e.preventDefault();
@ -143,12 +144,11 @@ document.getElementById("drop").addEventListener("drop", handleDrop, false);
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
// index.js - renderer process
```js title="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
// highlight-next-line
const XLSX = SheetJSDemoAPI.xlsx;
/* this function will show the open dialog and try to parse the workbook */
@ -164,10 +164,10 @@ async function importFile() {
return XLSX.readFile(result.filePaths[0]);
}
```
The xslx package here is being proxied from the main process via the bridge API.
The actual implementation of the `openFile` function is handled within the main process in `main.js`.
```js
// main.js - main process
```js title="main.js -- main process"
const { ipcMain, dialog } = require('electron');
ipcMain.handle('dialog:openFile', (_e, filters) =>
@ -181,8 +181,7 @@ ipcMain.handle('dialog:openFile', (_e, filters) =>
`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.
```js
// index.js - renderer process
```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;
@ -197,8 +196,7 @@ async function exportFile(workbook) {
}
```
And here is the implementation of the `saveFile` function in `main.js`:
```js
// main.js - main process
```js title="main.js -- main process"
const { ipcMain, dialog } = require('electron');
ipcMain.handle('dialog:saveFile', (_e, filters) =>
@ -206,6 +204,38 @@ ipcMain.handle('dialog:saveFile', (_e, filters) =>
);
```
### 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.
:::
## Complete Example
:::note Tested Deployments
@ -215,8 +245,8 @@ 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 |
@ -318,38 +348,6 @@ 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.
:::
### Testing
5) Download [the test file `pres.numbers`](https://docs.sheetjs.com/pres.numbers)
@ -445,10 +443,10 @@ Electron 12 and later also require `worldSafeExecuteJavascript: true` and
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](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.

@ -50,14 +50,12 @@ async function exportWorkbookAsFile() {
extensions: EXTENSIONS,
},
]);
// -- 2. if canceled or no file path, return
if (canceled || !filePath) return;
// -- 3. write workbook to file
// -- 2. write workbook to file
try {
XLSX.writeFile(currentWorkbook, filePath);
window.SheetJSDemoAPI.message(`Exported to ${filePath}`);
} catch (err) {
// -- 4. if error, display error
displayError(`Failed to export: ${err.message}`);
}
}
@ -67,18 +65,19 @@ exportBtn.addEventListener("click", exportWorkbookAsFile);
// Render workbook --> HTML tables
// ---------------------------------------------------------------------------
function renderWorkbookToTables(wb) {
// -- 1. convert each sheet to HTML
// -- 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` });
// -- 2. wrap in details element
return `<details class="sheetjs-sheet-container">
<summary class="sheetjs-sheet-name">${name}</summary>
<div class="sheetjs-tab-content">${table}</div>
</details>`;
}).join(""); // -- 3. join into single string
// -- 4. render to DOM
}).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
}
// ---------------------------------------------------------------------------
@ -133,24 +132,21 @@ async function handleReadBtn() {
extensions: EXTENSIONS,
},
]);
// -- 2. if canceled or no file path, return
if (canceled || !filePaths.length) return;
// -- 3. if multiple files selected, return error
if (filePaths.length !== 1)
return displayError("Please choose a single file.");
showSpinner();
await nextPaint(); // ensure spinner paints
try {
// -- 4. read the first selected file
const filePath = filePaths[0];
// -- 2. read the first selected file
currentWorkbook = XLSX.readFile(filePath);
renderWorkbookToTables(currentWorkbook);
showLoadedFileUI(basename(filePath));
} finally {
hideSpinner();
hideDropUI();
// -- 5. reset error UI state
onError && (onError.hidden = true);
}
}