From b230d64dd6792dfb5c64f5bae37666386b271189 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Tue, 17 Oct 2023 22:07:06 -0400 Subject: [PATCH] bundler --- .../01-frontend/19-bundler/05-webpack.md | 320 ++++++++++++++++++ .../01-frontend/19-bundler/_category_.json | 5 + .../{08-bundler.md => 19-bundler/index.md} | 247 +------------- docz/docs/03-demos/01-frontend/index.md | 11 + .../03-demos/03-net/02-server/09-elysia.md | 171 ++++++++++ docz/docs/03-demos/03-net/02-server/index.md | 8 + docz/docs/03-demos/04-static/06-webpack.md | 2 +- docz/docs/03-demos/index.md | 2 +- 8 files changed, 523 insertions(+), 243 deletions(-) create mode 100644 docz/docs/03-demos/01-frontend/19-bundler/05-webpack.md create mode 100644 docz/docs/03-demos/01-frontend/19-bundler/_category_.json rename docz/docs/03-demos/01-frontend/{08-bundler.md => 19-bundler/index.md} (84%) create mode 100644 docz/docs/03-demos/03-net/02-server/09-elysia.md diff --git a/docz/docs/03-demos/01-frontend/19-bundler/05-webpack.md b/docz/docs/03-demos/01-frontend/19-bundler/05-webpack.md new file mode 100644 index 0000000..c9de4e5 --- /dev/null +++ b/docz/docs/03-demos/01-frontend/19-bundler/05-webpack.md @@ -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: + + + +{`\ +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`} + + + + +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: + + + + +:::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 +``` + + + + +:::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 +``` + + + + + +5) Create a small HTML page that loads the script. Save to `index.html`: + +```html title="index.html" + + + + +

SheetJS Presidents Demo

+ + + + +``` + +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) \ No newline at end of file diff --git a/docz/docs/03-demos/01-frontend/19-bundler/_category_.json b/docz/docs/03-demos/01-frontend/19-bundler/_category_.json new file mode 100644 index 0000000..3f21321 --- /dev/null +++ b/docz/docs/03-demos/01-frontend/19-bundler/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Bundlers", + "collapsed": false, + "position": 19 +} \ No newline at end of file diff --git a/docz/docs/03-demos/01-frontend/08-bundler.md b/docz/docs/03-demos/01-frontend/19-bundler/index.md similarity index 84% rename from docz/docs/03-demos/01-frontend/08-bundler.md rename to docz/docs/03-demos/01-frontend/19-bundler/index.md index 98ff783..d13f063 100644 --- a/docz/docs/03-demos/01-frontend/08-bundler.md +++ b/docz/docs/03-demos/01-frontend/19-bundler/index.md @@ -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'; -``` - -::: - -
Complete Example (click to show) - -:::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: - - - -{`\ -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`} - - - - -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: - - - - -:::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. - -::: - - - - -:::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 -``` - - - - - -5) Create a small HTML page that loads the script. Save to `index.html`: - -```html title="index.html" - - - - -

SheetJS Presidents Demo

- - - - -``` - -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. - -
- -:::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 diff --git a/docz/docs/03-demos/01-frontend/index.md b/docz/docs/03-demos/01-frontend/index.md index 3b7eb3a..5a459bd 100644 --- a/docz/docs/03-demos/01-frontend/index.md +++ b/docz/docs/03-demos/01-frontend/index.md @@ -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: + + diff --git a/docz/docs/03-demos/03-net/02-server/09-elysia.md b/docz/docs/03-demos/03-net/02-server/09-elysia.md new file mode 100644 index 0000000..ee0925f --- /dev/null +++ b/docz/docs/03-demos/03-net/02-server/09-elysia.md @@ -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): + +{`\ +bun install https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} + + +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 : + +```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) diff --git a/docz/docs/03-demos/03-net/02-server/index.md b/docz/docs/03-demos/03-net/02-server/index.md index 4f27f50..4567d8e 100644 --- a/docz/docs/03-demos/03-net/02-server/index.md +++ b/docz/docs/03-demos/03-net/02-server/index.md @@ -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 diff --git a/docz/docs/03-demos/04-static/06-webpack.md b/docz/docs/03-demos/04-static/06-webpack.md index 8026fa5..42fd958 100644 --- a/docz/docs/03-demos/04-static/06-webpack.md +++ b/docz/docs/03-demos/04-static/06-webpack.md @@ -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. ::: diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md index af80d75..d7fd84e 100644 --- a/docz/docs/03-demos/index.md +++ b/docz/docs/03-demos/index.md @@ -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