NuxtJS workarounds

This commit is contained in:
SheetJS 2025-05-18 16:09:19 -04:00
parent 9af5473755
commit d77a40c6bf
13 changed files with 425 additions and 304 deletions

@ -118,20 +118,20 @@ importScripts("https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.mi
### Type Checker
:::danger VSCode Telemetry and Data Exfiltration
:::danger VS Code Telemetry and Data Exfiltration
The official Microsoft builds of Visual Studio Code ("VSCode") embed telemetry
and send information to external servers.
The official builds of Visual Studio Code ("VS Code" or "VSCode") embed
telemetry and send information to Microsoft servers.
**[VSCodium](https://vscodium.com/) is a telemetry-free fork of VSCode.**
**[VSCodium](https://vscodium.com/) is a telemetry-free fork of VS Code.**
When writing code that may process personally identifiable information (PII),
the SheetJS team strongly encourages building VSCode from source or using IDEs
the SheetJS team strongly encourages building VS Code from source or using IDEs
that do not exfiltrate data.
:::
The type checker integrated in VSCodium and VSCode does not currently provide
The type checker integrated in VSCodium and VS Code does not currently provide
type hints when using the standalone build. Using the JSDoc `@type` directive
coupled with type imports, VSCodium will recognize the types:
@ -176,7 +176,7 @@ The `.d.ts` file extension must be omitted.
JSDoc types using the `@import` directive are not supported in `<script>` tags.
**This is a known bug with VSCode!**
**This is a known bug with VS Code!**
:::

@ -35,6 +35,7 @@ This demo was tested in the following configurations:
| Platform | Architecture | Date |
|:------------------------------------------------------------------|:-------------|:-----------|
| NVIDIA RTX 5090 (32 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | 2025-05-17 |
| NVIDIA RTX 4090 (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | 2025-04-17 |
| NVIDIA RTX 4090 (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `linux-x64` | 2025-01-28 |
| AMD RX 7900 XTX (24 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | 2025-01-12 |

@ -49,7 +49,8 @@ This demo was tested in the following environments:
| Nuxt Content | Nuxt | Date |
|:-------------|:-----------|:-----------|
| `1.15.1` | `2.18.1` | 2025-04-23 |
| `2.13.4` | `3.14.159` | 2024-11-14 |
| `2.13.4` | `3.17.2` | 2025-05-12 |
| `3.5.1` | `3.17.3` | 2025-05-18 |
:::
@ -525,8 +526,8 @@ script files. The module script is expected to export a module configured with
- Add the transformer to Nuxt Content in the `content:context` hook
```js title="sheetmodule.ts (Module)"
import { resolve } from 'path'
import { defineNuxtModule } from '@nuxt/kit'
import { resolve } from 'path';
import { defineNuxtModule } from '@nuxt/kit';
export default defineNuxtModule({
/* module setup method */
@ -549,7 +550,7 @@ The module must be loaded in `nuxt.config.ts` and added to the `modules` array:
```ts title="nuxt.config.ts"
// highlight-next-line
import SheetJSModule from './sheetmodule'
import SheetJSModule from './sheetmodule';
export default defineNuxtConfig({
// @ts-ignore
@ -606,7 +607,7 @@ from the script setup will be shaped like the return value from the transformer.
:::caution pass
For some older versions, parts of the Nuxt dependency tree did not support
NodeJS version 20. If the `yarn install` step fails with a message like
NodeJS version 20. If the `pnpm install` step fails with a message like
```
error @nuxt/kit@3.4.1: The engine "node" is incompatible with this module.
@ -619,17 +620,18 @@ The recommended solution is to switch to Node 18.
1) Create a stock app and install dependencies:
```bash
npx -y nuxi init -t content --packageManager yarn --no-gitInit sheetjs-nc2
npx -y nuxi init -t content --packageManager pnpm --no-gitInit sheetjs-nc2 -M ,
cd sheetjs-nc2
npx -y yarn install
npx -y yarn add --dev @types/node
npx -y pnpm install
npx -y pnpm install @nuxt/content@2 --save
npx -y pnpm install @types/node @nuxt/kit --save
```
2) Install the SheetJS library and start the server:
<CodeBlock language="bash">{`\
npx -y yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npx -y yarn dev`}
npx -y pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npx -y pnpm dev`}
</CodeBlock>
@ -639,7 +641,7 @@ When the build finishes, the terminal will display a URL like:
> Local: http://localhost:3000/
```
The server is listening on that URL. Open the link in a web browser.
The server is listening on that URL. Open the link in a web browser.
3) Download https://docs.sheetjs.com/pres.xlsx and move to the `content` folder.
@ -649,12 +651,12 @@ curl -L -o content/pres.xlsx https://docs.sheetjs.com/pres.xlsx
4) Create the transformer. Two files must be saved at the root of the project:
- [`sheetformer.ts`](https://docs.sheetjs.com/nuxt/3/sheetformer.ts) (raw transformer module)
- [`sheetformer.ts`](https://docs.sheetjs.com/nuxt/2/sheetformer.ts) (raw transformer module)
- [`sheetmodule.ts`](https://docs.sheetjs.com/nuxt/3/sheetmodule.ts) (Nuxt configuration module)
```bash
curl -O https://docs.sheetjs.com/nuxt/3/sheetformer.ts
curl -O https://docs.sheetjs.com/nuxt/3/sheetmodule.ts
curl -O https://docs.sheetjs.com/nuxt/2/sheetformer.ts
curl -O https://docs.sheetjs.com/nuxt/2/sheetmodule.ts
```
After creating the source files, the module must be added to `nuxt.config.ts`:
@ -668,14 +670,13 @@ export default defineNuxtConfig({
// @ts-ignore
telemetry: false,
// highlight-end
devtools: { enabled: true },
// highlight-start
modules: [
// highlight-next-line
SheetJSModule,
'@nuxt/content'
],
content: {}
// highlight-end
devtools: { enabled: true },
// ...
});
```
@ -684,33 +685,13 @@ Stop the dev server (<kbd>CTRL</kbd>+<kbd>C</kbd>) and run the following:
```bash
npx -y nuxi clean
npx -y nuxi cleanup
npx -y nuxi typecheck
npx -y yarn run dev
npx -y pnpm run dev
```
Loading `http://localhost:3000/pres` should show some JSON data:
```json
{
// ...
"data": {
"_path": "/pres",
// ...
"_id": "content:pres.xlsx",
"body": [
{
"name": "Sheet1", // <-- sheet name
"data": [ // <-- array of data objects
{
"Name": "Bill Clinton",
"Index": 42
},
```
5) Download [`pres.vue`](pathname:///nuxt/3/pres.vue) and save to `pages`:
5) Download [`pres.vue`](pathname:///nuxt/2/pres.vue) and save to `app/pages`:
```bash
curl -o pages/pres.vue https://docs.sheetjs.com/nuxt/3/pres.vue
curl -o app/pages/pres.vue https://docs.sheetjs.com/nuxt/2/pres.vue
```
Stop the dev server (<kbd>CTRL</kbd>+<kbd>C</kbd>) and run the following:
@ -718,24 +699,247 @@ Stop the dev server (<kbd>CTRL</kbd>+<kbd>C</kbd>) and run the following:
```bash
npx -y nuxi clean
npx -y nuxi cleanup
npx -y yarn run dev
npx -y pnpm run dev
```
The browser should now display an HTML table.
6) From the browser window in step 2, access `/pres` from the site. For example,
if the URL in step 2 was `http://localhost:3000/`, the new page should be
`http://localhost:3000/pres`.
6) To verify that hot loading works, open `pres.xlsx` from the `content` folder
This page should now display an HTML table.
7) To verify that hot loading works, open `pres.xlsx` from the `content` folder
with a spreadsheet editor.
Set cell `A7` to "SheetJS Dev" and set `B7` to `47`. Save the spreadsheet.
The page should automatically refresh with the new content.
8) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
9) Copy `app/pages/pres.vue` to `app/pages/index.vue`:
```bash
cp app/pages/pres.vue app/pages/index.vue
```
:::info pass
In older test runs, the Nuxt starter created a default `/` page. The most recent
test did not create the index page, resulting in build errors. This step ensures
some sort of index page exists.
:::
10) Build the static site:
```bash
npx -y pnpm run generate
```
This will create a static site in `.output/public`, which can be served with:
```bash
npx -y http-server .output/public
```
Access the displayed URL (typically `http://localhost:8080`) in a web browser.
To confirm that the spreadsheet data is added to the site, view the page source.
Searching for `Bill Clinton` reveals the following encoded HTML row:
```html
<tr><td>Bill Clinton</td><td>42</td></tr>
```
## Nuxt Content v3
:::danger pass
When this demo was last tested, the official Nuxt Content v3 custom transformers
and custom collections examples did not work.
:::
[ViteJS modules](/docs/demos/static/vitejs) can be used in Nuxt v3.
### Installation
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
safely imported from `nuxt.config.ts` or transformer or module scripts. As long
as the SheetJS modules are not imported in the various `.vue` pages, the library
will not be added to the final page bundle!
### Configuration
The `vite` property in the NuxtJS config is passed to ViteJS. Plugins and other
configuration options can be copied to the object. `vite.config.js` for the
[Pure Data Plugin](/docs/demos/static/vitejs#pure-data-plugin) is shown below:
```js title="vite.config.js (raw ViteJS)"
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 ?sheetjs tags
name: "vite-sheet",
transform(_code, id) {
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).replace(/\\/g, "\\\\")}')`;
}
}
]
});
```
The `assetsInclude` and `plugins` properties should be added within the `vite`
property in the object passed to `defineNuxtConfig`.
:::danger pass
NuxtJS does not properly honor the `?sheetjs` tag! As a result, the transform
explicitly tests for the `.xlsx` extension.
:::
```ts title="nuxt.config.ts"
import { readFileSync } from 'fs';
import { read, utils } from 'xlsx';
export default defineNuxtConfig({
// highlight-next-line
vite: { // these options are passed to ViteJS
assetsInclude: ['**/*.xlsx'], // xlsx file should be treated as assets
plugins: [
{ // this plugin handles .xlsx
name: "vite-sheet",
transform(_code, id) {
if(!id.match(/\.xlsx$/)) 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).replace(/\\/g, "\\\\")}')`;
}
},
],
},
// ...
```
### Template Use
Pages can reference spreadsheets using a relative file reference. The ViteJS
plugin will transform files with the `.xlsx` extension.
```js title="Script section of .vue VueJS page"
import data from '../../pres.xlsx'; // data is an array of objects
```
In the template, `data` is an array of objects that works with `v-for`[^4]:
```xml title="Template section of .vue VueJS page"
<table>
<thead><tr><th>Name</th><th>Index</th></tr></thead><tbody>
<!-- loop over the rows of each worksheet -->
<tr v-for="row in data" v-bind:key="row.Index">
<!-- here `row` is a row object generated from sheet_to_json -->
<td>{{ row.Name }}</td>
<td>{{ row.Index }}</td>
</tr>
</table>
```
### Nuxt Content 3 Demo
1) Create a stock app and install dependencies:
```bash
npx -y nuxi init -t content --packageManager pnpm --no-gitInit sheetjs-nc3 -M ,
cd sheetjs-nc3
npx -y pnpm install
```
2) Install the SheetJS library and start the server:
<CodeBlock language="bash">{`\
npx -y pnpm install --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npx -y pnpm dev`}
</CodeBlock>
When the build finishes, the terminal will display a URL like:
```
> Local: http://localhost:3000/
```
The server is listening on that URL. Open the link in a web browser.
3) Download https://docs.sheetjs.com/pres.xlsx and move to the root folder:
```bash
curl -o pres.xlsx https://docs.sheetjs.com/pres.xlsx
```
4) Replace `nuxt.config.ts` with the following codeblock:
```ts title="nuxt.config.ts"
import { readFileSync } from 'fs';
import { read, utils } from 'xlsx';
export default defineNuxtConfig({
vite: { // these options are passed to ViteJS
assetsInclude: ['**/*.xlsx'], // xlsx file should be treated as assets
plugins: [
{ // this plugin handles .xlsx
name: "vite-sheet",
transform(_code, id) {
if(!id.match(/\.xlsx$/)) 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).replace(/\\/g, "\\\\")}')`;
}
},
],
},
modules: [
'@nuxt/content',
],
devtools: { enabled: true },
});
```
5) Create a new file `app.vue` with the following contents:
```jsx title="app.vue (create new file)"
<script setup>
import data from '../../pres.xlsx'
</script>
<template>
<table><thead><tr><th>Name</th><th>Index</th></tr></thead><tbody>
<tr v-for="row in data" v-bind:key="row.Index">
<td>{{ row.Name }}</td>
<td>{{ row.Index }}</td>
</tr>
</tbody></table>
</template>
```
6) Refresh the browser window. This page should now display an HTML table.
7) Stop the server (press <kbd>CTRL</kbd>+<kbd>C</kbd> in the terminal window).
8) Build the static site:
```bash
npx -y yarn run generate
npx -y pnpm run generate
```
This will create a static site in `.output/public`, which can be served with:

@ -111,6 +111,7 @@ const jwt = new google.auth.JWT({
key: creds.private_key,
scopes: [
'https://www.googleapis.com/auth/spreadsheets', // Google Sheets
'https://www.googleapis.com/auth/drive', // Google Drive
'https://www.googleapis.com/auth/drive.file', // Google Drive
]
});
@ -495,7 +496,7 @@ At this point `wb` is a SheetJS workbook object[^10].
:::note Tested Deployments
This demo was last tested on 2024-06-08 using `googleapis` version `140.0.0`.
This demo was last tested on 2025-05-14 using `googleapis` version `148.0.0`.
The demo uses Sheets v4 and Drive v3 APIs.
:::
@ -551,10 +552,10 @@ be a selection box. Click the `▼` icon to show the modal.
If the selection box is missing, expand the browser window.
3) Click "NEW PROJECT" in the top right corner of the modal.
3) Click "New project" in the top right corner of the modal.
4) In the New Project screen, enter "SheetJS Test" in the Project name textbox
and select "No organization" in the Location box. Click "CREATE".
and select "No organization" in the Location box. Click "Create".
A notification will confirm that the project was created:
@ -569,14 +570,13 @@ The goal of this section is to enable Google Sheets API and Google Drive API.
:::
5) Open the Project Selector (`▼` icon) and select "SheetJS Test"
5) Click "Select a project" and select "SheetJS Test" from the Recent tab.
6) In the search bar, type "Enabled" and select "Enabled APIs & services". This
item will be in the "PRODUCTS & PAGES" part of the search results.
6) In the search bar, type "Enabled" and select "Enabled APIs & services".
#### Enable Google Sheets API
7) Near the top of the page, click "+ ENABLE APIS AND SERVICES".
7) Near the top of the page, click "+ Enable APIs and services".
8) In the search bar near the middle of the page (not the search bar at the top),
type "Sheets" and press <kbd>Enter</kbd>.
@ -585,11 +585,11 @@ In the results page, look for "Google Sheets API". Click the card
9) In the Product Details screen, click the blue "ENABLE" button.
10) Click the left arrow (`<-`) next to "API/Service details".
10) Click the left arrow (`<-`) next to "API/Service Details".
#### Enable Google Drive API
11) Near the top of the page, click "+ ENABLE APIS AND SERVICES".
11) Near the top of the page, click "+ Enable APIs and services".
12) In the search bar near the middle of the page (not the search bar at the top),
type "Drive" and press <kbd>Enter</kbd>.
@ -614,13 +614,13 @@ the top bar.
15) Click the Project Selector (`:·` icon) and select "SheetJS Test".
16) In the search bar, type "Credentials" and select the "Credentials" item with
subtitle "APIs & Services". This item will be in the "PRODUCTS & PAGES" group:
subtitle "APIs & Services":
![Credentials](pathname:///gsheet/creds.png)
17) Click "+ CREATE CREDENTIALS". In the dropdown, select "Service Account"
17) Click "+ Create credentials". In the dropdown, select "Service account"
18) Enter "SheetJService" for Service account name. Click "CREATE AND CONTINUE"
18) Enter "SheetJService" for Service account name. Click "Create and continue".
:::note pass
@ -628,24 +628,24 @@ The Service account ID is generated automatically.
:::
19) In Step 2 "Grant this service account access to project", click CONTINUE
19) In Step 2 "Grant this service account access to project", click Continue.
20) In Step 3 click "DONE". You will be taken back to the credentials screen
20) In Step 3 click "Done". You will be taken back to the credentials screen
#### Create JSON Key
21) Look for "SheetJService" in the "Service Accounts" table and click the email
address in the row.
22) Click "KEYS" in the horizontal bar near the top of the page.
22) Click "Keys" in the horizontal bar near the top of the page.
23) Click "ADD KEY" and select "Create new key" in the dropdown.
23) Click "Add key" and select "Create new key" in the dropdown.
24) In the popup, select the "JSON" radio button and click "CREATE".
24) In the popup, select the "JSON" radio button and click "Create".
The page will download a JSON file. If prompted, allow the download.
25) Click "CLOSE"
25) Click "Close"
### Create Document
@ -675,7 +675,7 @@ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz googlea
29) Download [`init.mjs`](pathname:///gsheet/init.mjs):
```bash
curl -LO https://docs.sheetjs.com/gsheet/init.mjs
curl -o init.mjs https://docs.sheetjs.com/gsheet/init.mjs
```
Edit the marked lines near the top of the file:
@ -713,11 +713,12 @@ Shared a-long-string-of-characters with YOUR_ACCOUNT@gmail.com
The long string of characters after "Created Google Workbook" is the ID. Take
note of this ID.
31) Sign into Google Sheets. A shared document "SheetJS Test" should be
displayed in the table. It will be owned by the service account.
31) Sign into Google Drive and select "Shared with me" from the left sidebar. A
shared document "SheetJS Test" should be displayed in the table. It will be
owned by the service account.
32) Open the shared document from step 31 and confirm that the document has two
worksheets named "SheetJS1" and "SheetJS2".
32) Click `⋮` next to "SheetJS Test" and select "Open with" > "Google Sheets".
Confirm that the document has two worksheets named "SheetJS1" and "SheetJS2".
Confirm the worksheet data matches the following screenshots:
@ -786,13 +787,13 @@ NUMBERS file.
34) Download the [test file `pres.numbers`](https://docs.sheetjs.com/pres.numbers):
```bash
curl -LO https://docs.sheetjs.com/pres.numbers
curl -o pres.numbers https://docs.sheetjs.com/pres.numbers
```
35) Download [`load.mjs`](pathname:///gsheet/load.mjs):
```bash
curl -LO https://docs.sheetjs.com/gsheet/load.mjs
curl -o load.mjs https://docs.sheetjs.com/gsheet/load.mjs
```
Edit the marked lines near the top of the file:
@ -830,7 +831,7 @@ The goal of this section is to export the raw data from Google Sheets to XLSB.
38) Download [`dump.mjs`](pathname:///gsheet/dump.mjs):
```bash
curl -LO https://docs.sheetjs.com/gsheet/dump.mjs
curl -o dump.mjs https://docs.sheetjs.com/gsheet/dump.mjs
```
Edit the marked lines near the top of the file:
@ -876,7 +877,7 @@ assign a grid of values
43) Download [`raw.mjs`](pathname:///gsheet/raw.mjs):
```bash
curl -LO https://docs.sheetjs.com/gsheet/raw.mjs
curl -o raw.mjs https://docs.sheetjs.com/gsheet/raw.mjs
```
Edit the marked lines near the top of the file:

@ -266,7 +266,7 @@ function SheetJSEnregistrez() {
:::note Tested Deployments
This demo was last tested on 2024-05-27.
This demo was last tested on 2025-05-14.
:::

@ -1,5 +1,5 @@
---
title: Excel Viewer in Visual Studio Code
title: Visualizing Data in VS Code
sidebar_label: Visual Studio Code
description: View Excel files directly in VS Code. Seamlessly browse spreadsheet data without leaving your editor using SheetJS. Navigate between worksheets and pages of data with a responsive
pagination_prev: demos/cloud/index
@ -16,50 +16,68 @@ import CodeBlock from '@theme/CodeBlock';
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
[Visual Studio Code](https://code.visualstudio.com) is a popular code editor that supports extensions for customizing
and enhancing functionality.
[Visual Studio Code](https://code.visualstudio.com) is a popular code editor
that supports JavaScript extensions for customizing and enhancing functionality.
This demo uses SheetJS in a VS Code extension to view Excel files directly within the editor. The extension leverages
VS Code's `Custom Editor API`[^2] and `WebView API`[^1] to display XLSX data as HTML tables.
The ["Complete Example"](#complete-example) uses SheetJS in a VS Code extension
to view Excel files directly within the editor. The extension leverages the VS
Code "Custom Editor API"[^2] and "WebView API"[^1] to display spreadsheet data
as HTML tables.
The demo includes the full source code for the extension and installation instructions.
:::tip pass
["SheetJS Spreadsheet Viewer"](https://marketplace.visualstudio.com/items?itemName=asadbek.sheetjs-demo)
is a sample extension based on this demo.
[The source code](https://git.sheetjs.com/asadbek064/sheetjs-vscode-extension)
is available on the SheetJS Git server. Feedback and contributions are welcome!
:::
![Expected output](pathname:///vscode/extension-viewing-xls-file.png)
:::note Tested Deployments
This demo was verified in the following deployments:
| Platform | OS Version | Architecture | Date |
|:----------------|:------------------------|:------------------|:-----------|
| VS Code 1.100.0 | macOS 15.3.1 | Darwin (arm64) | 2025-05-15 |
| Platform | Architecture | Date |
|:-----------------|:-------------|:-----------|
| VS Code 1.100.0 | `darwin-arm` | 2025-05-15 | TODO
| VSCodium 1.100.0 | `darwin-arm` | 2025-05-15 | TODO
| Cursor | `win11-arm` | 2025-05-15 | TODO
| Windsurf | `win11-arm` | 2025-05-15 | TODO
| Void | `win11-arm` | 2025-05-15 | TODO
:::
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be imported from any component or script in the extension.
:::note
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
imported from any component or script in the extension.
> Install the package as a developer dependency.
> Otherwise, `vsce`[^5] (Visual Studio Code Extension Manager) will **fail to package or publish** your extension correctly.
:::caution pass
The module must be installed as a development dependency. If the module is
installed as a normal dependency, `vsce`[^5] (Visual Studio Code Extension
Manager) will fail to package or publish your extension correctly.
<Tabs groupId="pm">
<TabItem value="npm" label="npm">
<CodeBlock language="bash">{`\
npm rm --save xlsx
npm i --save-dev https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
npm rm --save xlsx
npm i --save-dev https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="pnpm" label="pnpm">
<CodeBlock language="bash">{`\
pnpm rm xlsx
pnpm install -D https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
pnpm rm xlsx
pnpm install -D https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
<TabItem value="yarn" label="Yarn" default>
<CodeBlock language="bash">{`\
yarn remove xlsx
yarn add -D https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
yarn remove xlsx
yarn add -D https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
</TabItem>
</Tabs>
@ -77,28 +95,25 @@ The VS Code Spreadsheet viewer extension has three main components:
The extension uses VS Code's `Custom Editor API`[^2] to register as a handler for Excel files. When a file is opened,
SheetJS parses it and displays the data in a WebView component.
## Building the Extension
### Extension Entry Point
The main entry point registers the custom editor provider:
<CodeBlock language="typescript" value="typescript" title="src/extension.ts">
{`import * as vscode from 'vscode';
// highlight-start
import { ExcelEditorProvider } from './excelEditorProvider';
// highlight-end
```ts title="src/extension.ts"
import * as vscode from 'vscode';
// highlight-start
import { ExcelEditorProvider } from './excelEditorProvider';
// highlight-end
export function activate(context: vscode.ExtensionContext) {
// SheetJS Spreadsheet Viewer extension activating...
// highlight-start
const provider = ExcelEditorProvider.register(context);
context.subscriptions.push(provider);
// highlight-end
}
export function deactivate() {}`}
</CodeBlock>
export function activate(context: vscode.ExtensionContext) {
// SheetJS Spreadsheet Viewer extension activating...
// highlight-start
const provider = ExcelEditorProvider.register(context);
context.subscriptions.push(provider);
// highlight-end
}
export function deactivate() {}`}
```
The `custom editor`[^3] is configured to support specific file types, giving us complete control over how each file is
presented to the user. Additionally, `custom document`[^4] enables us to maintain and persist the state of each individual
@ -122,7 +137,7 @@ file that's opened.
public static register(context: vscode.ExtensionContext): vscode.Disposable {
return vscode.window.registerCustomEditorProvider(
'excelViewer.spreadsheet',
new ExcelEditorProvider(context),
new ExcelEditorProvider(),
{ webviewOptions: { retainContextWhenHidden: true } } // keep webview state when hidden
);
}
@ -131,6 +146,7 @@ file that's opened.
</CodeBlock>
### Reading Files
The extension reads Excel files using the VS Code filesystem API and passes
the data to SheetJS for parsing:
@ -189,7 +205,7 @@ sequenceDiagram
## Complete Example
1. Create a new VS Code extension
1) Create a new VS Code extension
```bash
npx --package yo --package generator-code -- yo code
@ -209,78 +225,78 @@ When prompted, enter the following options:
- `Do you want to open the new folder with Visual Studio Code?`: Press <kbd>Enter</kbd>
2. [Install the dependencies](#integration-details) and start:
2) [Install the dependencies](#integration-details) and start the dev server:
<CodeBlock language="bash">{`\
cd sheetjs-demo
pnpm install -D https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
pnpm run watch
`}
</CodeBlock>
3. Create a new provider `excelEditorProivder.ts` and paste the following:
3) Save the following code snippet to `src/excelEditorProvider.ts`:
<CodeBlock language="typescript" value="typescript" title="src/excelEditorProvider.ts">
{`import * as vscode from 'vscode';
import * as XLSX from 'xlsx';
<CodeBlock language="typescript" value="typescript" title="src/excelEditorProvider.ts">{`\
import * as vscode from 'vscode';
import * as XLSX from 'xlsx';
class ExcelDocument implements vscode.CustomDocument {
constructor(public readonly uri: vscode.Uri) { }
dispose() { }
}
class ExcelDocument implements vscode.CustomDocument {
constructor(public readonly uri: vscode.Uri) { }
dispose() { }
}
export class ExcelEditorProvider implements vscode.CustomReadonlyEditorProvider<ExcelDocument> {
public static register(context: vscode.ExtensionContext): vscode.Disposable {
return vscode.window.registerCustomEditorProvider(
'excelViewer.spreadsheet',
new ExcelEditorProvider(context),
{ webviewOptions: { retainContextWhenHidden: true } } // keep webview state when hidden
);
}
private async loadWorkbook(document: ExcelDocument, webviewPanel: vscode.WebviewPanel): Promise<XLSX.WorkBook> {
const data: Uint8Array = await vscode.workspace.fs.readFile(document.uri);
const options: XLSX.ParsingOptions = {
type: 'array',
cellStyles: true,
cellDates: true,
};
return XLSX.read(new Uint8Array(data), options); // returns a XLSX.WorkBook
}
// This is called when the first time an editor for a given resource is opened
async openCustomDocument(uri: vscode.Uri): Promise<ExcelDocument> {
return new ExcelDocument(uri);
}
// This is called whenever the user opens a new editor
async resolveCustomEditor(document: ExcelDocument, webviewPanel: vscode.WebviewPanel): Promise<void> {
const wb: XLSX.WorkBook = await this.loadWorkbook(document, webviewPanel);
const htmlTable = XLSX.utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
webviewPanel.webview.html = \`<!DOCTYPE html><html><body>\${htmlTable}</body></html>\`;
}
}`}
</CodeBlock>
4. Register the custom editor provider with the main entry point `extension.ts`
<CodeBlock language="typescript" value="typescript" title="src/extension.ts">
{`import * as vscode from 'vscode';
import { ExcelEditorProvider } from './excelEditorProvider';
export function activate(context: vscode.ExtensionContext) {
// SheetJS Spreadsheet Viewer extension activating...
const provider = ExcelEditorProvider.register(context);
context.subscriptions.push(provider);
public static register(context: vscode.ExtensionContext): vscode.Disposable {
return vscode.window.registerCustomEditorProvider(
'excelViewer.spreadsheet',
new ExcelEditorProvider(),
{ webviewOptions: { retainContextWhenHidden: true } } // keep webview state when hidden
);
}
export function deactivate() {}`}
private async loadWorkbook(document: ExcelDocument, webviewPanel: vscode.WebviewPanel): Promise<XLSX.WorkBook> {
const data: Uint8Array = await vscode.workspace.fs.readFile(document.uri);
const options: XLSX.ParsingOptions = {
type: 'array',
cellStyles: true,
cellDates: true,
};
return XLSX.read(new Uint8Array(data), options); // returns a XLSX.WorkBook
}
// This is called when the first time an editor for a given resource is opened
async openCustomDocument(uri: vscode.Uri): Promise<ExcelDocument> {
return new ExcelDocument(uri);
}
// This is called whenever the user opens a new editor
async resolveCustomEditor(document: ExcelDocument, webviewPanel: vscode.WebviewPanel): Promise<void> {
const wb: XLSX.WorkBook = await this.loadWorkbook(document, webviewPanel);
const htmlTable = XLSX.utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
webviewPanel.webview.html = \`<!DOCTYPE html><html><body>\${htmlTable}</body></html>\`;
}
}`}
</CodeBlock>
4) Register the custom editor provider in `src/extension.ts`:
5. Registering the custom editor in your `package.json` file.
<CodeBlock language="typescript" value="typescript" title="src/extension.ts (replace contents)">{`\
import * as vscode from 'vscode';
import { ExcelEditorProvider } from './excelEditorProvider';
<CodeBlock language="json" value="json" title="./package.json">{`\
export function activate(context: vscode.ExtensionContext) {
// SheetJS Spreadsheet Viewer extension activating...
const provider = ExcelEditorProvider.register(context);
context.subscriptions.push(provider);
}
export function deactivate() {}`}
</CodeBlock>
5) Register the custom editor in the `contributes` section of `package.json`:
<CodeBlock language="json" value="json" title="package.json (add highlighted lines)">{`\
"main": "./dist/extension.js",
"contributes": {
// highlight-start
@ -306,18 +322,13 @@ export class ExcelEditorProvider implements vscode.CustomReadonlyEditorProvider<
</CodeBlock>
6. Inside the editor, open `src/extension.ts` and press <kbd>F5</kbd> or run the command **Debug: Start Debugging**
from the Command Pallet (<kbd>⇧⌘P</kbd>). This will compile and run the extension in a new Extension Development Host window.
from the Command Palette (<kbd>⇧⌘P</kbd>). This will compile and run the extension in a new Extension Development Host window.
7. Select the new VSCode Window and open a `.xlsx` or `.xls` file.
![Expected output](pathname:///vscode/extension-viewing-xls-file.png)
---
### Learn More
You can check out [the complete SheetJS VS Code extension demo repository](https://git.sheetjs.com/asadbek064/sheetjs-vscode-extension) - your feedback and contributions are welcome!
[^1]: See [`Webview API`](https://code.visualstudio.com/api/extension-guides/webview) for more details.
[^2]: See [`Custom Editor API`](https://code.visualstudio.com/api/extension-guides/custom-editors) documentation for more details.
[^3]: See [`Custom Editor`](https://code.visualstudio.com/api/extension-guides/custom-editors#custom-editor) for more details.

@ -367,7 +367,7 @@ This,is,a,Test
The test suite is regularly run against a number of modern and legacy browsers
using [Sauce Labs](https://saucelabs.com/).
The following chart shows test results on 2024-10-20 for version `0.20.3`:
The following chart shows test results on 2025-05-15 for version `0.20.3`:
[![Build Status](pathname:///test/sheetjs.svg)](https://saucelabs.com/u/sheetjs)

@ -15,6 +15,10 @@
},
"overrides": {
"@cmfcmf/docusaurus-search-local": {
"@algolia/autocomplete-theme-classic": "1.19.1",
"@algolia/autocomplete-js": "1.19.1",
"@algolia/client-search": "5.25.0",
"algoliasearch": "5.25.0",
"@docusaurus/core": "3.7.0"
}
},

@ -1,4 +1,4 @@
import SheetJSModule from './sheetmodule'
import SheetJSModule from './sheetmodule';
export default defineNuxtConfig({
// @ts-ignore

@ -2,7 +2,7 @@
import { defineTransformer } from "@nuxt/content/transformers/utils";
import { read, utils } from "xlsx";
import { readFileSync } from "node:fs";
import { resolve } from 'node:path';
import { resolve } from "node:path";
export default defineTransformer({
name: 'sheetformer',

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="890" height="480" data-created="2024-10-20T20:28:10.455635">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="890" height="480" data-created="2025-05-16T06:48:51.357946">
<defs>
<style>
@font-face {
@ -114,7 +114,7 @@
<svg x="5" y="68" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">131</text>
<text x="7" y="22" text-anchor="left" class="browser_version">138</text>
<use xlink:href="#windows" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10</text>
<use xlink:href="#passing" x="90" width="10"></use>
@ -123,7 +123,7 @@
</svg>
<svg x="220" y="0" width="119" height="280">
<svg x="220" y="0" width="119" height="315">
<use x="12" y="7" width="20" height="20" xlink:href="#chrome" fill="#333f4b"></use>
<text x="42" y="24" text-anchor="left" class="head">Chrome</text>
@ -197,6 +197,16 @@
</svg>
<svg x="5" y="272" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">136</text>
<use xlink:href="#windows" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
</svg>
<svg x="330" y="0" width="119" height="105">
@ -225,33 +235,13 @@
</svg>
<svg x="440" y="0" width="119" height="140">
<svg x="440" y="0" width="119" height="70">
<use x="12" y="7" width="20" height="20" xlink:href="#ios" fill="#333f4b"></use>
<text x="42" y="24" text-anchor="left" class="head">iPad</text>
<svg x="5" y="34" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">11</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.13</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="68" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">13</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.15</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="102" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">15</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">12</text>
@ -261,36 +251,16 @@
</svg>
<svg x="550" y="0" width="119" height="140">
<svg x="550" y="0" width="119" height="70">
<use x="12" y="7" width="20" height="20" xlink:href="#ios" fill="#333f4b"></use>
<text x="42" y="24" text-anchor="left" class="head">iPhone</text>
<svg x="5" y="34" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">10</text>
<text x="7" y="22" text-anchor="left" class="browser_version">15</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.12</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="68" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">12</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.15</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="102" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">14</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">11</text>
<text x="53" y="22" text-anchor="left" class="platform_version">12</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
@ -414,7 +384,7 @@
<svg x="5" y="408" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">130</text>
<text x="7" y="22" text-anchor="left" class="browser_version">135</text>
<use xlink:href="#windows" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10</text>
<use xlink:href="#passing" x="90" width="10"></use>
@ -423,83 +393,13 @@
</svg>
<svg x="770" y="0" width="119" height="350">
<svg x="770" y="0" width="119" height="105">
<use x="12" y="7" width="20" height="20" xlink:href="#safari" fill="#333f4b"></use>
<text x="42" y="24" text-anchor="left" class="head">Safari</text>
<svg x="5" y="34" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">8</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.10</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="68" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">9</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.11</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="102" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">10</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.12</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="136" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">11</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.13</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="170" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">12</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.13</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="204" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">13</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">10.15</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="238" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">14</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">11</text>
<use xlink:href="#passing" x="90" width="10"></use>
</svg>
<svg x="5" y="272" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">15</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">12</text>
@ -508,9 +408,9 @@
<svg x="5" y="306" width="109" height="33" viewBox="0 0 109 33">
<svg x="5" y="68" width="109" height="33" viewBox="0 0 109 33">
<rect x="0" y="0" fill="#69cc01" width="109" height="33" />
<text x="7" y="22" text-anchor="left" class="browser_version">16</text>
<text x="7" y="22" text-anchor="left" class="browser_version">17</text>
<use xlink:href="#mac" x="34" width="15" fill="#000"></use>
<text x="53" y="22" text-anchor="left" class="platform_version">13</text>
<use xlink:href="#passing" x="90" width="10"></use>

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 105 KiB