forked from sheetjs/docs.sheetjs.com
		
	next-asset-module
This commit is contained in:
		
							parent
							
								
									0acbc990f0
								
							
						
					
					
						commit
						ae138cc327
					
				| @ -7,13 +7,12 @@ sidebar_custom_props: | ||||
| --- | ||||
| 
 | ||||
| import current from '/version.js'; | ||||
| import Tabs from '@theme/Tabs'; | ||||
| import TabItem from '@theme/TabItem'; | ||||
| import CodeBlock from '@theme/CodeBlock'; | ||||
| 
 | ||||
| # NodeJS | ||||
| 
 | ||||
| import Tabs from '@theme/Tabs'; | ||||
| import TabItem from '@theme/TabItem'; | ||||
| 
 | ||||
| Tarballs are available on <https://cdn.sheetjs.com>. | ||||
| 
 | ||||
| <p><a href={`https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}>https://cdn.sheetjs.com/xlsx-{current}/xlsx-{current}.tgz</a> is the URL for version {current}</p> | ||||
| @ -158,3 +157,38 @@ XLSX.stream.set_readable(Readable); | ||||
| import * as cpexcel from 'xlsx/dist/cpexcel.full.mjs'; | ||||
| XLSX.set_cptable(cpexcel); | ||||
| ``` | ||||
| 
 | ||||
| #### NextJS | ||||
| 
 | ||||
| :::warning | ||||
| 
 | ||||
| `fs` cannot be imported from the top level in NextJS pages. This will not work: | ||||
| 
 | ||||
| ```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 ! */ | ||||
| // highlight-next-line | ||||
| import * as fs from 'fs'; // this import will fail | ||||
| set_fs(fs); | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| `fs` should be loaded with a dynamic import within a lifecycle function: | ||||
| 
 | ||||
| ```js title="index.js" | ||||
| /* it is safe to import the library from the top level */ | ||||
| import { readFile, utils, set_fs } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| 
 | ||||
| export async function getServerSideProps() { | ||||
| // highlight-next-line | ||||
|   set_fs(await import("fs")); // dynamically import 'fs' in `getServerSideProps` | ||||
|   const wb = readFile(join(cwd(), "public", "sheetjs.xlsx")); | ||||
|   // ... | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| The [NextJS demo](/docs/demos/static/nextjs) includes complete examples. | ||||
|  | ||||
| @ -5,61 +5,36 @@ pagination_next: demos/mobile/index | ||||
| --- | ||||
| 
 | ||||
| import current from '/version.js'; | ||||
| import Tabs from '@theme/Tabs'; | ||||
| import TabItem from '@theme/TabItem'; | ||||
| import CodeBlock from '@theme/CodeBlock'; | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This was tested against `next v13.1.1` on 2023 January 14. | ||||
| This was tested against `next v13.4.4` on 2023 May 26. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::info | ||||
| The [NodeJS module](/docs/getting-started/installation/nodejs) can be imported | ||||
| from pages or loaded in Webpack loaders. | ||||
| 
 | ||||
| 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! | ||||
| :::warning | ||||
| 
 | ||||
| [`import`](/docs/getting-started/installation/nodejs#esm-import) does not load | ||||
| NodeJS native modules. The Installation section includes a note on dynamic | ||||
| import of `fs` within lifecycle methods. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| The general strategy with NextJS apps is to generate HTML snippets or data from | ||||
| the lifecycle functions and reference them in the template. | ||||
| NextJS best practices have evolved over time, but there are three key parts: | ||||
| 
 | ||||
| HTML output can be generated using `XLSX.utils.sheet_to_html` and inserted into | ||||
| the document using the `dangerouslySetInnerHTML` attribute: | ||||
| 1) [Loading Data](#loading-data): NextJS can read files in lifecycle methods OR | ||||
| custom Webpack loaders can create asset modules. | ||||
| 
 | ||||
| ```jsx | ||||
| export default function Index({html, type}) { return ( | ||||
|   // ... | ||||
| // highlight-next-line | ||||
|   <div dangerouslySetInnerHTML={{ __html: html }} /> | ||||
|   // ... | ||||
| ); } | ||||
| ``` | ||||
| 2) [Lifecycle Methods](#nextjs-strategies): NextJS includes strategies for | ||||
| static pages (`getStaticProps`) as well as dynamic pages (`getServerSideProps`). | ||||
| 
 | ||||
| :::warning Reading and writing files during the build process | ||||
| 
 | ||||
| `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 | ||||
|   // ... | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 3) [Data Presentation](#data-presentation): Pages use React and JSX. | ||||
| 
 | ||||
| :::caution Next 13+ and SWC | ||||
| 
 | ||||
| @ -75,6 +50,154 @@ module.exports = { | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## Loading Data | ||||
| 
 | ||||
| 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 methods. | ||||
| 
 | ||||
| Asset modules are appropriate for static sites when the file names are known in | ||||
| advance. Performing file read operations in lifecycle methods is more flexible | ||||
| but does not support live reloading. | ||||
| 
 | ||||
| ### Asset Module | ||||
| 
 | ||||
| :::caution | ||||
| 
 | ||||
| When the demo was last tested, Turbopack did not support true raw loaders. For | ||||
| development use, the normal `npx next dev` should be used. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| The following diagram depicts the workbook waltz: | ||||
| 
 | ||||
| ```mermaid | ||||
| flowchart LR | ||||
|   file[(workbook\nfile)] | ||||
|   subgraph SheetJS operations | ||||
|     base64(base64\nstring) | ||||
|     aoo(array of\nobjects) | ||||
|   end | ||||
|   html{{HTML\nTABLE}} | ||||
|   file --> |base64-loader.js\ncustom plugin| base64 | ||||
|   base64 --> |page\nlifecycle method| aoo | ||||
|   aoo --> |page\nIndex method| html | ||||
| ``` | ||||
| 
 | ||||
| In this flow, it is strongly recommended to make a loader return a Base64 string: | ||||
| 
 | ||||
| ```js title="base64-loader.js" | ||||
| function loader(content) { | ||||
|   /* since `loader.raw` is true, `content` is a Buffer */ | ||||
|   return `export default '${content.toString("base64")}'`; | ||||
| } | ||||
| /* ensure the function receives a Buffer */ | ||||
| loader.raw = true; | ||||
| module.exports = loader; | ||||
| ``` | ||||
| 
 | ||||
| The webpack configuration is controlled in `next.config.js`: | ||||
| 
 | ||||
| ```js title="next.config.js" | ||||
| module.exports = { | ||||
|   webpack: (config) => { | ||||
|     // highlight-start | ||||
|     /* add to the webpack config module.rules array */ | ||||
|     config.module.rules.push({ | ||||
|       /* `test` matches file extensions */ | ||||
|       test: /\.(numbers|xls|xlsx|xlsb)/, | ||||
|       /* use the loader script */ | ||||
|       use: [ { loader: './base64-loader' } ] | ||||
|     }); | ||||
|     // highlight-end | ||||
|     return config; | ||||
|   } | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| Module alias directories can be defined in `jsconfig.json` or `tsconfig.json`: | ||||
| 
 | ||||
| ```json title="jsconfig.json" | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "baseUrl": ".", | ||||
|     "paths": { | ||||
|       // highlight-next-line | ||||
|       "@/*": ["*"] | ||||
|     } | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| Pages can import the files directly. It is strongly recommended to store files | ||||
| in a `data` folder. This example uses `getStaticProps` to parse `sheetjs.xlsx`: | ||||
| 
 | ||||
| ```jsx title="index.js" | ||||
| import { read, utils } from 'xlsx'; | ||||
| // highlight-next-line | ||||
| import base64 from '@/data/sheetjs.xlsx'; | ||||
| 
 | ||||
| export async function getStaticProps() { | ||||
|   /* parse base64 data */ | ||||
|   // highlight-next-line | ||||
|   const wb = read(base64, { type: "base64" }); | ||||
|   return { props: { | ||||
|       /* generate array of objects from the first sheet */ | ||||
|       data: utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]) | ||||
|   } }; | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### Raw Operations | ||||
| 
 | ||||
| Files can be read using `readFile` in lifecycle methods. The `cwd` method from | ||||
| the `process` module will point to the root of the project. | ||||
| 
 | ||||
| The following diagram depicts the workbook waltz: | ||||
| 
 | ||||
| ```mermaid | ||||
| flowchart LR | ||||
|   file[(workbook\nfile)] | ||||
|   subgraph SheetJS operations | ||||
|     buffer(NodeJS\nBuffer) | ||||
|     aoo(array of\nobjects) | ||||
|   end | ||||
|   html{{HTML\nTABLE}} | ||||
|   file --> |page\nlifecycle method| buffer | ||||
|   buffer --> |page\nlifecycle method| aoo | ||||
|   aoo --> |page\nIndex method| html | ||||
| ``` | ||||
| 
 | ||||
| This example reads the file `sheetjs.xlsx` in the `data` folder in the project: | ||||
| 
 | ||||
| ```js | ||||
| import { readFile, utils, set_fs } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| 
 | ||||
| export async function getServerSideProps() { | ||||
| // highlight-start | ||||
|   set_fs(await import("fs")); // dynamically import 'fs' when needed | ||||
|   const filename = join(cwd(), "data", "sheetjs.xlsx"); // /data/sheetjs.xlsx | ||||
|   const wb = readFile(filename); | ||||
|   // highlight-end | ||||
| 
 | ||||
|   /* generate and return the html from the first worksheet */ | ||||
|   const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); | ||||
|   return { props: { data } }; | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| :::warning Reading and writing files during the build process | ||||
| 
 | ||||
| As the NextJS workaround is non-traditional, it bears repeating: | ||||
| 
 | ||||
| `fs` cannot be statically imported from the top level in NextJS pages.  The | ||||
| dynamic import must happen within a lifecycle function. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 
 | ||||
| ## NextJS Strategies | ||||
| 
 | ||||
| NextJS currently provides 3 strategies: | ||||
| @ -86,14 +209,17 @@ NextJS currently provides 3 strategies: | ||||
| ### Static Site Generation | ||||
| 
 | ||||
| When using `getStaticProps`, the file will be read once during build time. | ||||
| This example reads `sheetjs.xlsx` from the `data` folder: | ||||
| 
 | ||||
| <Tabs groupId="data"> | ||||
|   <TabItem value="asset" label="Asset Module"> | ||||
| 
 | ||||
| ```js | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { read, utils } from 'xlsx'; | ||||
| import base64 from '@/data/sheetjs.xlsx'; | ||||
| 
 | ||||
| export async function getStaticProps() { | ||||
|   /* read file */ | ||||
|   set_fs(await import("fs")); | ||||
|   const wb = readFile(path_to_file) | ||||
|   const wb = read(base64, { type: "base64" }); | ||||
| 
 | ||||
|   /* generate and return the html from the first worksheet */ | ||||
|   const html = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); | ||||
| @ -101,6 +227,28 @@ export async function getStaticProps() { | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="raw" label="Raw Operations"> | ||||
| 
 | ||||
| ```js | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| 
 | ||||
| export async function getStaticProps() { | ||||
|   set_fs(await import("fs")); | ||||
|   const filename = join(cwd(), "data", "sheetjs.xlsx"); // /data/sheetjs.xlsx | ||||
|   const wb = readFile(filename); | ||||
| 
 | ||||
|   /* generate and return the html from the first worksheet */ | ||||
|   const html = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); | ||||
|   return { props: { html } }; | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| ### Dynamic Routes | ||||
| 
 | ||||
| Typically a static site with dynamic routes has an endpoint `/sheets/[id]` that | ||||
| @ -108,10 +256,35 @@ implements both `getStaticPaths` and `getStaticProps`. | ||||
| 
 | ||||
| - `getStaticPaths` should return an array of worksheet indices: | ||||
| 
 | ||||
| <Tabs groupId="data"> | ||||
|   <TabItem value="asset" label="Asset Module"> | ||||
| 
 | ||||
| ```js | ||||
| import { read } from 'xlsx'; | ||||
| import base64 from '@/data/sheetjs.xlsx'; | ||||
| 
 | ||||
| export async function getStaticPaths() { | ||||
|   /* read file */ | ||||
|   const wb = read(base64, { type: "base64" }); | ||||
| 
 | ||||
|   /* 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 }; | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="raw" label="Raw Operations"> | ||||
| 
 | ||||
| ```js | ||||
| import { readFile, set_fs } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| 
 | ||||
| export async function getStaticPaths() { | ||||
|   /* read file */ | ||||
|   set_fs(await import("fs")); | ||||
|   const filename = join(cwd(), "data", "sheetjs.xlsx"); // /data/sheetjs.xlsx | ||||
|   const wb = readFile(path); | ||||
| 
 | ||||
|   /* generate an array of objects that will be used for generating pages */ | ||||
| @ -120,6 +293,9 @@ export async function getStaticPaths() { | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| For a pure static site, `fallback` must be set to `false`! | ||||
| @ -128,10 +304,36 @@ For a pure static site, `fallback` must be set to `false`! | ||||
| 
 | ||||
| - `getStaticProps` will generate the actual HTML for each page: | ||||
| 
 | ||||
| <Tabs groupId="data"> | ||||
|   <TabItem value="asset" label="Asset Module"> | ||||
| 
 | ||||
| ```js | ||||
| import { read, utils } from 'xlsx'; | ||||
| import base64 from '@/data/sheetjs.xlsx'; | ||||
| 
 | ||||
| export async function getStaticProps(ctx) { | ||||
|   /* read file */ | ||||
|   const wb = read(base64, { type: "base64" }); | ||||
| 
 | ||||
|   /* 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 } }; | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="raw" label="Raw Operations"> | ||||
| 
 | ||||
| ```js | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| 
 | ||||
| export async function getStaticProps(ctx) { | ||||
|   /* read file */ | ||||
|   set_fs(await import("fs")); | ||||
|   const filename = join(cwd(), "data", "sheetjs.xlsx"); // /data/sheetjs.xlsx | ||||
|   const wb = readFile(path); | ||||
| 
 | ||||
|   /* get the corresponding worksheet and generate HTML */ | ||||
| @ -141,6 +343,9 @@ export async function getStaticProps(ctx) { | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| ### Server-Side Rendering | ||||
| 
 | ||||
| :::caution Do not use on a static site | ||||
| @ -156,13 +361,24 @@ changes frequently and a static site is undesirable. | ||||
| 
 | ||||
| When using `getServerSideProps`, the file will be read on each request. | ||||
| 
 | ||||
| <Tabs groupId="data"> | ||||
|   <TabItem value="asset" label="Asset Module"> | ||||
| 
 | ||||
| :::caution Consider using a static strategy | ||||
| 
 | ||||
| When using asset modules, the file names and file paths are processed during the | ||||
| build step. The content is fixed. In this situation, a static approach such as | ||||
| `getStaticProps` is strongly recommended. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ```js | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { read } from 'xlsx'; | ||||
| import base64 from '@/data/sheetjs.xlsx'; | ||||
| 
 | ||||
| export async function getServerSideProps() { | ||||
|   /* read file */ | ||||
|   set_fs(await import("fs")); | ||||
|   const wb = readFile(path_to_file); | ||||
|   const wb = read(base64, { type: "base64" }); | ||||
| 
 | ||||
|   /* generate and return the html from the first worksheet */ | ||||
|   const html = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); | ||||
| @ -170,24 +386,119 @@ export async function getServerSideProps() { | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="raw" label="Raw Operations"> | ||||
| 
 | ||||
| ```js | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| 
 | ||||
| export async function getServerSideProps() { | ||||
|   /* read file */ | ||||
|   set_fs(await import("fs")); | ||||
|   const filename = join(cwd(), "data", "sheetjs.xlsx"); // /data/sheetjs.xlsx | ||||
|   const wb = readFile(path); | ||||
| 
 | ||||
|   /* generate and return the html from the first worksheet */ | ||||
|   const html = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); | ||||
|   return { props: { html } }; | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| ## Data Presentation | ||||
| 
 | ||||
| [The React demo](/docs/demos/frontend/react) compares common approaches. | ||||
| 
 | ||||
| ### HTML | ||||
| 
 | ||||
| HTML output can be generated using `XLSX.utils.sheet_to_html` and inserted into | ||||
| the document using the `dangerouslySetInnerHTML` attribute: | ||||
| 
 | ||||
| ```mermaid | ||||
| flowchart LR | ||||
|   subgraph SheetJS operations | ||||
|     data(File\nData) | ||||
|     code{{HTML\nTABLE}} | ||||
|   end | ||||
|   html{{Rendered\nPage}} | ||||
|   data --> |lifecycle\nsheet_to_html| code | ||||
|   code --> |Index\ninnerHTML| html | ||||
| ``` | ||||
| 
 | ||||
| ```jsx | ||||
| export default function Index({html, type}) { return ( | ||||
|   <div dangerouslySetInnerHTML={{ __html: html }} /> | ||||
| ); } | ||||
| ``` | ||||
| 
 | ||||
| ### Arrays of Objects | ||||
| 
 | ||||
| Arrays of objects can be generated using `XLSX.utils.sheet_to_json` and inserted | ||||
| into the document using standard JSX: | ||||
| 
 | ||||
| ```mermaid | ||||
| flowchart LR | ||||
|   subgraph SheetJS operations | ||||
|     data(File\nData) | ||||
|     aoo(array of\nobjects) | ||||
|   end | ||||
|   html{{Rendered\nPage}} | ||||
|   data --> |lifecycle\nsheet_to_json| aoo | ||||
|   aoo --> |Index\nReact + JSX| html | ||||
| ``` | ||||
| 
 | ||||
| ```jsx | ||||
| export default function Index({aoo, type}) { return ( | ||||
|   <table><thead><tr key={0}><th>Name</th><th>Index</th></tr></thead><tbody> | ||||
| // highlight-start | ||||
|     {aoo.map(row => ( <tr> | ||||
|       <td>{row.Name}</td> | ||||
|       <td>{row.Index}</td> | ||||
|     </tr>))} | ||||
| // highlight-end | ||||
|   </tbody></table> | ||||
| ); } | ||||
| ``` | ||||
| 
 | ||||
| ## Demo | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo showcases the following SheetJS + NextJS flows: | ||||
| 
 | ||||
| | Page                  | Loading Data | Lifecycle Method     | SheetJS API     | | ||||
| |:----------------------|:-------------|:---------------------|:----------------| | ||||
| | `/getStaticProps`     | asset module | `getStaticProps`     | `sheet_to_json` | | ||||
| | `/sheets/[id]`        | asset module | `getStaticPaths`     | `sheet_to_html` | | ||||
| | `/getServerSideProps` | lifecycle    | `getServerSideProps` | `sheet_to_html` | | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Initial Setup | ||||
| 
 | ||||
| 0) Disable NextJS telemetry: | ||||
| 
 | ||||
| ```js | ||||
| npx next@13.1.1 telemetry disable | ||||
| npx next@13.4.4 telemetry disable | ||||
| ``` | ||||
| 
 | ||||
| Confirm it is disabled by running | ||||
| 
 | ||||
| ```js | ||||
| npx next@13.1.1 telemetry status | ||||
| npx next@13.4.4 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 sheetjs-next | ||||
| cd sheetjs-next | ||||
| mkdir -p pages/sheets/ | ||||
| ``` | ||||
| 
 | ||||
| @ -201,13 +512,30 @@ curl -LO https://docs.sheetjs.com/next/sheetjs.xlsx | ||||
| 3) Install dependencies: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz next@13.1.1`} | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz next@13.4.4`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 4) Download test scripts: | ||||
| 4) Download NextJS config scripts and place in the root folder: | ||||
| 
 | ||||
| - [`base64-loader.js`](pathname:///next/base64-loader.js) | ||||
| - [`jsconfig.json`](pathname:///next/jsconfig.json) | ||||
| - [`next.config.js`](pathname:///next/next.config.js) | ||||
| - [`styles.css`](pathname:///next/styles.css) | ||||
| 
 | ||||
| On Linux or MacOS or WSL: | ||||
| 
 | ||||
| ```bash | ||||
| curl -LO https://docs.sheetjs.com/next/base64-loader.js | ||||
| curl -LO https://docs.sheetjs.com/next/jsconfig.json | ||||
| curl -LO https://docs.sheetjs.com/next/next.config.js | ||||
| curl -LO https://docs.sheetjs.com/next/styles.css | ||||
| ``` | ||||
| 
 | ||||
| 5) Download test scripts: | ||||
| 
 | ||||
| Download and place the following scripts in the `pages` subfolder: | ||||
| 
 | ||||
| - [`_app.js`](pathname:///next/_app.js) | ||||
| - [`index.js`](pathname:///next/index.js) | ||||
| - [`getServerSideProps.js`](pathname:///next/getServerSideProps.js) | ||||
| - [`getStaticPaths.js`](pathname:///next/getStaticPaths.js) | ||||
| @ -227,6 +555,7 @@ On Linux or MacOS or WSL: | ||||
| 
 | ||||
| ```bash | ||||
| cd pages | ||||
| curl -LO https://docs.sheetjs.com/next/_app.js | ||||
| 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 | ||||
| @ -236,10 +565,12 @@ curl -LOg 'https://docs.sheetjs.com/next/[id].js' | ||||
| cd ../.. | ||||
| ``` | ||||
| 
 | ||||
| 5) Test the deployment: | ||||
| ### Testing | ||||
| 
 | ||||
| 6) Test the deployment: | ||||
| 
 | ||||
| ```bash | ||||
| npx next@13.1.1 | ||||
| npx next@13.4.4 | ||||
| ``` | ||||
| 
 | ||||
| Open a web browser and access: | ||||
| @ -247,43 +578,50 @@ 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) | ||||
| - `http://localhost:3000/getStaticPaths` shows a list (2 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 server and run a production build: | ||||
| 7) While the development server is running, open the `/getStaticProps` page and | ||||
| open `sheetjs.xlsx` with a spreadsheet editor. In the editor, add a row to the | ||||
| bottom of the "Indices" worksheet. | ||||
| 
 | ||||
| After saving the file, the website should refresh with the new row. | ||||
| 
 | ||||
| ### Production Build | ||||
| 
 | ||||
| 8) Stop the server and run a production build: | ||||
| 
 | ||||
| ```bash | ||||
| npx next@13.1.1 build | ||||
| npx next@13.4.4 build | ||||
| ``` | ||||
| 
 | ||||
| The final output will show a list of the routes and types: | ||||
| 
 | ||||
| ``` | ||||
| Route (pages)                              Size     First Load JS | ||||
| ┌ ○ /                                      541 B          77.4 kB | ||||
| ├ ○ /404                                   181 B          73.7 kB | ||||
| ├ λ /getServerSideProps                    594 B          77.4 kB | ||||
| ├ ● /getStaticPaths                        2.56 kB        79.4 kB | ||||
| ├ ● /getStaticProps                        591 B          77.4 kB | ||||
| └ ● /sheets/[id] (447 ms)                  569 B          77.4 kB | ||||
| ┌ ○ /                                      563 B          74.4 kB | ||||
| ├   /_app                                  0 B            73.9 kB | ||||
| ├ ○ /404                                   182 B          74.1 kB | ||||
| ├ λ /getServerSideProps                    522 B          74.4 kB | ||||
| ├ ● /getStaticPaths                        2.89 kB        76.8 kB | ||||
| ├ ● /getStaticProps                        586 B          74.5 kB | ||||
| └ ● /sheets/[id]                           522 B          74.4 kB | ||||
|     ├ /sheets/0 | ||||
|     ├ /sheets/1 | ||||
|     └ /sheets/2 | ||||
|     └ /sheets/1 | ||||
| ``` | ||||
| 
 | ||||
| As explained in the summary, the `/getStaticPaths` and `/getStaticProps` routes | ||||
| are completely static.  3 `/sheets/#` pages were generated, corresponding to 3 | ||||
| are completely static.  2 `/sheets/#` pages were generated, corresponding to 2 | ||||
| worksheets in the file.  `/getServerSideProps` is server-rendered. | ||||
| 
 | ||||
| 7) Try to build a static site: | ||||
| 9) Try to build a static site: | ||||
| 
 | ||||
| ```bash | ||||
| npx next@13.1.1 export | ||||
| npx next@13.4.4 export | ||||
| ``` | ||||
| 
 | ||||
| :::note The static export will fail! | ||||
| @ -293,34 +631,38 @@ is still server-rendered. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 8) Delete `pages/getServerSideProps.js` and rebuild: | ||||
| ### Static Site | ||||
| 
 | ||||
| 10) Delete `pages/getServerSideProps.js` and rebuild: | ||||
| 
 | ||||
| ```bash | ||||
| rm -f pages/getServerSideProps.js | ||||
| npx next@13.1.1 build | ||||
| npx next@13.4.4 build | ||||
| ``` | ||||
| 
 | ||||
| Inspecting the output, there should be no lines with the `λ` symbol: | ||||
| 
 | ||||
| ``` | ||||
| Route (pages)                              Size     First Load JS | ||||
| ┌ ○ /                                      541 B          77.4 kB | ||||
| ├ ○ /404                                   181 B          73.7 kB | ||||
| ├ ● /getStaticPaths                        2.56 kB        79.4 kB | ||||
| ├ ● /getStaticProps                        591 B          77.4 kB | ||||
| └ ● /sheets/[id] (459 ms)                  569 B          77.4 kB | ||||
| ┌ ○ /                                      563 B          74.4 kB | ||||
| ├   /_app                                  0 B            73.9 kB | ||||
| ├ ○ /404                                   182 B          74.1 kB | ||||
| ├ ● /getStaticPaths                        2.89 kB        76.8 kB | ||||
| ├ ● /getStaticProps                        586 B          74.5 kB | ||||
| └ ● /sheets/[id]                           522 B          74.4 kB | ||||
|     ├ /sheets/0 | ||||
|     ├ /sheets/1 | ||||
|     └ /sheets/2 | ||||
|     └ /sheets/1 | ||||
| ``` | ||||
| 
 | ||||
| 9) Generate the static site: | ||||
| 11) Generate the static site: | ||||
| 
 | ||||
| ```bash | ||||
| npx next@13.1.1 export | ||||
| npx next@13.4.4 export | ||||
| ``` | ||||
| 
 | ||||
| The static site will be written to the `out` subfolder, which can be hosted with | ||||
| The static site will be written to the `out` subfolder | ||||
| 
 | ||||
| 12) Serve the static site: | ||||
| 
 | ||||
