This commit is contained in:
SheetJS 2023-10-17 22:07:06 -04:00
parent 60118bad51
commit b230d64dd6
8 changed files with 523 additions and 243 deletions

@ -0,0 +1,320 @@
---
title: Bundling Sheets with Webpack
sidebar_label: Webpack
pagination_prev: demos/index
pagination_next: demos/grid/index
sidebar_position: 5
---
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
[Webpack](https://webpack.js.org/) is a module bundler.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses Webpack and SheetJS to export data. We'll explore how to bundle
SheetJS in a site using Webpack and how to export data to spreadsheets.
:::info pass
The [Webpack section of the Content demo](/docs/demos/static/webpack) covers asset
loaders. They are ideal for static sites pulling data from sheets at build time.
:::
:::note
This demo was tested against the following Webpack versions:
| Version | Date | Required Workarounds |
|:---------|:-----------|:------------------------------------|
| `2.7.0` | 2023-10-17 | Import `xlsx/dist/xlsx.full.min.js` |
| `3.12.0` | 2023-10-17 | Import `xlsx/dist/xlsx.full.min.js` |
| `4.47.0` | 2023-10-17 | Downgrade NodeJS (tested v16.20.2) |
| `5.89.0` | 2023-10-17 | |
:::
## Integration Details
[The "Frameworks" section](/docs/getting-started/installation/frameworks) covers
installation with Yarn and other package managers.
After installing the SheetJS module in a Webpack 5 project, `import` statements
and `require` expressions can load relevant parts of the library.
:::info pass
The ECMAScript Module build has no `require` or `import` statements and does
not use `process` or any variable that Webpack could interpret as a NodeJS
feature. Various `package.json` fields have been added to appease various
Webpack versions starting from the `2.x` series.
:::
Projects that import data will use methods such as `read`[^1] to parse workbooks
and `sheet_to_json`[^2] to generate usable data from files. As `sheet_to_json`
is part of the `utils` object, the required import is:
```js
import { read, utils } from 'xlsx';
```
Projects that export data will use methods such as `json_to_sheet`[^3] to
generate worksheets and `writeFile`[^4] to export files. As `json_to_sheet` is
part of the `utils` object, the required import is:
```js
import { utils, writeFile } from 'xlsx';
```
:::tip pass
The `writeFileXLSX` function is a small version of `writeFile` that exclusively
supports generating XLSX spreadsheets. When the application only allows XLSX
exports, `writeFileXLSX` will reduce the final page size.
:::
### CommonJS and ESM
:::info pass
Webpack bundled the CommonJS build in older versions of the library. Version
`0.18.1` changed the NodeJS module package so that Webpack uses the ESM build.
:::
The CommonJS build includes the codepage support library for XLS processing.
The ESM build does not include the codepage support library.
[As described in the installation instructions](/docs/getting-started/installation/frameworks),
the codepage dependency should be imported explicitly:
```js
import * as XLSX from 'xlsx';
import * as cptable from 'xlsx/dist/cpexcel.full.mjs';
set_cptable(cptable);
```
### Legacy Webpack
:::caution pass
Some older webpack projects will throw an error in the browser:
```
require is not defined (xlsx.mjs)
```
This was a bug in Webpack and affected projects built with `create-react-app`.
If upgrading Webpack is not feasible, explicitly import the standalone script:
```js
import * as XLSX from 'xlsx/dist/xlsx.full.min.js';
```
:::
## Complete Example
0) Initialize a new project:
```bash
mkdir sheetjs-webpack
cd sheetjs-webpack
npm init -y
```
1) Install the tarball using a package manager:
<Tabs groupId="pm">
<TabItem value="npm" label="npm">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="pnpm" label="pnpm">
<CodeBlock language="bash">{`\
pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="yarn" label="Yarn" default>
<CodeBlock language="bash">{`\
yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
</Tabs>
2) Save the following to `index.js`:
```js title="index.js"
// highlight-next-line
import { utils, version, writeFileXLSX } from 'xlsx';
document.getElementById("xport").addEventListener("click", function() {
/* fetch JSON data and parse */
var url = "https://sheetjs.com/data/executive.json";
fetch(url).then(function(res) { return res.json(); }).then(function(raw_data) {
/* filter for the Presidents */
var prez = raw_data.filter(function(row) { return row.terms.some(function(term) { return term.type === "prez"; }); });
/* sort by first presidential term */
prez.forEach(function(row) {
row.start = row.terms.find(function(term) {
return term.type === "prez";
}).start
});
prez.sort(function(l,r) { return l.start.localeCompare(r.start); });
/* flatten objects */
var rows = prez.map(function(row) { return {
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}; });
/* generate worksheet and workbook */
var worksheet = utils.json_to_sheet(rows);
var 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 */
var max_width = rows.reduce(function(w, r) { return 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");
});
});
```
:::info pass
The minifier that ships with Webpack 2.x does not handle `async` functions or
ES6 arrow functions.
To demonstrate compatibility with older versions of Webpack, the `index.js`
script uses normal functions and traditional Promise chains.
:::
3) Create a small `webpack.config.js` script that writes to `index.min.js`:
```js title="webpack.config.js"
module.exports = {
/* entry point index.js */
entry: './index.js',
/* write to index.min.js */
output: { path:__dirname, filename: './index.min.js' }
}
```
4) Build for production. The command depends on the version of webpack:
<Tabs>
<TabItem value="23" label="2.x and 3.x">
:::note pass
In Webpack 2.x and 3.x, the import statement must use the standalone script.
Replace the import statement in `index.js` with the following:
```js title="index.js (replace import statement)"
import { utils, version, writeFileXLSX } from 'xlsx/dist/xlsx.full.min.js';
```
:::
This line must be changed before bundling.
**Webpack 2.x**
```bash
npx webpack@2.x -p
```
**Webpack 3.x**
```bash
npx webpack@3.x -p
```
</TabItem>
<TabItem value="4+" label="4.x, 5.x and beyond" default>
:::warning Pinning specific versions of webpack
The webpack tooling is not designed for switching between versions. A specific
version above 4.0 can be pinned by locally installing webpack and the CLI tool.
:::
**Webpack 4.x**
:::info pass
Webpack 4 is incompatible with Node 18+. When this demo was last tested, NodeJS
was locally downgraded to 16.20.2
:::
```bash
npm i --save webpack@4.x webpack-cli@4.x
npx webpack --mode=production
```
**Webpack 5.x**
```bash
npm i --save webpack@5.x webpack-cli@5.x
npx webpack --mode=production
```
**Webpack latest**
```bash
npm i --save webpack webpack-cli
npx webpack --mode=production
```
</TabItem>
</Tabs>
5) Create a small HTML page that loads the script. Save to `index.html`:
```html title="index.html"
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<h1>SheetJS Presidents Demo</h1>
<button id="xport">Click here to export</button>
<script src="./index.min.js"></script>
</body>
</html>
```
6) Start a local HTTP server and go to `http://localhost:8080/`
```bash
npx http-server .
```
Click on "Click here to export" to generate a file.
## Miscellany
[^1]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^2]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^3]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
[^4]: See [`writeFile` in "Writing Files"](/docs/api/write-options)

