NuxtJS workarounds
This commit is contained in:
		
							parent
							
								
									9af5473755
								
							
						
					
					
						commit
						d77a40c6bf
					
				@ -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":
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
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!
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
:::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.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### 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`:
 | 
			
		||||
 | 
			
		||||
[](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  | 
		Loading…
	
		Reference in New Issue
	
	Block a user