diff --git a/docz/data/cli.xls b/docz/data/cli.xls index bcc547f..f3f3b94 100644 --- a/docz/data/cli.xls +++ b/docz/data/cli.xls @@ -86,16 +86,16 @@ NodeJS SEA - + - Bun SEA + BunJS SEA + - diff --git a/docz/docs/03-demos/12-static/05-vitejs.md b/docz/docs/03-demos/12-static/05-vitejs.md index f58562a..a9bb7f3 100644 --- a/docz/docs/03-demos/12-static/05-vitejs.md +++ b/docz/docs/03-demos/12-static/05-vitejs.md @@ -12,15 +12,15 @@ sidebar_custom_props: import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; -[ViteJS](https://vitejs.dev/) is a modern build tool for generating static sites. -It has a robust JavaScript-powered plugin system[^1] +[ViteJS](https://vitejs.dev/) is a build tool for generating static websites. It +has a robust JavaScript-powered plugin system[^1]. [SheetJS](https://sheetjs.com) 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. +plugin and evaluate data loading strategies. The ["Complete Demo"](#complete-demo) section creates a complete website powered by a XLSX spreadsheet. @@ -32,16 +32,97 @@ suitable for end of week or end of month (EOM) reports published in HTML tables. For processing user-submitted files in the browser, the [ViteJS "Bundlers" demo](/docs/demos/frontend/bundler/vitejs) shows client-side -bundling of the SheetJS library. The ["ReactJS" demo](/docs/demos/frontend/react) +bundling of SheetJS libraries. The ["ReactJS" demo](/docs/demos/frontend/react) shows example sites using ViteJS with the ReactJS starter. ::: ## Plugins -ViteJS supports static asset imports[^2], 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. +ViteJS supports static asset imports[^2], but the default raw loader interprets +data as UTF-8 strings. This corrupts binary formats including XLSX and XLS. A +custom loader can bypass the raw loader and directly read files. + +Since a custom loader must be used, some data processing work can be performed +by the loader. Three approaches are explored in this demo. + +The following diagrams show the ViteJS data flow. The pink "Main Script import" +boxes represent the division between the loader and the main script. The green +"SheetJS Operations" boxes represent the steps performed by SheetJS libraries. + + + + + + + + + + + + +
[HTML](#html-plugin)[Data](#pure-data-plugin)[Base64](#base64-plugin)
+ +```mermaid +flowchart TB + file[(workbook\nfile)] + buffer(NodeJS\nBuffer) + sheetjs[[SheetJS Operations]] + tabeller{{HTML\nString}} + handoff[[Main Script import]] + html{{HTML\nTABLE}} + style handoff fill:#FFC7CE + style sheetjs fill:#C6EFCE + file --> buffer + buffer --> sheetjs + sheetjs --> tabeller + tabeller --> handoff + handoff --------> html +``` + + + +```mermaid +flowchart TB + file[(workbook\nfile)] + buffer(NodeJS\nBuffer) + sheetjs[[SheetJS Operations]] + aoo(array of\nobjects) + handoff[[Main Script import]] + import(array of\nobjects) + html{{HTML\nTABLE}} + style handoff fill:#FFC7CE + style sheetjs fill:#C6EFCE + file --> buffer + buffer --> sheetjs + sheetjs --> aoo + aoo --> handoff + handoff ------> import + import --> html +``` + + + +```mermaid +flowchart TB + file[(workbook\nfile)] + base64(Base64\nString) + handoff[[Main Script import]] + import(Base64\nString) + sheetjs[[SheetJS Operations]] + aoo(array of\nobjects) + html{{HTML\nTABLE}} + style handoff fill:#FFC7CE + style sheetjs fill:#C6EFCE + file --> base64 + base64 ------> handoff + handoff --> import + import --> sheetjs + sheetjs --> aoo + aoo --> html +``` + +
For simple tables of data, ["Pure Data Plugin"](#pure-data-plugin) is strongly recommended. The file processing is performed at build time and the generated @@ -51,6 +132,9 @@ For more complex parsing or display logic, ["Base64 Plugin"](#base64-plugin) is preferable. Since the raw parsing logic is performed in the page, the library will be included in the final bundle. +The ["HTML Plugin"](#html-plugin) generates HTML in the loader script. The +SheetJS HTML writer renders merged cells and other features. + ### Pure Data Plugin For a pure static site, a plugin can load data into an array of row objects. The @@ -72,7 +156,7 @@ flowchart LR ``` This ViteJS plugin will read spreadsheets using the SheetJS `read` method[^3] -and generate arrays of row objects with `sheet_to_json`[^4]: +and generate arrays of row objects with the SheetJS `sheet_to_json`[^4] method: ```js title="vite.config.js" import { readFileSync } from 'fs'; @@ -89,13 +173,23 @@ export default defineConfig({ 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)}')`; + return `export default JSON.parse('${JSON.stringify(data).replace(/\\/g, "\\\\")}')`; } } ] }); ``` +:::info pass + +ViteJS plugins are expected to return strings representing ECMAScript modules. + +The plugin uses `JSON.stringify` to encode the array of objects. The generated +string is injected into the new module code. When ViteJS processes the module, +`JSON.parse` recovers the array of objects. + +::: + In frontend code, the loader will look for all modules with a `?sheetjs` query string. The default export is an array of row objects. @@ -115,9 +209,75 @@ document.querySelector('#app').innerHTML = `
`; ``` +### HTML Plugin + +A plugin can generate raw HTML strings that can be added to a page. The SheetJS +libraries are used in the plugin but will not be added to the site. + +The following diagram depicts the workbook waltz: + +```mermaid +flowchart LR + file[(workbook\nfile)] + subgraph SheetJS operations + buffer(NodeJS\nBuffer) + tavolo{{HTML\nString}} + end + html{{HTML\nTABLE}} + file --> |vite.config.js\ncustom plugin| buffer + buffer --> |vite.config.js\ncustom plugin| tavolo + tavolo --> |main.js\nfrontend code| html +``` + +This ViteJS plugin will read spreadsheets using the SheetJS `read` method[^5] +and generate HTML using the SheetJS `sheet_to_html`[^6] method: + +```js title="vite.config.js" +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 ?html tags + name: "vite-sheet-html", + transform(code, id) { + if(!id.match(/\?html/)) return; + var wb = read(readFileSync(id.replace(/\?html/, ""))); + var html = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); + return (`export default JSON.parse('${JSON.stringify(html).replace(/\\/g, "\\\\")}')`); + } + } + ] +}); +``` + +:::info pass + +ViteJS plugins are expected to return strings representing ECMAScript modules. + +The plugin uses `JSON.stringify` to encode the HTML string. The generated string +is injected into the new module code. When ViteJS processes the module, +`JSON.parse` recovers the original HTML string. + +::: + +In frontend code, the loader will look for all modules with a `?html` query +string. The default export is a string that can be directly added to the page. + +The following example script sets the `innerHTML` property of the container: + +```js title="main.js" +import html from './data/pres.xlsx?html'; + +document.querySelector('#app').innerHTML = html; +``` + ### Base64 Plugin -This plugin pulls in data as a Base64 string that can be read with `read`[^5]. +This plugin pulls in data as a Base64 string that can be read with `read`[^7]. While this approach works, it is not recommended since it loads the library in the front-end site. @@ -161,7 +321,7 @@ export default defineConfig({ ``` When importing using the `b64` query, the raw Base64 string will be exposed. -`read` will process the Base64 string using the `base64` input type[^6]: +`read` will process the Base64 string using the `base64` input type[^8]: ```js title="main.js" import { read, utils } from "xlsx"; @@ -187,22 +347,22 @@ document.querySelector('#app').innerHTML = ` ## Complete Demo +The demo walks through the process of creating a new ViteJS website from scratch. +A Git repository with the completed site can be cloned[^9]. + :::note Tested Deployments This demo was tested in the following environments: | ViteJS | Date | |:---------|:-----------| -| `5.0.5` | 2023-12-04 | -| `4.5.0` | 2023-12-04 | -| `3.2.7` | 2023-12-04 | -| `2.9.16` | 2023-12-04 | +| `5.2.12` | 2024-06-02 | +| `4.5.3` | 2024-06-02 | +| `3.2.10` | 2024-06-02 | +| `2.9.18` | 2024-06-02 | ::: -The demo walks through the process of creating a new ViteJS website from scratch. -A Git repository with the completed site can be cloned[^7]. - ### Initial Setup 1) Create a new site with the `vue-ts` template and install the SheetJS package: @@ -275,9 +435,30 @@ npx http-server dist/ The terminal will display a URL, typically `http://127.0.0.1:8080` . Access that page with a web browser. +:::caution pass + +When this demo was tested against ViteJS `2.9.18`, the build failed: + +``` +src/App.vue:8:3 - error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. + +8 Vue logo + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` + +**As it affects the project template, this is a bug in ViteJS.** + +The simplest workaround is to force upgrade the `vue-tsc` dependency: + +```bash +npm i vue-tsc@latest +``` + +::: + 7) 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. +source. The code will reference a script `/assets/index-HASH.js` where `HASH` is +a string of characters. Open that script. Searching for `Bill Clinton` reveals the following: @@ -291,11 +472,11 @@ included in the final site! :::info pass 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 docs[^8]. +would be added to the final page. Details are covered in the ViteJS docs[^10]. ::: -### Base64 Test +### HTML Test 8) Run the dev server: @@ -303,10 +484,88 @@ would be added to the final page. Details are covered in the ViteJS docs[^8]. npm run dev ``` -Open a browser window to the displayed URL. +Open a browser window to the displayed URL (typically `http://localhost:5173` ) 9) Replace the component `src/components/HelloWorld.vue` with: +```html title="src/components/HelloWorld.vue" + + + +``` + +Save and refresh the page. A data table should be displayed + +10) Stop the dev server and build the site + +```bash +npm run build +npx http-server dist/ +``` + +The terminal will display a URL, typically `http://127.0.0.1:8080` . Access +that page with a web browser. + +:::caution pass + +When this demo was tested against ViteJS `2.9.18`, the build failed: + +``` +src/App.vue:8:3 - error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. + +8 Vue logo + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` + +**As it affects the project template, this is a bug in ViteJS.** + +The simplest workaround is to force upgrade the `vue-tsc` dependency: + +```bash +npm i vue-tsc@latest +``` + +::: + +11) To confirm that only the raw HTML is present in the page, view the page +source. The code will reference a script `/assets/index-HASH.js` where `HASH` is +a string of characters. Open that script. + +Searching for `Bill Clinton` reveals the following encoded HTML element: + +``` + +``` + +Searching for `BESSELJ` should reveal no results. The SheetJS scripts are not +included in the final site! + +:::info pass + +The HTML code is still stored in a script and is injected dynamically. + +ViteJS "Server-Side Rendering" offers the option to render the site at build +time, ensuring that the HTML table is directly added to the page. + +::: + +### Base64 Test + +12) Run the dev server: + +```bash +npm run dev +``` + +Open a browser window to the displayed URL (typically `http://localhost:5173` ) + +13) Replace the component `src/components/HelloWorld.vue` with: + ```html title="src/components/HelloWorld.vue" + + +EOF + +npm run build +npm ls | grep "vite@" +node test.cjs +# Expected output: CSV contents of first sheet + +echo "Clinton" $(grep Clinton dist/assets/*.js | wc -l) "BESSELJ" $(grep BESSELJ dist/assets/*.js | wc -l) +# Expected output: Clinton 1 BESSELJ 0 + +## HTML Test +cat >src/components/HelloWorld.vue < +// @ts-ignore +import html from '../../data/pres.xlsx?html'; + + + +EOF + +npm run build +npm ls | grep "vite@" +node test.cjs +# Expected output: CSV contents of first sheet + +echo "Clinton" $(grep Clinton dist/assets/*.js | wc -l) "BESSELJ" $(grep BESSELJ dist/assets/*.js | wc -l) +# Expected output: Clinton 1 BESSELJ 0 + +## Base64 Test +cat >src/components/HelloWorld.vue < +// @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(ws); + + + +EOF + +npm run build +npm ls | grep "vite@" +node test.cjs +# Expected output: CSV contents of first sheet + +echo "Clinton" $(grep Clinton dist/assets/*.js | wc -l) "BESSELJ" $(grep BESSELJ dist/assets/*.js | wc -l) +# Expected output: Clinton 0 BESSELJ 1 + +cd - +done
Bill Clinton