@ -0,0 +1,5 @@
{
"label": "Bundlers",
"collapsed": false,
"position": 19
}

@ -2,7 +2,7 @@
title: Bundlers
pagination_prev: demos/index
pagination_next: demos/grid/index
sidebar_position: 8
sidebar_position: 19
sidebar_custom_props:
skip: 1
---
@ -12,10 +12,9 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
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.
SheetJS predates ECMAScript modules and most bundler tools. As best practices
have evolved, stress testing SheetJS libraries have revealed bugs in bundlers
and other tools. This demo collects various notes and provides basic examples.
:::note pass
@ -1276,243 +1275,9 @@ loaders. They are ideal for static sites pulling data from sheets at build time.
:::
## Webpack
#### Webpack
The ECMAScript Module build has no `require` or `import` statements and does
not use `process` or any variable that Webpack could interpret as a NodeJS
feature. Various `package.json` fields have been added to appease various
Webpack versions starting from the `2.x` series.
:::note CommonJS and ESM
Webpack bundled the CommonJS build in older versions of the library. Version
`0.18.1` changed the NodeJS module package so that Webpack uses the ESM build.
The ESM build does not include the codepage support library for XLS reading.
[As described in the installation instructions](/docs/getting-started/installation/frameworks),
the codepage dependency should be imported explicitly:
```js
import * as XLSX from 'xlsx';
import * as cptable from 'xlsx/dist/cpexcel.full.mjs';
set_cptable(cptable);
```
:::
:::caution pass
Some older webpack projects will throw an error in the browser:
```
require is not defined (xlsx.mjs)
```
This was a bug in Webpack and affected projects built with `create-react-app`.
If upgrading Webpack is not feasible, explicitly import the standalone script:
```js
import * as XLSX from 'xlsx/dist/xlsx.full.min.js';
```
:::
<details><summary><b>Complete Example</b> (click to show)</summary>
:::note
This demo was tested against the following Webpack versions:
| Version | Date | Required Workarounds |
|:---------|:-----------|:------------------------------------|
| `2.7.0` | 2023-05-07 | Import `xlsx/dist/xlsx.full.min.js` |
| `3.12.0` | 2023-05-07 | Import `xlsx/dist/xlsx.full.min.js` |
| `4.46.0` | 2023-05-07 | Downgrade NodeJS (tested v16.20.0) |
| `5.82.0` | 2023-05-07 | |
:::
1) Install the tarball using a package manager:
<Tabs groupId="pm">
<TabItem value="npm" label="npm">
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="pnpm" label="pnpm">
<CodeBlock language="bash">{`\
pnpm install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="yarn" label="Yarn" default>
<CodeBlock language="bash">{`\
yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
</Tabs>
2) Save the following to `index.js`:
```js title="index.js"
// highlight-next-line
import { utils, version, writeFileXLSX } from 'xlsx';
document.getElementById("xport").addEventListener("click", function() {
/* fetch JSON data and parse */
var url = "https://sheetjs.com/data/executive.json";
fetch(url).then(function(res) { return res.json(); }).then(function(raw_data) {
/* filter for the Presidents */
var prez = raw_data.filter(function(row) { return row.terms.some(function(term) { return term.type === "prez"; }); });
/* flatten objects */
var rows = prez.map(function(row) { return {
name: row.name.first + " " + row.name.last,
birthday: row.bio.birthday
}; });
/* generate worksheet and workbook */
var worksheet = utils.json_to_sheet(rows);
var 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 */
var max_width = rows.reduce(function(w, r) { return 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");
});
});
```
3) Create a small `webpack.config.js` script that writes to `index.min.js`:
```js title="webpack.config.js"
module.exports = {
/* entry point index.js */
entry: './index.js',
/* write to index.min.js */
output: { path:__dirname, filename: './index.min.js' }
}
```
4) Build for production. The command depends on the version of webpack:
<Tabs>
<TabItem value="23" label="2.x and 3.x">
:::note pass
In Webpack 2.x and 3.x, the import statement must use the standalone script:
```js title="index.js"
// highlight-next-line
import { utils, version, writeFileXLSX } from 'xlsx/dist/xlsx.full.min.js';
```
:::
This line must be changed before bundling.
**Webpack 2.x**
```bash
npx webpack@2.x -p
```
**Webpack 3.x**
```bash
npx webpack@3.x -p
```
:::caution pass
The minifier that ships with Webpack 2.x does not handle `async` functions. The
unminified code generated by Webpack will work for the purposes of the demo. It
is strongly recommended to upgrade to a newer version of Webpack. If that is
not feasible, the example should be replaced with a traditional Promise chain.
:::
</TabItem>
<TabItem value="4+" label="4.x, 5.x and beyond" default>
:::warning Pinning specific versions of webpack
The webpack tooling is not designed for switching between versions. A specific
version above 4.0 can be pinned by locally installing webpack and the CLI tool.
:::
**Webpack 4.x**
:::info pass
Webpack 4 is incompatible with Node 18+. When this demo was last tested, NodeJS
was locally downgraded to 16.20.0
:::
```bash
npm i --save webpack@4.x webpack-cli@4.x
npx webpack --mode=production
```
**Webpack 5.x**
```bash
npm i --save webpack@5.x webpack-cli@5.x
npx webpack --mode=production
```
**Webpack latest**
```bash
npm i --save webpack webpack-cli
npx webpack --mode=production
```
</TabItem>
</Tabs>
5) Create a small HTML page that loads the script. Save to `index.html`:
```html title="index.html"
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<h1>SheetJS Presidents Demo</h1>
<button id="xport">Click here to export</button>
<script src="./index.min.js"></script>
</body>
</html>
```
6) Start a local HTTP server and go to `http://localhost:8080/`
```bash
npx http-server .
```
Click on "Click here to export" to generate a file.
</details>
:::note pass
The [Webpack section of the Content demo](/docs/demos/static/webpack) covers asset
loaders. They are ideal for static sites pulling data from sheets at build time.
:::
**[The exposition has been moved to a separate page.](/docs/demos/frontend/bundler/webpack)**
## WMR

