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