| ```bash | ||||
| npx http-server out | ||||
|  | ||||
| @ -236,7 +236,8 @@ sequenceDiagram | ||||
| 
 | ||||
| ## Bindings | ||||
| 
 | ||||
| Duktape is easily embeddable.  Bindings exist for many languages. | ||||
| Bindings exist for many languages. As these bindings require "native" code, they | ||||
| may not work on every platform. | ||||
| 
 | ||||
| ### Perl | ||||
| 
 | ||||
|  | ||||
| @ -263,7 +263,8 @@ file `sheetjsw.xlsb` will be created.  That file can be opened with Excel. | ||||
| 
 | ||||
| ## Bindings | ||||
| 
 | ||||
| V8 is easily embeddable.  Bindings exist for many languages. | ||||
| Bindings exist for many languages. As these bindings require "native" code, they | ||||
| may not work on every platform. | ||||
| 
 | ||||
| ### Rust | ||||
| 
 | ||||
|  | ||||
| @ -1,28 +1,25 @@ | ||||
| import Head from 'next/head'; | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| import { read, utils } from 'xlsx'; | ||||
| import base64 from "@/sheetjs.xlsx"; | ||||
| 
 | ||||
| export default function Index({type, html, name}) { return ( <div> | ||||
| export default function Index({type, html, name}) { return ( <> | ||||
|   <Head> | ||||
|     <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <title>{`SheetJS Next.JS ${type} Demo`}</title> | ||||
|     <style jsx>{`body, #app { height: 100%; };`}</style> | ||||
|   </Head> | ||||
|   <pre> | ||||
|     <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|     This demo reads from /sheetjs.xlsx<br/><br/> | ||||
|     <b>{name}</b> | ||||
|     <div dangerouslySetInnerHTML={{ __html: html }} /> | ||||
|   </pre> | ||||
| </div> ); } | ||||
|   <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|   <p> | ||||
|     This demo reads from <code>/sheetjs.xlsx</code><br/><br/> | ||||
|     Sheet name: <b>{name}</b><br/> | ||||
|   </p> | ||||
|   <div dangerouslySetInnerHTML={{ __html: html }} /> | ||||
| </> ); } | ||||
| 
 | ||||
