docs.sheetjs.com/docz/docs/03-demos/04-static/05-vitejs.md
2023-06-19 21:21:34 -04:00

10 KiB

title sidebar_class_name sidebar_label description pagination_prev pagination_next sidebar_custom_props
ViteJS red ViteJS Make static websites from spreadsheets using ViteJS. Seamlessly integrate data into your website using SheetJS. Empower non-technical people to write content from Excel. demos/net/index demos/mobile/index
type
bundler

ViteJS Spreadsheet Plugins

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

ViteJS is a modern build tool for generating static sites. It has a robust JavaScript-powered plugin system1

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

This demo uses ViteJS and SheetJS to pull data from a spreadsheet and display the content in an HTML table. We'll explore how to load SheetJS in a ViteJS plugin and compare a few different data loading strategies.

The "Complete Demo" section includes a complete website powered by an XLSX spreadsheet.

:::note

This demo covers static asset imports. For processing files in the browser, the "Bundlers" demo includes an example.

:::

Plugins

ViteJS supports static asset imports2, but the default raw loader interprets data as UTF-8 strings. This corrupts binary formats like XLSX and XLS, but a custom loader can override the default behavior.

For simple tables of data, "Pure Data Plugin" is strongly recommended. The heavy work is performed at build time and the generated site only includes the raw data.

For more complex parsing or display logic, "Base64 Plugin" is preferable. Since the raw parsing logic is performed in the page, the library will be included in the final bundle.

Pure Data Plugin

For a pure static site, a plugin can load data into an array of row objects. The SheetJS work is performed in the plugin. The library is not loaded in the page!

The following diagram depicts the workbook waltz:

flowchart LR
  file[(workbook\nfile)]
  subgraph SheetJS operations
    buffer(NodeJS\nBuffer)
    aoo(array of\nobjects)
  end
  html{{HTML\nTABLE}}
  file --> |vite.config.js\ncustom plugin| buffer
  buffer --> |vite.config.js\ncustom plugin| aoo
  aoo --> |main.js\nfrontend code| html

This ViteJS plugin will read spreadsheets using the SheetJS read method3 and generate arrays of row objects with sheet_to_json4:

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)}')`;
      }
    }
  ]
});

In frontend code, the loader will look for all modules with a ?sheetjs query string. The default export is an array of row objects.

The following example script displays the data in a table:

import data from './data/pres.xlsx?sheetjs';

document.querySelector('#app').innerHTML = `<table>
  <thead><tr><th>Name</th><th>Index</th></tr></thead>
  <tbody>
    ${data.map(row => `<tr>
      <td>${row.Name}</td>
      <td>${row.Index}</td>
    </tr>`).join("\n")}
  </tbody>
</table>`;

Base64 Plugin

This plugin pulls in data as a Base64 string that can be read with read5. While this approach works, it is not recommended since it loads the library in the front-end site.

The following diagram depicts the workbook waltz:

flowchart LR
  file[(workbook\nfile)]
  subgraph SheetJS operations
    base64(base64\nstring)
    aoo(array of\nobjects)
  end
  html{{HTML\nTABLE}}
  file --> |vite.config.js\ncustom plugin| base64
  base64 --> |main.js\nfrontend code| aoo
  aoo --> |main.js\nfrontend code| html

This ViteJS plugin will read spreadsheet files and export the data as a Base64 string. SheetJS is not imported in the plugin:

import { readFileSync } from 'fs';
import { defineConfig } from 'vite';

export default defineConfig({
  assetsInclude: ['**/*.xlsx'], // mark that xlsx file should be treated as assets

  plugins: [
    { // this plugin handles ?b64 tags
      name: "vite-b64-plugin",
      transform(code, id) {
        if(!id.match(/\?b64$/)) return;
        var path = id.replace(/\?b64/, "");
        var data = readFileSync(path, "base64");
        return `export default '${data}'`;
      }
    }
  ]
});

When importing using the b64 query, the raw Base64 string will be exposed. read will process the Base64 string using the base64 input type6:

import { read, utils } from "xlsx";

/* import workbook data */
import b64 from './data.xlsx?b64';

/* parse workbook and pull data from the first worksheet */
const wb = read(b64, { type: "base64" });
const wsname = wb.SheetNames[0];
const data = utils.sheet_to_json(wb.Sheets[wsname]);