@ -54,3 +54,14 @@ final website that can be deployed.
[The "Bundler" subsection](/docs/demos/frontend/bundler) covers a number of
common bundlers and build tools.
The following demos are in separate pages:
<ul>{useCurrentSidebarCategory().items.filter(item => item.label == "Bundlers")[0].items.map((item, index) => {
const listyle = (item.customProps?.icon) ? {
listStyleImage: `url("${item.customProps.icon}")`
} : {};
return (<li style={listyle} {...(item.customProps?.class ? {className: item.customProps.class}: {})}>
<a href={item.href}>{item.label}</a>{item.customProps?.summary && (" - " + item.customProps.summary)}
</li>);
})}</ul>

@ -0,0 +1,171 @@
---
title: Sheets in Elysia
sidebar_label: ElysiaJS
pagination_prev: demos/net/network
pagination_next: demos/net/email
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
[Elysia](https://elysiajs.com/) is a BunJS server-side framework.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses ElysiaJS and SheetJS to read and write data. We'll explore how to
parse uploaded files in a POST request handler and respond to GET requests with
downloadable spreadsheets.
The ["Complete Example"](#complete-example) section includes a complete server.
:::note
This demo was last tested on 2023 October 17 against ElysiaJS 0.7.17 and BunJS 1.0.6.
:::
## Integration Details
The [SheetJS BunJS module](/docs/getting-started/installation/bun) can be
imported from ElysiaJS server scripts.
### Reading Data
The ElysiaJS body parser accepts a schema[^1]. The `t.File` method marks that a
field is expected to be a file.
The following schema indicates the field `upload` should be a submitted file:
```js
{
body: t.Object({
upload: t.File()
})
}
```
ElysiaJS exposes the file as a `Blob` object. The `Blob#arrayBuffer` method
returns a Promise that resolves to an `ArrayBuffer`. That `ArrayBuffer` can be
parsed with the SheetJS `read` method[^2].
This example server responds to POST requests. The server will look for a file
in the request body under the `"upload"` key. If a file is present, the server
will parse the file, generate an HTML table using the `sheet_to_html` method[^3]
and respond with the HTML code:
```js
import { Elysia, t } from "elysia";
import { read, utils } from "xlsx";
const app = new Elysia();
app.post("/", async({ body: { upload } }) => {
// highlight-start
const data = await upload.arrayBuffer();
const wb = read(data);
// highlight-end
return utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]);
}, {
body: t.Object({
// highlight-next-line
upload: t.File()
})
});
app.listen(3000);
```
### Writing Data
Given a SheetJS workbook object, the `write` method using `type: "buffer"`[^4]
generates data objects which can be passed to the BunJS `File` constructor. The
`File` constructor accepts a second parameter for the generated file name.
This example server responds to GET requests. The server will generate a SheetJS
worksheet object from an array of arrays[^5], build up a new workbook using the
`book_new`[^6] and `book_append_sheet`[^7] utility methods, generate a XLSX file
using `write`, and send the file with appropriate headers to initiate a download
with file name `SheetJSElysia.xlsx`:
```js
import { Elysia } from "elysia";
import { utils, write } from "xlsx";
const app = new Elysia();
app.get("/", () => {
var ws = utils.aoa_to_sheet(["SheetJS".split(""), [5,4,3,3,7,9,5]]);
var wb = utils.book_new(); utils.book_append_sheet(wb, ws, "Data");
// highlight-start
/* generate buffer */
var buf = write(wb, {type: "buffer", bookType: "xlsx"});
return new File([buf], "SheetJSElysia.xlsx");
// highlight-end
});
app.listen(3000);
```
## Complete Example
1) Create a new ElysiaJS project:
```bash
bun create elysia sheetjs-elysia
cd sheetjs-elysia
```
2) Install the [SheetJS BunJS module](/docs/getting-started/installation/bun):
<CodeBlock language="bash">{`\
bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
3) Save the following script to `src/SheetJSElysia.ts`:
```ts title="src/SheetJSElysia.ts"
import { Elysia, t } from "elysia";
import { read, utils, write } from "xlsx";
const app = new Elysia();
app.get("/", () => {
var ws = utils.aoa_to_sheet(["SheetJS".split(""), [5,4,3,3,7,9,5]]);
var wb = utils.book_new(); utils.book_append_sheet(wb, ws, "Data");
/* generate buffer */
var buf = write(wb, {type: "buffer", bookType: "xlsx"});
return new File([buf], "SheetJSElysia.xlsx");
/* set headers */
});
app.post("/", async({ body: { upload } }) => {
const data = await upload.arrayBuffer();
const wb = read(data);
return utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]);
}, {
body: t.Object({
upload: t.File()
})
});
app.listen(3000);
```
4) Run the server:
```bash
bun run src/SheetJSElysia.ts
```
5) Test POST requests using <https://sheetjs.com/pres.numbers>:
```bash
curl -LO https://sheetjs.com/pres.numbers
curl -X POST -F upload=@pres.numbers http://localhost:3000/
```
6) Test GET requests by opening `http://localhost:3000/` in your browser.
The page should attempt to download `SheetJSElysia.xlsx` . Open the new file.
[^1]: See ["Explicit Body"](https://elysiajs.com/concept/explicit-body.html) in the ElysiaJS documentation.
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^3]: See [`sheet_to_html` in "Utilities"](/docs/api/utilities/html#html-table-output)
[^4]: See [`write` in "Writing Files"](/docs/api/write-options)
[^5]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input)
[^6]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)
[^7]: See [`book_append_sheet` in "Utilities"](/docs/api/utilities/wb)

@ -225,6 +225,14 @@ This will generate `SheetJSPool.xlsx`.
## Other Platforms
### Bun
Bun provides the basic elements to implement a web server.
#### ElysiaJS
**[The exposition has been moved to a separate page.](/docs/demos/net/server/elysia)**
### Deno
:::caution pass

@ -26,7 +26,7 @@ XLSX spreadsheet.
:::info pass
This demo covers static asset imports. For processing files in the browser, the
["Bundlers" demo](/docs/demos/frontend/bundler#webpack) includes an example of
["Bundlers" demo](/docs/demos/frontend/bundler/webpack) includes an example of
importing the SheetJS library in a browser script.
:::

@ -126,7 +126,7 @@ in the [issue tracker](https://git.sheetjs.com/sheetjs/docs.sheetjs.com/issues)
- [`swc`](/docs/demos/frontend/bundler#swc)
- [`systemjs`](/docs/demos/frontend/bundler#systemjs)
- [`vite`](/docs/demos/frontend/bundler#vite)
- [`webpack`](/docs/demos/frontend/bundler#webpack)
- [`webpack`](/docs/demos/frontend/bundler/webpack)
- [`wmr`](/docs/demos/frontend/bundler#wmr)
### Other Programming Languages