| let cache = []; | ||||
| 
 | ||||
| export async function getStaticProps(ctx) { | ||||
|   if(!cache || !cache.length) { | ||||
|     set_fs(await import("fs")); | ||||
|     const wb = readFile(join(cwd(), "sheetjs.xlsx")); | ||||
|     const wb = read(base64, {type: "base64"}); | ||||
|     cache = wb.SheetNames.map((name) => ({ name, sheet: wb.Sheets[name] })); | ||||
|   } | ||||
|   const entry = cache[ctx.params.id]; | ||||
| @ -37,11 +34,10 @@ export async function getStaticProps(ctx) { | ||||
| } | ||||
| 
 | ||||
| export async function getStaticPaths() { | ||||
|   set_fs(await import("fs")); | ||||
|   const wb = readFile(join(cwd(), "sheetjs.xlsx")); | ||||
|   const wb = read(base64, {type: "base64"}); | ||||
|   cache = wb.SheetNames.map((name) => ({ name, sheet: wb.Sheets[name] })); | ||||
|   return { | ||||
|     paths: wb.SheetNames.map((name, idx) => ({ params: { id: idx.toString()  } })), | ||||
|     paths: wb.SheetNames.map((_, idx) => ({ params: { id: idx.toString()  } })), | ||||
|     fallback: false, | ||||
|   }; | ||||
| } | ||||
|  | ||||
							
								
								
									
										5
									
								
								docz/static/next/_app.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										5
									
								
								docz/static/next/_app.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| import '../styles.css' | ||||
| 
 | ||||
| export default function MyApp({ Component, pageProps }) { | ||||
|   return <Component {...pageProps} /> | ||||
| } | ||||
							
								
								
									
										7
									
								
								docz/static/next/base64-loader.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										7
									
								
								docz/static/next/base64-loader.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| function loader(content) { | ||||
|   /* since `loader.raw` is true, `content` is a Buffer */ | ||||
|   return `export default '${content.toString("base64")}'`; | ||||
| } | ||||
| /* ensure the function receives a Buffer */ | ||||
| loader.raw = true; | ||||
| module.exports = loader; | ||||
| @ -7,14 +7,13 @@ export default function Index({type, html}) { return ( <div> | ||||
|   <Head> | ||||
|     <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <title>{`SheetJS Next.JS ${type} Demo`}</title> | ||||
|     <style jsx>{`body, #app { height: 100%; };`}</style> | ||||
|   </Head> | ||||
|   <pre> | ||||
|     <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|   <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|   <p> | ||||
|     This demo reads from /sheetjs.xlsx<br/><br/> | ||||
|     It generates HTML from the first sheet.<br/><br/> | ||||
|     <div dangerouslySetInnerHTML={{ __html: html }} /> | ||||
|   </pre> | ||||
|   </p> | ||||
|   <div dangerouslySetInnerHTML={{ __html: html }} /> | ||||
| </div> ); } | ||||
| 
 | ||||
| export async function getServerSideProps() { | ||||
|  | ||||
| @ -1,30 +1,28 @@ | ||||
| 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'; | ||||
| import { read } from 'xlsx'; | ||||
| import base64 from "@/sheetjs.xlsx"; | ||||
| 
 | ||||
| export default function Index({type, snames}) { return ( <div> | ||||
| export default function Index({type, snames}) { return ( <> | ||||
|   <Head> | ||||
|     <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <title>{`SheetJS Next.JS ${type} Demo`}</title> | ||||
|     <style jsx>{`body, #app { height: 100%; };`}</style> | ||||
|   </Head> | ||||
|   <pre> | ||||
|     <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|     This demo reads from /sheetjs.xlsx<br/><br/> | ||||
|     Each worksheet maps to a path:<br/><br/> | ||||
|     <ul> | ||||
| {snames.map((sname, idx) => (<li key={idx}> | ||||
|   <Link href="/sheets/[id]" as={`/sheets/${idx}`}>{`Sheet index=${idx} name="${sname}"`}</Link> | ||||
| </li>))} | ||||
|     </ul> | ||||
|   </pre> | ||||
| </div> ); } | ||||
|   <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|   <p> | ||||
|     This demo reads from <code>/sheetjs.xlsx</code><br/><br/> | ||||
|     Each worksheet maps to a path:<br/> | ||||
|   </p> | ||||
|   <table><thead><tr key={0}><th>Sheet Name</th><th>URL</th></tr></thead><tbody> | ||||
|     {snames.map((sname, idx) => (<tr key={idx+1}> | ||||
|       <td><Link href="/sheets/[id]" as={`/sheets/${idx}`}>{sname}</Link></td> | ||||
|       <td><code>/sheets/{idx}</code></td> | ||||
|     </tr>))} | ||||
|   </tbody></table> | ||||
| </> ); } | ||||
| 
 | ||||
| export async function getStaticProps() { | ||||
|   set_fs(await import("fs")); | ||||
|   const wb = readFile(join(cwd(), "sheetjs.xlsx")) | ||||
|   const wb = read(base64, {type: "base64"}); | ||||
|   return { | ||||
|     props: { | ||||
|       type: "getStaticPaths", | ||||
|  | ||||
| @ -1,29 +1,31 @@ | ||||
| import Head from 'next/head'; | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| import { read, utils } from 'xlsx'; | ||||
| import base64 from "@/sheetjs.xlsx"; | ||||
| 
 | ||||
| export default function Index({type, html}) { return ( <div> | ||||
| export default function Index({type, aoo}) { return ( <> | ||||
|   <Head> | ||||
|     <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <title>{`SheetJS Next.JS ${type} Demo`}</title> | ||||
|     <style jsx>{`body, #app { height: 100%; };`}</style> | ||||
|   </Head> | ||||
|   <pre> | ||||
|     <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|     This demo reads from /sheetjs.xlsx<br/><br/> | ||||
|     It generates HTML from the first sheet.<br/><br/> | ||||
|     <div dangerouslySetInnerHTML={{ __html: html }} /> | ||||
|   </pre> | ||||
| </div> ); } | ||||
|   <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|   <p> | ||||
|     This demo reads from <code>/sheetjs.xlsx</code><br/><br/> | ||||
|     It generates objects from the first sheet.<br/><br/> | ||||
|   </p> | ||||
|   <table><thead><tr key={0}><th>Name</th><th>Index</th></tr></thead><tbody> | ||||
|     {aoo.map((row, R) => ( <tr key={R+1}> | ||||
|       <td>{row.Name}</td> | ||||
|       <td>{row.Index}</td> | ||||
|     </tr>))} | ||||
|   </tbody></table> | ||||
| </> ); } | ||||
| 
 | ||||
| export async function getStaticProps() { | ||||
|   set_fs(await import("fs")); | ||||
|   const wb = readFile(join(cwd(), "sheetjs.xlsx")) | ||||
|   const wb = read(base64, {type: "base64"}); | ||||
|   return { | ||||
|     props: { | ||||
|       type: "getStaticProps", | ||||
|       html: utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]), | ||||
|       aoo: utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]), | ||||
|     }, | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,16 +1,26 @@ | ||||
| import Head from 'next/head'; | ||||
| 
 | ||||
| export default function Index() { return ( <div> | ||||
| export default function Index() { return ( <> | ||||
|   <Head> | ||||
|     <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <title>SheetJS Next.JS Demo</title> | ||||
|     <style jsx>{`body, #app { height: 100%; };`}</style> | ||||
|   </Head> | ||||
|   <pre> | ||||
|     <h3>SheetJS Next.JS Demo</h3> | ||||
|     This demo reads from /sheetjs.xlsx<br/><br/> | ||||
|     - <a href="/getStaticProps">getStaticProps</a><br/><br/> | ||||
|     - <a href="/getServerSideProps">getServerSideProps</a><br/><br/> | ||||
|     - <a href="/getStaticPaths">getStaticPaths</a><br/> | ||||
|   </pre> | ||||
| </div> ); } | ||||
|   <h3>SheetJS Next.JS Demo</h3> | ||||
|   <p> | ||||
|     This demo reads from <code>/sheetjs.xlsx</code><br/> | ||||
|   </p> | ||||
|   <table><thead><tr><th>Route</th><th>NextJS Strategy</th></tr></thead><tbody> | ||||
|     <tr> | ||||
|       <td><a href="/getStaticProps"><code>/getStaticProps</code></a></td> | ||||
|       <td><code>getStaticProps</code></td> | ||||
|     </tr> | ||||
|     <tr> | ||||
|       <td><a href="/getServerSideProps"><code>/getServerSideProps</code></a></td> | ||||
|       <td><code>getServerSideProps</code></td> | ||||
|     </tr> | ||||
|     <tr> | ||||
|       <td><a href="/getStaticPaths"><code>/getStaticPaths</code></a></td> | ||||
|       <td><code>getStaticPaths</code></td> | ||||
|     </tr> | ||||
|   </tbody></table> | ||||
| </> ); } | ||||
|  | ||||
							
								
								
									
										8
									
								
								docz/static/next/jsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										8
									
								
								docz/static/next/jsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "baseUrl": ".", | ||||
|     "paths": { | ||||
|       "@/*": ["*"] | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										12
									
								
								docz/static/next/next.config.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										12
									
								
								docz/static/next/next.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| module.exports = { | ||||
|   webpack: (config) => { | ||||
|     /* add to the webpack config module.rules array */ | ||||
|     config.module.rules.push({ | ||||
|       /* `test` matches file extensions */ | ||||
|       test: /\.(numbers|xls|xlsx|xlsb)/, | ||||
|       /* use the loader script */ | ||||
|       use: [ { loader: './base64-loader' } ] | ||||
|     }); | ||||
|     return config; | ||||
|   } | ||||
| }; | ||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										5
									
								
								docz/static/next/styles.css
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										5
									
								
								docz/static/next/styles.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| body, #app { | ||||
|   height: 100%; | ||||
|   font-size: 16px; | ||||
|   font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user