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