Migrate Perspective from CDN to npm; upgrade to 4.5.1

Replace runtime CDN imports with static ESM imports from npm packages.
Uses @perspective-dev/client and viewer inline builds (WASM embedded).
Bumps all packages to 4.5.1; d3fc stays at 4.4.1 (no 4.5.x release yet).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-06-12 23:00:23 -04:00
parent 89a70bdf7e
commit 0c3cee4945
3 changed files with 11 additions and 22 deletions

View File

@ -158,7 +158,7 @@ The frontend lives in `ui/src/` and is built to `public/` via `npm run build` fr
### Pages
- **Sources / Rules / Mappings / Records** — standard CRUD pages
- **Pivot** (`ui/src/pages/Pivot.jsx`) — interactive pivot/crosstab powered by Perspective (`@perspective-dev` v4.4.0, loaded from CDN). See `docs/perspective-pivot.md` for the full Perspective API reference.
- **Pivot** (`ui/src/pages/Pivot.jsx`) — interactive pivot/crosstab powered by Perspective (`@perspective-dev` v4.5.1, installed via npm). See `docs/perspective-pivot.md` for the full Perspective API reference.
- **Stacks** — multi-source union views with running balance
- **Log** — import audit trail

View File

@ -10,6 +10,10 @@
"preview": "vite preview"
},
"dependencies": {
"@perspective-dev/client": "^4.5.1",
"@perspective-dev/viewer": "^4.5.1",
"@perspective-dev/viewer-d3fc": "^4.4.1",
"@perspective-dev/viewer-datagrid": "^4.5.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.13.2",

View File

@ -1,34 +1,19 @@
import { useEffect, useRef, useState, useCallback } from 'react'
import { api } from '../api'
import useTheme from '../theme.jsx'
import perspective from '@perspective-dev/client/inline'
import '@perspective-dev/viewer/inline'
import '@perspective-dev/viewer-datagrid'
import '@perspective-dev/viewer-d3fc'
import '@perspective-dev/viewer/themes'
async function fetchAllRows(source) {
const res = await api.getViewData(source, 100000, 0)
return res.rows || []
}
let perspectivePromise = null
function loadPerspective() {
if (perspectivePromise) return perspectivePromise
perspectivePromise = (async () => {
if (!document.getElementById('psp-theme')) {
const link = document.createElement('link')
link.id = 'psp-theme'
link.rel = 'stylesheet'
link.crossOrigin = 'anonymous'
link.href = 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer/dist/css/themes.css'
document.head.appendChild(link)
}
const [{ default: perspective }] = await Promise.all([
import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/client@4.4.0/dist/cdn/perspective.js'),
import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer@4.4.0/dist/cdn/perspective-viewer.js'),
import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer-datagrid@4.4.0/dist/cdn/perspective-viewer-datagrid.js'),
import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer-d3fc@4.4.0/dist/cdn/perspective-viewer-d3fc.js'),
])
return perspective
})()
return perspectivePromise
return Promise.resolve(perspective)
}
function formatVal(v, decimals = 2) {