docs.sheetjs.com/docz/docs/03-demos/19-desktop/04-tauri.md

18 KiB

title sidebar_label description pagination_prev pagination_next sidebar_position sidebar_custom_props
Data Wrangling in Tauri Apps Tauri Build data-intensive desktop apps using Tauri. Seamlessly integrate spreadsheets into your app using SheetJS. Modernize Excel-powered business processes with confidence. demos/mobile/index demos/cli/index 4
summary
Webview + Rust Backend

import current from '/version.js'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock';

Tauri is a modern toolkit for building desktop apps. Tauri apps leverage platform-native browser engines to build lightweight programs.

SheetJS is a JavaScript library for reading and writing data from spreadsheets.

This demo uses Tauri and SheetJS to pull data from a spreadsheet and display the data in the app. We'll explore how to load SheetJS in a Tauri app and exchange file data between the JavaScript frontend and Rust backend.

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

Windows screenshot

macOS screenshot

Linux screenshot

Integration Details

The SheetJS NodeJS Module can be installed and imported from JavaScript code.

:::note pass

Tauri currently does not provide the equivalent of NodeJS fs module. The raw @tauri-apps/api methods used in the examples are not expected to change.

:::

For security reasons, Tauri apps must explicitly enable system features.1 They are enabled in src-tauri/tauri.conf.json in the allowlist subsection of the tauri section of the config.

  • The fs entitlement2 enables reading and writing file data.
  "tauri": {
    "allowlist": {
      //highlight-start
      "fs": {
        "all": true
      }
      // highlight-end
  • The dialog entitlement3 enables the open and save dialog methods.
  "tauri": {
    "allowlist": {
      //highlight-start
      "dialog": {
        "all": true
      }
      // highlight-end
  • The http entitlement4 enables downloading files. Note that http is not needed for reading or writing files in the local filesystem.
  "tauri": {
    "allowlist": {
      //highlight-start
      "http": {
        "all": true,
        "request": true,
        "scope": ["https://**"]
      }
      // highlight-end

Reading Files

There are three steps to reading files:

  1. Show an open file dialog to allow users to select a path. The open method in @tauri-apps/api/dialog5 simplifies this process.

  2. Read raw data from the selected file using the readBinaryFile method in @tauri-apps/api/fs6. This method resolves to a standard Uint8Array

  3. Parse the data with the SheetJS read method7. This method returns a SheetJS workbook object.

The following code example defines a single function openFile that performs all three steps and returns a SheetJS workbook object:

import { read } from 'xlsx';
import { open } from '@tauri-apps/api/dialog';
import { readBinaryFile } from '@tauri-apps/api/fs';

const filters = [
  {name: "Excel Binary Workbook", extensions: ["xlsb"]},
  {name: "Excel Workbook", extensions: ["xlsx"]},
  {name: "Excel 97-2004 Workbook", extensions: ["xls"]},
  // ... other desired formats ...
];

async function openFile() {
  /* show open file dialog */
  const selected = await open({
    title: "Open Spreadsheet",
    multiple: false,
    directory: false,
    filters
  });

  /* read data into a Uint8Array */
  const d = await readBinaryFile(selected);

  /* parse with SheetJS */
  const wb = read(d);
  return wb;
}

At this point, standard SheetJS utility functions8 can extract data from the workbook object. The demo includes a button that calls sheet_to_json9 to generate an array of arrays of data.

The following snippet uses the VueJS framework:

import { utils } from 'xlsx';
import { shallowRef } from 'vue';
const data = shallowRef([[]]); // update data by setting `data.value`

const open_button_callback = async() => {
  const wb = await openFile();

  /* get the first worksheet */
  // highlight-start
  const ws = wb.Sheets[wb.SheetNames[0]];
  // highlight-end

  /* get data from the first worksheet */
  // highlight-start
  const array = utils.sheet_to_json(ws, { header: 1 });
  // highlight-end
  data.value = array;
};

The following snippet shows a simple Kaioponent:

import { utils } from 'xlsx';
import { useState } from 'kaioken';

function SheetJSImportKaioponent() {
  const [data, setData] = useState<any[][]>([]);

  const open_callback = async() => {
    const wb = await openFile();

    /* get the first worksheet */
    // highlight-start
    const ws = wb.Sheets[wb.SheetNames[0]];
    // highlight-end

    /* get data from the first worksheet */
    // highlight-start
    const array = utils.sheet_to_json(ws, { header: 1 });
    // highlight-end
    setData(array);
  };

  return ( <>
    <button type="button" onclick={open_callback}>Load Data</button>
    <table><tbody>{data.map((row) =>
      <tr>{row.map((cell) => <td>{cell}</td>)}</tr>
    )}</tbody></table>
  </>);
}

Writing Files

There are three steps to writing files:

  1. Show a save file dialog to allow users to select a path. The save method in @tauri-apps/api/dialog10 simplifies this process.

  2. Write the data with the SheetJS write method11. The output book type can be inferred from the selected file path. Using the buffer output type12, the method will return a Uint8Array object that plays nice with Tauri.

  3. Write the data using writeBinaryFile in @tauri-apps/api/fs13.

The following code example defines a single function saveFile that performs all three steps starting from a SheetJS workbook object:

import { write } from 'xlsx';
import { save } from '@tauri-apps/api/dialog';
import { writeBinaryFile } from '@tauri-apps/api/fs';

const filters = [
  {name: "Excel Binary Workbook", extensions: ["xlsb"]},
  {name: "Excel Workbook", extensions: ["xlsx"]},
  {name: "Excel 97-2004 Workbook", extensions: ["xls"]},
  // ... other desired formats ...
];

async function saveFile(wb) {
  /* show save file dialog */
  const selected = await save({
    title: "Save to Spreadsheet",
    filters
  });
  if(!selected) return;

  /* Generate workbook */
  const bookType = selected.slice(selected.lastIndexOf(".") + 1);
  const d = write(wb, {type: "buffer", bookType});

  /* save data to file */
  await writeBinaryFile(selected, d);
}

The demo includes a button that calls aoa_to_sheet14 to generate a sheet from array of arrays of data. A workbook is constructed using book_new and book_append_sheet15.

The following snippet uses the VueJS framework:

import { utils } from 'xlsx';
import { shallowRef } from 'vue';
const data = shallowRef([[]]); // `data.value` is an array of arrays

const save_button_callback = async() => {
  /* generate worksheet from the data */
  // highlight-start
  const ws = utils.aoa_to_sheet(data.value);
  // highlight-end

  /* create a new workbook object */
  // highlight-start
  const wb = utils.book_new();
  // highlight-end

  /* append the worksheet to the workbook using the sheet name "SheetJSTauri" */
  // highlight-start
  utils.book_append_sheet(wb, ws, "SheetJSTauri");
  // highlight-end

  await saveFile(wb);
}

The following snippet shows a simple Kaioponent:

import { utils } from 'xlsx';
import { useState } from 'kaioken';

function SheetJSExportKaioponent() {
  const [data, setData] = useState<any[][]>(["SheetJS".split(""), "Kaioken".split("")]);

  const save_callback = async() => {
    /* generate worksheet from the data */
    // highlight-start
    const ws = utils.aoa_to_sheet(data);
    // highlight-end

    /* create a new workbook object */
    // highlight-start
    const wb = utils.book_new();
    // highlight-end

    /* append the worksheet to the workbook using the sheet name "SheetJSTauri" */
    // highlight-start
    utils.book_append_sheet(wb, ws, "SheetJSTauri");
    // highlight-end

    await saveFile(wb);
  }

  return ( <button type="button" onclick={save_callback}>Save Data</button> );
}

Complete Example

:::note Tested Deployments

This demo was tested in the following environments:

OS and Version Architecture Tauri Date
macOS 14.4 darwin-x64 v1.5.11 2024-03-15
macOS 14.0 darwin-arm v1.5.2 2023-10-18
Windows 10 win10-x64 v1.5.11 2024-03-24
Windows 11 win11-arm v1.5.7 2023-12-01
Linux (HoloOS) linux-x64 v1.5.11 2024-03-21
Linux (Debian) linux-arm v1.5.7 2023-12-01

:::

  1. Read Tauri "Getting Started" guide and install prerequisites.16
Installation Notes (click to show)

At a high level, the following software is required for building Tauri apps:

  • a native platform-specific C/C++ compiler (for example, macOS requires Xcode)
  • a browser engine integration (for example, linux requires webkit2gtk)
  • Rust

The platform configuration can be verified by running:

npx @tauri-apps/cli info

If required dependencies are installed, the output will show a checkmark next to "Environment". The output from the most recent macOS test is shown below:

[✔] Environment
    - OS: Mac OS 14.4.0 X64
    ✔ Xcode Command Line Tools: installed
    ✔ rustc: 1.76.0 (07dca489a 2024-02-04)
    ✔ cargo: 1.76.0 (c84b36747 2024-01-18)
    ✔ rustup: 1.27.0 (bbb9276d2 2024-03-08)
    ✔ Rust toolchain: stable-x86_64-apple-darwin (default)
    - node: 20.11.1
    - npm: 10.2.4
    - bun: 1.0.31

:::caution pass

When the demo was last tested on ARM64 macOS, the output mentioned X64. The build step will correctly detect the platform architecture.

:::

  1. Create a new Tauri app:
npm create tauri-app@latest -- -m npm -t vue-ts SheetJSTauri -y

:::note pass

There is no official Tauri Kaioken template. This demo starts from the vanilla TypeScript template and manually wires Kaioken

:::

npm create tauri-app@latest -- -m npm -t vanilla-ts SheetJSTauri -y
  1. Enter the directory and install dependencies:

{\ cd SheetJSTauri npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz npm i --save @tauri-apps/api npm i --save-dev @tauri-apps/cli}

npm add kaioken --save
npm add vite-plugin-kaioken -D --save
  1. Add the highlighted lines to src-tauri/tauri.conf.json in the tauri.allowlist section:
  "tauri": {
    "allowlist": {
// highlight-start
      "http": {
        "all": true,
        "request": true,
        "scope": ["https://**"]
      },
      "dialog": {
        "all": true
      },
      "fs": {
        "all": true
      },
// highlight-end

In the same file, look for the "identifier" key and replace the value with com.sheetjs.tauri:

        "icons/icon.ico"
      ],
      // highlight-next-line
      "identifier": "com.sheetjs.tauri",
      "longDescription": "",
  1. Download App.vue and replace src/App.vue with the downloaded script.
curl -o src/App.vue https://docs.sheetjs.com/tauri/App.vue
  1. Wire up Kaioken to the Tauri app:
  • Add the highlighted lines to vite.config.ts:
import { defineConfig } from "vite";
// highlight-next-line
import kaioken from "vite-plugin-kaioken"

// https://vitejs.dev/config/
export default defineConfig(async () => ({
// highlight-start
  esbuild: {
    jsxInject: `import * as kaioken from "kaioken"`,
    jsx: "transform",
    jsxFactory: "kaioken.createElement",
    jsxFragment: "kaioken.fragment",
    loader: "tsx",
    include: ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"],
  },
  plugins: [kaioken()],
// highlight-end
  • Add the highlighted line to tsconfig.json:
{
  "compilerOptions": {
// highlight-next-line
    "jsx": "preserve",
    "target": "ES2020",
  • Replace index.html with the following codeblock:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="/src/styles.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SheetJS x Tauri</title>
    <script type="module" src="/src/main.ts" defer></script>
  </head>

  <body>
    <div id="container" class="container"></div>
  </body>
</html>
  • Add the following lines to src/styles.css:
.logo {
  padding: 0px;
  height: 64px; width: 64px;
  vertical-align: middle;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.centre { text-align: center; }
table.center {
  margin-left: auto;
  margin-right: auto;
}
  • Replace src/main.ts with the following codeblock:
import { mount } from "kaioken";
import App from "./App";

const root = document.getElementById("container");
mount(App, root!);
  • Download App.tsx and save to src/App.tsx:
curl -o src/App.tsx https://docs.sheetjs.com/tauri/App.tsx
  1. Build the app with
npm run tauri build

At the end, it will print the path to the generated installer.

:::info pass

If the build fails, see "Troubleshooting" for more details.

:::

  1. Run the program.

Depending on the version of Tauri, the command may be

./src-tauri/target/release/SheetJSTauri

or

./src-tauri/target/release/sheet-js-tauri

or

./src-tauri/target/release/sheetjstauri
.\src-tauri\target\release\SheetJSTauri.exe

The following features should be manually verified:

  • When it is loaded, the app will download https://sheetjs.com/pres.numbers and display the data in a table.
  • Clicking "Save Data" will show a save dialog. After selecting a path and name, the app will write a file. That file can be opened in a spreadsheet editor.
  • Edit the file in a spreadsheet editor, then click "Load Data" and select the edited file. The table will refresh with new contents.

Troubleshooting

:::note pass

During the last Linux ARM64 test, the build failed to create an AppImage:

        Error [tauri-cli-node] failed to bundle project: error running appimage.sh

This is a known Tauri AppImage packaging bug. Since the actual application and the .deb distributable are created, the error can be ignored.

:::

:::note pass

During the last Linux x64 test, the build failed with the error message:

'openssl/opensslv.h' file not found

OpenSSL must be installed. On Arch Linux and HoloOS (Steam Deck):

sudo pacman -S openssl

:::

:::note pass

During the last macOS test, the build failed with the following error message:

       Error failed to bundle project: error running bundle_dmg.sh

The root cause of the error can be discovered by running

npm run tauri build -- --verbose

The most recent test failed with a message:

execution error: Not authorized to send Apple events to Finder

This error was resolved by allowing Terminal to control Finder.

In the "System Settings" app, select "Privacy & Security" in the left column and select "Automation" in the body. Look for "Terminal", expand the section, and enable "Finder".

:::


  1. See "Security" in the Tauri documentation ↩︎

  2. See FsAllowlistConfig in the Tauri documentation ↩︎

  3. See DialogAllowlistConfig in the Tauri documentation ↩︎

  4. See HttpAllowlistConfig in the Tauri documentation ↩︎

  5. See dialog in the Tauri documentation ↩︎

  6. See fs in the Tauri documentation ↩︎

  7. See read in "Reading Files" ↩︎

  8. See "Utility Functions" ↩︎

  9. See "Array Output" in "Utility Functions" ↩︎

  10. See dialog in the Tauri documentation ↩︎

  11. See write in "Writing Files" ↩︎

  12. See "Supported Output Formats" ↩︎

  13. See fs in the Tauri documentation ↩︎

  14. See "Array of Arrays Input" in "Utility Functions" ↩︎

  15. See "Workbook Helpers" in "Utility Functions" ↩︎

  16. See "Prerequisites" in the Tauri documentation ↩︎