document.querySelector('#app').innerHTML = `<table>
  <thead><tr><th>Name</th><th>Index</th></tr></thead>
  <tbody>
    ${data.map(row => `<tr>
      <td>${row.Name}</td>
      <td>${row.Index}</td>
    </tr>`).join("\n")}
  </tbody>
</table>`;

Complete Demo

:::note

This demo was tested on 2023 June 06 against vite v4.3.9.

:::

The demo walks through the process of creating a new ViteJS website from scratch. A Git repository with the completed site can be cloned7.

Initial Setup

  1. Create a new site with the vue-ts template and install the SheetJS package:

{\ npm create vite@latest sheetjs-vite -- --template vue-ts cd sheetjs-vite npm i npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz}

  1. Replace vite.config.ts with the following:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { readFileSync } from 'fs';
import { read, utils } from 'xlsx';

export default defineConfig({
  assetsInclude: ['**/*.xlsx'], // xlsx file should be treated as assets

  plugins: [
    vue(),
    { // 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)}')`;
      }
    },
    { // this plugin handles ?b64 tags
      name: "vite-b64-plugin",
      transform(code, id) {
        if(!id.match(/\?b64$/)) return;
        var path = id.replace(/\?b64/, "");
        var data = readFileSync(path, "base64");
        return `export default '${data}'`;
      }
    }
  ]
});
  1. Make a data folder and download https://sheetjs.com/pres.xlsx :
mkdir -p data
curl -L -o data/pres.xlsx https://sheetjs.com/pres.xlsx

Pure Data Test

  1. Run the dev server:
npm run dev

Open a browser window to the displayed URL (typically http://localhost:5173 )

  1. Replace the component src/components/HelloWorld.vue with:
<script setup lang="ts">
// @ts-ignore
import data from '../../data/pres.xlsx?sheetjs';
</script>

<template>
  <table>
    <tr><th>Name</th><th>Index</th></tr>
    <tr v-for="(row,R) in data" v-bind:key="R">
      <td>{{row.Name}}</td>
      <td>{{row.Index}}</td>
    </tr>
  </table>
</template>

Save and refresh the page. A data table should be displayed

  1. Stop the dev server and build the site
npm run build
npx http-server dist/

The terminal will display a url like http://127.0.0.1:8080 . Access that page with a web browser.

  1. To confirm that only the raw data is present in the page, view the page source. The code will reference some script like /assets/index-HASH.js. Open that script. Searching for Bill Clinton reveals the following:
{"Name":"Bill Clinton","Index":42}

Searching for BESSELJ should reveal no results. The SheetJS scripts are not included in the final site!

:::note

ViteJS also supports "Server-Side Rendering". In SSR, only the HTML table would be added to the final page. Details are covered in the ViteJS docs8.

:::

Base64 Test

  1. Run the dev server:
npm run dev

Open a browser window to the displayed URL.

  1. Replace the component src/components/HelloWorld.vue with:
<script setup lang="ts">
// @ts-ignore
import b64 from '../../data/pres.xlsx?b64';
import { read, utils } from "xlsx";
/* parse workbook and convert first sheet to row array */
const wb = read(b64);
const ws = wb.Sheets[wb.SheetNames[0]];
interface IPresident { Name: string; Index: number; };
const data = utils.sheet_to_json<IPresident>(ws);
</script>

<template>
  <table>
    <tr><th>Name</th><th>Index</th></tr>
    <tr v-for="(row,R) in data" v-bind:key="R">
      <td>{{row.Name}}</td>
      <td>{{row.Index}}</td>
    </tr>
  </table>
</template>
  1. Stop the dev server and build the site
npm run build
npx http-server dist/

The terminal will display a URL ( http://127.0.0.1:8080 ). Access that page with a web browser.

  1. To confirm that the object data is not present in the page, view the page source. The code will reference some script like /assets/index-HASH.js. Open that script. Searching for Bill Clinton should yield no results. Searching for BESSELJ should match the code:
425:"BESSELJ"

The SheetJS library is embedded in the final site.


  1. See "Using Plugins" in the ViteJS documentation. ↩︎

  2. See "Static Asset Handling" in the ViteJS documentation. ↩︎

  3. See read in "Reading Files" ↩︎

  4. See sheet_to_json in "Utilities" ↩︎

  5. See read in "Reading Files" ↩︎

  6. See the "base64" type in "Reading Files" ↩︎

  7. See SheetJS/sheetjs-vite on the SheetJS git server. ↩︎

  8. See "Server-Side Rendering" in the ViteJS documentation. ↩︎