diff --git a/docz/docs/03-demos/20-content.md b/docz/docs/03-demos/20-content.md index 64f91f8..8eca7ca 100644 --- a/docz/docs/03-demos/20-content.md +++ b/docz/docs/03-demos/20-content.md @@ -21,6 +21,310 @@ in the parsing logic, issues should then be raised with the SheetJS project. ::: +## NextJS + +:::note + +This was tested against `next v12.2.5` on 2022 August 16. + +::: + +:::caution + +At a high level, there are two ways to pull spreadsheet data into NextJS apps: +loading an asset module or performing the file read operations from the NextJS +lifecycle. At the time of writing, NextJS does not offer an out-of-the-box +asset module solution, so this demo focuses on raw operations. NextJS does not +watch the spreadsheets, so `next dev` hot reloading will not work! + +::: + +The general strategy with NextJS apps is to generate HTML snippets or data from +the lifecycle functions and reference them in the template. + +HTML output can be generated using `XLSX.utils.sheet_to_html` and inserted into +the document using the `dangerouslySetInnerHTML` attribute: + +```jsx +export default function Index({html, type}) { return ( + // ... +// highlight-next-line +
+ // ... +); } +``` + +:::warning + +`fs` cannot be statically imported from the top level in NextJS pages. The +dynamic import must happen within a lifecycle function. For example: + +```js +/* it is safe to import the library from the top level */ +import { readFile, utils, set_fs } from 'xlsx'; +/* it is not safe to import 'fs' from the top level ! */ +// import * as fs from 'fs'; // this will fail +import { join } from 'path'; +import { cwd } from 'process'; + +export async function getServerSideProps() { +// highlight-next-line + set_fs(await import("fs")); // dynamically import 'fs' when needed + const wb = readFile(join(cwd(), "public", "sheetjs.xlsx")); // works + // ... +} +``` + + + +::: + +### Strategies + +#### "Static Site Generation" using `getStaticProps` + +When using `getStaticProps`, the file will be read once during build time. + +```js +import { readFile, set_fs, utils } from 'xlsx'; + +export async function getStaticProps() { + /* read file */ + set_fs(await import("fs")); + const wb = readFile(path_to_file) + + /* generate and return the html from the first worksheet */ + const html = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); + return { props: { html } }; +}; +``` + +#### "Static Site Generation with Dynamic Routes" using `getStaticPaths` + +Typically a static site with dynamic routes has an endpoint `/sheets/[id]` that +implements both `getStaticPaths` and `getStaticProps`. + +- `getStaticPaths` should return an array of worksheet indices: + +```js +export async function getStaticPaths() { + /* read file */ + set_fs(await import("fs")); + const wb = readFile(path); + + /* generate an array of objects that will be used for generating pages */ + const paths = wb.SheetNames.map((name, idx) => ({ params: { id: idx.toString() } })); + return { paths, fallback: false }; +}; +``` + +:::note + +For a pure static site, `fallback` must be set to `false`! + +::: + +- `getStaticProps` will generate the actual HTML for each page: + +```js +export async function getStaticProps(ctx) { + /* read file */ + set_fs(await import("fs")); + const wb = readFile(path); + + /* get the corresponding worksheet and generate HTML */ + const ws = wb.Sheets[wb.SheetNames[ctx.params.id]]; // id from getStaticPaths + const html = utils.sheet_to_html(ws); + return { props: { html } }; +}; +``` + +#### "Server-Side Rendering" using `getServerSideProps` + +:::caution Do not use on a static site + +These routes require a NodeJS dynamic server. Static page generation will fail! + +`getStaticProps` and `getStaticPaths` support SSG. + +`getServerSideProps` is suited for NodeJS hosted deployments where the workbook +changes frequently and a static site is undesirable. + +::: + +When using `getServerSideProps`, the file will be read on each request. + +```js +import { readFile, set_fs, utils } from 'xlsx'; + +export async function getServerSideProps() { + /* read file */ + set_fs(await import("fs")); + const wb = readFile(path_to_file); + + /* generate and return the html from the first worksheet */ + const html = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); + return { props: { html } }; +}; +``` + +### Demo + +
Complete Example (click to show) + +0) Disable NextJS telemetry: + +```js +npx next telemetry disable +``` + +Confirm it is disabled by running + +```js +npx next telemetry status +``` + +1) Set up folder structure. At the end, a `pages` folder with a `sheets` + subfolder must be created. On Linux or macOS or WSL: + +```bash +mkdir -p pages/sheets/ +``` + +2) Download the [test file](pathname:///next/sheetjs.xlsx) and place in the + project root. On Linux or macOS or WSL: + +```bash +curl -LO https://docs.sheetjs.com/next/sheetjs.xlsx +``` + +3) Install dependencies: + +```bash +npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz next +``` + +4) Download test scripts: + +Download and place the following scripts in the `pages` subdirectory: + +- [`index.js`](pathname:///next/index.js) +- [`getServerSideProps.js`](pathname:///next/getServerSideProps.js) +- [`getStaticPaths.js`](pathname:///next/getStaticPaths.js) +- [`getStaticProps.js`](pathname:///next/getStaticProps.js) + +Download [`[id].js`](pathname:///next/%5Bid%5D.js) and place in the +`pages/sheets` subdirectory. + +:::caution Percent-Encoding in the script name + +The `[id].js` script must have the literal square brackets in the name. If your +browser saved the file to `%5Bid%5D.js`. rename the file. + +::: + +On Linux or macOS or WSL: + +```bash +cd pages +curl -LO https://docs.sheetjs.com/next/index.js +curl -LO https://docs.sheetjs.com/next/getServerSideProps.js +curl -LO https://docs.sheetjs.com/next/getStaticPaths.js +curl -LO https://docs.sheetjs.com/next/getStaticProps.js +cd sheets +curl -LOg 'https://docs.sheetjs.com/next/[id].js' +cd ../.. +``` + +5) Test the deployment: + +```bash +npx next +``` + +Open a web browser and access: + +- http://localhost:3000 landing page +- http://localhost:3000/getStaticProps shows data from the first sheet +- http://localhost:3000/getServerSideProps shows data from the first sheet +- http://localhost:3000/getStaticPaths shows a list (3 sheets) + +The individual worksheets are available at + +- http://localhost:3000/sheets/0 +- http://localhost:3000/sheets/1 +- http://localhost:3000/sheets/2 + +6) Stop the dev server and run a production build: + +```bash +npx next build +``` + +The final output will show a list of the routes and types: + +``` +Route (pages) Size First Load JS +┌ ○ / 551 B 81.7 kB +├ ○ /404 194 B 77.2 kB +├ λ /getServerSideProps 602 B 81.7 kB +├ ● /getStaticPaths 2.7 kB 83.8 kB +├ ● /getStaticProps 600 B 81.7 kB +└ ● /sheets/[id] (312 ms) 580 B 81.7 kB + ├ /sheets/0 + ├ /sheets/1 + └ /sheets/2 +``` + +As explained in the summary, the `/getStaticPaths` and `/getStaticProps` routes +are completely static. 3 `/sheets/#` pages were generated, corresponding to 3 +worksheets in the file. `/getServerSideProps` is server-rendered. + +7) Try to build a static site: + +```bash +npx next export +``` + +:::note The static export will fail! + +A static page cannot be generated at this point because `/getServerSideProps` +is still server-rendered. + +::: + +8) Remove `pages/getServerSideProps.js` and rebuild with `npx next build`. + +Inspecting the output, there should be no lines with the `λ` symbol: + +``` +Route (pages) Size First Load JS +┌ ○ / 551 B 81.7 kB +├ ○ /404 194 B 77.2 kB +├ ● /getStaticPaths 2.7 kB 83.8 kB +├ ● /getStaticProps 600 B 81.7 kB +└ ● /sheets/[id] (312 ms) 580 B 81.7 kB + ├ /sheets/0 + ├ /sheets/1 + └ /sheets/2 +``` + +9) Generate the static site: + +```bash +npx next export +``` + +The static site will be written to the `out` subdirectory, which can be hosted with + +```bash +npx http-server out +``` + +The command will start a local webserver on port 8080. + +
+ ## NuxtJS `@nuxt/content` is a file-based CMS for Nuxt, enabling static-site generation @@ -99,7 +403,7 @@ neatly with nested `v-for`: ### Nuxt Content Demo -
Complete Example (click to show) +
Complete Example (click to show) :::note diff --git a/docz/static/next/[id].js b/docz/static/next/[id].js new file mode 100644 index 0000000..7ad71e9 --- /dev/null +++ b/docz/static/next/[id].js @@ -0,0 +1,47 @@ +import Head from 'next/head'; +import { readFile, set_fs, utils } from 'xlsx'; +import { join } from 'path'; +import { cwd } from 'process'; + +export default function Index({type, html, name}) { return (
+ + + {`SheetJS Next.JS ${type} Demo`} + + +
+    

{`SheetJS Next.JS ${type} Demo`}

+ This demo reads from /sheetjs.xlsx

+ {name} +
+
+
); } + +let cache = []; + +export async function getStaticProps(ctx) { + if(!cache || !cache.length) { + set_fs(await import("fs")); + const wb = readFile(join(cwd(), "sheetjs.xlsx")); + cache = wb.SheetNames.map((name) => ({ name, sheet: wb.Sheets[name] })); + } + const entry = cache[ctx.params.id]; + return { + props: { + type: "getStaticPaths", + name: entry.name, + id: ctx.params.id.toString(), + html: entry.sheet ? utils.sheet_to_html(entry.sheet) : "", + }, + } +} + +export async function getStaticPaths() { + set_fs(await import("fs")); + const wb = readFile(join(cwd(), "sheetjs.xlsx")); + cache = wb.SheetNames.map((name) => ({ name, sheet: wb.Sheets[name] })); + return { + paths: wb.SheetNames.map((name, idx) => ({ params: { id: idx.toString() } })), + fallback: false, + }; +} diff --git a/docz/static/next/getServerSideProps.js b/docz/static/next/getServerSideProps.js new file mode 100644 index 0000000..139c752 --- /dev/null +++ b/docz/static/next/getServerSideProps.js @@ -0,0 +1,29 @@ +import Head from 'next/head'; +import { readFile, set_fs, utils } from 'xlsx'; +import { join } from 'path'; +import { cwd } from 'process'; + +export default function Index({type, html}) { return (
+ + + {`SheetJS Next.JS ${type} Demo`} + + +
+    

{`SheetJS Next.JS ${type} Demo`}

+ This demo reads from /sheetjs.xlsx

+ It generates HTML from the first sheet.

+
+
+
); } + +export async function getServerSideProps() { + set_fs(await import("fs")); + const wb = readFile(join(cwd(), "sheetjs.xlsx")) + return { + props: { + type: "getServerSideProps", + html: utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]), + }, + } +} diff --git a/docz/static/next/getStaticPaths.js b/docz/static/next/getStaticPaths.js new file mode 100644 index 0000000..09affec --- /dev/null +++ b/docz/static/next/getStaticPaths.js @@ -0,0 +1,34 @@ +import Head from 'next/head'; +import Link from "next/link"; +import { readFile, set_fs, utils } from 'xlsx'; +import { join } from 'path'; +import { cwd } from 'process'; + +export default function Index({type, snames}) { return (
+ + + {`SheetJS Next.JS ${type} Demo`} + + +
+    

{`SheetJS Next.JS ${type} Demo`}

+ This demo reads from /sheetjs.xlsx

+ Each worksheet maps to a path:

+ +
+
); } + +export async function getStaticProps() { + set_fs(await import("fs")); + const wb = readFile(join(cwd(), "sheetjs.xlsx")) + return { + props: { + type: "getStaticPaths", + snames: wb.SheetNames, + }, + } +} diff --git a/docz/static/next/getStaticProps.js b/docz/static/next/getStaticProps.js new file mode 100644 index 0000000..280322b --- /dev/null +++ b/docz/static/next/getStaticProps.js @@ -0,0 +1,29 @@ +import Head from 'next/head'; +import { readFile, set_fs, utils } from 'xlsx'; +import { join } from 'path'; +import { cwd } from 'process'; + +export default function Index({type, html}) { return (
+ + + {`SheetJS Next.JS ${type} Demo`} + + +
+    

{`SheetJS Next.JS ${type} Demo`}

+ This demo reads from /sheetjs.xlsx

+ It generates HTML from the first sheet.

+
+
+
); } + +export async function getStaticProps() { + set_fs(await import("fs")); + const wb = readFile(join(cwd(), "sheetjs.xlsx")) + return { + props: { + type: "getStaticProps", + html: utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]), + }, + } +} diff --git a/docz/static/next/index.js b/docz/static/next/index.js new file mode 100644 index 0000000..ff0a830 --- /dev/null +++ b/docz/static/next/index.js @@ -0,0 +1,16 @@ +import Head from 'next/head'; + +export default function Index() { return (
+ + + SheetJS Next.JS Demo + + +
+    

SheetJS Next.JS Demo

+ This demo reads from /sheetjs.xlsx

+ - getStaticProps

+ - getServerSideProps

+ - getStaticPaths
+
+
); } diff --git a/docz/static/next/sheetjs.xlsx b/docz/static/next/sheetjs.xlsx new file mode 100644 index 0000000..beb3b2d Binary files /dev/null and b/docz/static/next/sheetjs.xlsx differ