17 KiB
sidebar_position | title |
---|---|
9 | Bundlers |
import current from '/version.js'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
SheetJS predates ECMAScript modules and bundler tools like Webpack. As best practices have evolved, stress testing SheetJS libraries have revealed bugs in the respective bundlers. This demo collects various notes and provides basic examples.
:::note
Issues should be reported to the respective bundler projects. Typically it is considered a bundler bug if the tool cannot properly handle JS libraries.
:::
Browserify
browserify
is compatible with the library and should "just work" with the
require
form in a main page or in a web worker:
var XLSX = require("xlsx");
// ... use XLSX ...
After installing the module, bundling is easy:
browserify app.js > browserify.js
uglifyjs browserify.js > browserify.min.js
Web Worker scripts can be bundled using the same approach.
Complete Example (click to show)
- Install the tarball using a package manager:
{`\
$ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
- Download the following files:
- Bundle the scripts:
npx browserify app.js > browserify.js
npx browserify xlsxworker.js > worker.js
- Spin up a local web server:
npx http-server
- Access the site http://localhost:8080/ and use the file input element to select a spreadsheet.
Bun
bun bun
is capable of optimizing imported libraries in node_modules
. In
local testing, a bundled script can save tens of milliseconds per run.
Complete Example (click to show)
- Install the tarball using a package manager:
{`\
$ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
- Save the following script to
bun.js
:
// highlight-next-line
import * as XLSX from 'xlsx/xlsx.mjs';
// highlight-next-line
import * as fs from 'fs';
// highlight-next-line
XLSX.set_fs(fs);
/* fetch JSON data and parse */
const url = "https://sheetjs.com/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
const prez = raw_data.filter((row) => row.terms.some((term) => term.type === "prez"));
/* flatten objects */
const rows = prez.map((row) => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
/* generate worksheet and workbook */
const worksheet = XLSX.utils.json_to_sheet(rows);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Dates");
/* fix headers */
XLSX.utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
/* calculate column width */
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
/* create an XLSX file and try to save to Presidents.xlsx */
XLSX.writeFile(workbook, "Presidents.xlsx");
- Bundle the script with
bun bun
:
bun bun bun.js
This procedure will generate node_modules.bun
.
- Run the script
bun bun.js
ESBuild
The xlsx.mjs
source file are written in a subset of ES6 that ESBuild
understands and is able to transpile down for older browsers.
Both the node
and browser
platforms work out of the box.
Complete Example (click to show)
- Install the tarball using a package manager:
{`\
$ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
- Save the following to
esbrowser.js
:
// highlight-next-line
import { utils, version, writeFileXLSX } from 'xlsx/xlsx.mjs';
(async() => {
/* fetch JSON data and parse */
const url = "https://sheetjs.com/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
/* flatten objects */
const rows = prez.map(row => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
/* generate worksheet and workbook */
const worksheet = utils.json_to_sheet(rows);
const workbook = utils.book_new();
utils.book_append_sheet(workbook, worksheet, "Dates");
/* fix headers */
utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
/* calculate column width */
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
/* create an XLSX file and try to save to Presidents.xlsx */
writeFileXLSX(workbook, "Presidents.xlsx");
})();
- Create a small HTML page that loads the script. Save to
index.html
:
<body><script src="esb.browser.js"></script></body>
- Create bundle:
npx esbuild esbrowser.js --bundle --outfile=esb.browser.js
- Start a local HTTP server, then go to http://localhost:8080/
npx http-server .
- Install the tarball using a package manager:
{`\
$ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
- Save the following to
esbnode.js
:
// highlight-next-line
import { set_fs, utils, version, writeFile } from 'xlsx/xlsx.mjs';
// highlight-next-line
import * as fs from 'fs';
// highlight-next-line
set_fs(fs);
(async() => {
/* fetch JSON data and parse */
const url = "https://sheetjs.com/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
/* flatten objects */
const rows = prez.map(row => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
/* generate worksheet and workbook */
const worksheet = utils.json_to_sheet(rows);
const workbook = utils.book_new();
utils.book_append_sheet(workbook, worksheet, "Dates");
/* fix headers */
utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
/* calculate column width */
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
/* create an XLSX file and try to save to Presidents.xlsx */
writeFile(workbook, "Presidents.xlsx");
})();
- Create bundle:
npx esbuild esbnode.js --bundle --platform=node --outfile=esb.node.js
- Run the bundle:
node esb.node.js
Parcel
Parcel Bundler should play nice with SheetJS out of the box.
:::warning Parcel Bug
Errors of the form Could not statically evaluate fs call
stem from a
parcel bug. Upgrade to
Parcel version 1.5.0 or later.
:::
Complete Example (click to show)
This demo follows the Presidents Example.
- Save the following to
index.html
:
<body>
<h3>SheetJS <span id="vers"></span> export demo</h3>
<button id="xport">Click to Export!</button>
<!-- the script tag must be marked as `type="module"` -->
<!-- highlight-next-line -->
<script type="module">
// ESM-style import from "xlsx"
// highlight-next-line
import { utils, version, writeFileXLSX } from 'xlsx';
document.getElementById("vers").innerText = version;
document.getElementById("xport").onclick = async() => {
/* fetch JSON data and parse */
const url = "https://sheetjs.com/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
/* flatten objects */
const rows = prez.map(row => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
/* generate worksheet and workbook */
const worksheet = utils.json_to_sheet(rows);
const workbook = utils.book_new();
utils.book_append_sheet(workbook, worksheet, "Dates");
/* fix headers */
utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
/* calculate column width */
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
/* create an XLSX file and try to save to Presidents.xlsx */
writeFileXLSX(workbook, "Presidents.xlsx");
};
</script>
<body>
- Install the SheetJS node module:
{`\
$ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
- Run the Parcel CLI tool:
npx -y parcel index.html
- Access the page listed in the output (typically
http://localhost:1234
) and click the "Click to Export!" button to generate a file.
RequireJS
Standalone scripts comply with AND define
semantics, enabling use in RequireJS out of the box.
To enable use of the alias xlsx
, the RequireJS config should set an alias in
the paths
property:
require.config({
baseUrl: ".",
name: "app",
paths: {
// highlight-next-line
xlsx: "xlsx.full.min"
}
});
// highlight-next-line
require(["xlsx"], function(XLSX) {
/* use XLSX here */
console.log(XLSX.version);
});
The Live demo loads RequireJS from the
CDN, uses it to load the standalone script from the SheetJS CDN, and uses the
XLSX
variable to create a button click handler that creates a workbook.
The r.js
optimizer also supports the standalone scripts.
Snowpack
Snowpack works with no caveats.
Complete Example (click to show)
- Install the tarball using a package manager:
{`\
$ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
{`\
$ yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
- Save the following to
index.js
:
// highlight-next-line
import { utils, version, writeFileXLSX } from 'xlsx/xlsx.mjs';
document.getElementById("xport").addEventListener("click", async() => {
/* fetch JSON data and parse */
const url = "https://sheetjs.com/executive.json";
const raw_data = await (await fetch(url)).json();
/* filter for the Presidents */
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
/* flatten objects */
const rows = prez.map(row => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
/* generate worksheet and workbook */
const worksheet = utils.json_to_sheet(rows);
const workbook = utils.book_new();
utils.book_append_sheet(workbook, worksheet, "Dates");
/* fix headers */
utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
/* calculate column width */
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
/* create an XLSX file and try to save to Presidents.xlsx */
writeFileXLSX(workbook, "Presidents.xlsx");
});
- Create a small HTML page that loads the script. Save to
index.html
:
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<h1>SheetJS Presidents Demo</h1>
<button id="xport">Click here to export</button>
<script type="module" src="./index.js"></script>
</body>
</html>
:::note
Unlike other bundlers, Snowpack requires a full page including HEAD
element.
:::
- Build for production:
npx snowpack build
- Start a local HTTP server, then go to http://localhost:8080/
npx http-server build/
Click on "Click here to export" to generate a file.
Vite
:::caution
ViteJS adopted nascent package.json
patterns. Version 0.18.10 implements the
patterns required for ViteJS 3.0.3. These patterns are evolving and a future
version of Vite may require more packaging changes.
:::
ViteJS 3.0.3 is known to work with SheetJS version 0.18.10.
Complete Example (click to show)
- Create a new ViteJS project:
npm create vite@latest
cd sheetjs-vite # (project name)
npm i
When prompted for Project Name, type sheetjs-vite
When prompted for Framework, select vue
then vue-ts
- Add the SheetJS dependency:
{`\
$ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
- Replace
src\components\HelloWorld.vue
with:
<script setup lang="ts">
import { version, utils, writeFileXLSX } from 'xlsx';
interface President {
terms: { "type": "prez" | "viceprez"; }[];
name: { first: string; last: string; }
bio: { birthday: string; }
}
async function xport() {
/* fetch JSON data and parse */
const url = "https://sheetjs.com/executive.json";
const raw_data: President[] = await (await fetch(url)).json();
/* filter for the Presidents */
const prez = raw_data.filter(row => row.terms.some(term => term.type === "prez"));
/* flatten objects */
const rows = prez.map(row => ({
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}));
/* generate worksheet and workbook */
const worksheet = utils.json_to_sheet(rows);
const workbook = utils.book_new();
utils.book_append_sheet(workbook, worksheet, "Dates");
/* fix headers */
utils.sheet_add_aoa(worksheet, [["Name", "Birthday"]], { origin: "A1" });
/* calculate column width */
const max_width = rows.reduce((w, r) => Math.max(w, r.name.length), 10);
worksheet["!cols"] = [ { wch: max_width } ];
/* create an XLSX file and try to save to Presidents.xlsx */
writeFileXLSX(workbook, "Presidents.xlsx");
}
</script>
<template>
<button type="button" @click="xport">Export with SheetJS version {{ version }}</button>
</template>
-
Run
npm run dev
and test functionality by opening a web browser to http://localhost:5173/ and clicking the button -
Run
npx vite build
and verify the generated pages work by running a local web server in thedist
folder:
npx http-server dist/
Access http://localhost:8080 in your web browser.