The `<perspective-viewer>` Custom Element, frontend for Perspective.js, providing interactive analytics and data visualization.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Manual WebAssembly initialization for advanced use cases and custom deployments.
Initialize the perspective-viewer WebAssembly module manually when automatic initialization is not suitable.
/**
* Initialize the perspective-viewer WebAssembly client
* @param wasm_binary - WebAssembly binary data in various formats
*/
function init_client(
wasm_binary: Promise<Response | ArrayBuffer | Uint8Array>
| Response
| ArrayBuffer
| Uint8Array
): Promise<void>;
/**
* Default export providing init_client function
*/
export default { init_client };import { init_client } from "@finos/perspective-viewer";
// Initialize with WebAssembly binary URL
const wasmResponse = fetch("/path/to/perspective-viewer.wasm");
await init_client(wasmResponse);
// Now the custom elements are available
const viewer = document.createElement("perspective-viewer");import { init_client } from "@finos/perspective-viewer";
// Load WASM binary as ArrayBuffer
const wasmBinary = await fetch("/path/to/perspective-viewer.wasm")
.then(response => response.arrayBuffer());
await init_client(wasmBinary);
// Custom elements are now registered
const viewer = document.createElement("perspective-viewer");import { init_client } from "@finos/perspective-viewer";
// Example: Load from base64 or other encoded format
const base64Wasm = "AGFzbQEAAAABhQEYY..."; // Base64 encoded WASM
const wasmBytes = Uint8Array.from(atob(base64Wasm), c => c.charCodeAt(0));
await init_client(wasmBytes);import perspectiveViewer from "@finos/perspective-viewer";
// Alternative import pattern using default export
const wasmResponse = fetch("/path/to/perspective-viewer.wasm");
await perspectiveViewer.init_client(wasmResponse);
// Custom elements are now registered
const viewer = document.createElement("perspective-viewer");import { init_client } from "@finos/perspective-viewer";
class PerspectiveManager {
private initialized = false;
async ensureInitialized() {
if (!this.initialized) {
await this.initializePerspective();
this.initialized = true;
}
}
private async initializePerspective() {
try {
// Try CDN first
const cdnWasm = fetch("https://cdn.jsdelivr.net/npm/@finos/perspective-viewer/dist/cdn/perspective-viewer.wasm");
await init_client(cdnWasm);
} catch (error) {
console.warn("CDN initialization failed, trying local:", error);
// Fallback to local WASM
const localWasm = fetch("/assets/perspective-viewer.wasm");
await init_client(localWasm);
}
}
async createViewer(): Promise<HTMLPerspectiveViewerElement> {
await this.ensureInitialized();
return document.createElement("perspective-viewer");
}
}
// Usage
const manager = new PerspectiveManager();
const viewer = await manager.createViewer();import { init_client } from "@finos/perspective-viewer";
class LazyPerspectiveLoader {
private initPromise: Promise<void> | null = null;
async loadPerspective(): Promise<void> {
if (!this.initPromise) {
this.initPromise = this.performInitialization();
}
return this.initPromise;
}
private async performInitialization(): Promise<void> {
const startTime = performance.now();
try {
// Show loading indicator
this.showLoadingIndicator();
// Load WASM with timeout
const wasmPromise = fetch("/wasm/perspective-viewer.wasm");
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error("WASM load timeout")), 10000);
});
const wasmResponse = await Promise.race([wasmPromise, timeoutPromise]);
if (!wasmResponse.ok) {
throw new Error(`Failed to load WASM: ${wasmResponse.status}`);
}
await init_client(wasmResponse);
const loadTime = performance.now() - startTime;
console.log(`Perspective initialized in ${loadTime.toFixed(2)}ms`);
} catch (error) {
console.error("Failed to initialize Perspective:", error);
this.showErrorState(error);
throw error;
} finally {
this.hideLoadingIndicator();
}
}
private showLoadingIndicator() {
const indicator = document.createElement("div");
indicator.id = "perspective-loading";
indicator.textContent = "Loading Perspective Viewer...";
indicator.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
background: rgba(0,0,0,0.8);
color: white;
border-radius: 5px;
z-index: 10000;
`;
document.body.appendChild(indicator);
}
private hideLoadingIndicator() {
const indicator = document.getElementById("perspective-loading");
if (indicator) {
indicator.remove();
}
}
private showErrorState(error: Error) {
const errorDiv = document.createElement("div");
errorDiv.textContent = `Failed to load Perspective: ${error.message}`;
errorDiv.style.cssText = `
color: red;
padding: 10px;
border: 1px solid red;
background: #ffe6e6;
margin: 10px;
`;
document.body.appendChild(errorDiv);
// Provide retry button
const retryBtn = document.createElement("button");
retryBtn.textContent = "Retry";
retryBtn.onclick = () => {
errorDiv.remove();
this.initPromise = null; // Reset for retry
this.loadPerspective();
};
errorDiv.appendChild(retryBtn);
}
}
// Usage
const loader = new LazyPerspectiveLoader();
document.getElementById("load-perspective")?.addEventListener("click", async () => {
try {
await loader.loadPerspective();
// Create viewer after successful initialization
const viewer = document.createElement("perspective-viewer");
document.body.appendChild(viewer);
} catch (error) {
console.error("Could not load Perspective:", error);
}
});import { init_client } from "@finos/perspective-viewer";
class PWAPerspectiveLoader {
private readonly CACHE_NAME = "perspective-wasm-v1";
private readonly WASM_URL = "/wasm/perspective-viewer.wasm";
async initializeWithCaching(): Promise<void> {
try {
// Try to get from cache first
const cachedWasm = await this.getCachedWasm();
if (cachedWasm) {
console.log("Using cached WASM");
await init_client(cachedWasm);
return;
}
// Download and cache
console.log("Downloading WASM");
const wasmResponse = await fetch(this.WASM_URL);
if (!wasmResponse.ok) {
throw new Error(`HTTP ${wasmResponse.status}: ${wasmResponse.statusText}`);
}
// Cache for future use
await this.cacheWasm(wasmResponse.clone());
// Initialize
await init_client(wasmResponse);
} catch (error) {
console.error("WASM initialization failed:", error);
throw error;
}
}
private async getCachedWasm(): Promise<Response | null> {
if (!("caches" in window)) {
return null;
}
const cache = await caches.open(this.CACHE_NAME);
return await cache.match(this.WASM_URL);
}
private async cacheWasm(response: Response): Promise<void> {
if (!("caches" in window)) {
return;
}
const cache = await caches.open(this.CACHE_NAME);
await cache.put(this.WASM_URL, response);
}
async clearCache(): Promise<void> {
if ("caches" in window) {
await caches.delete(this.CACHE_NAME);
}
}
}
// Usage in PWA
const pwaLoader = new PWAPerspectiveLoader();
// Initialize on app startup
pwaLoader.initializeWithCaching()
.then(() => {
console.log("Perspective ready");
// App is ready to create viewers
})
.catch(error => {
console.error("Perspective initialization failed:", error);
// Show fallback UI
});// main.ts
import { init_client } from "@finos/perspective-viewer";
class WorkerPerspectiveLoader {
async initializeWithWorker(): Promise<void> {
return new Promise((resolve, reject) => {
const worker = new Worker("/js/wasm-loader-worker.js");
worker.postMessage({ action: "loadWasm", url: "/wasm/perspective-viewer.wasm" });
worker.onmessage = async (event) => {
const { action, data, error } = event.data;
if (action === "wasmLoaded") {
try {
await init_client(data);
resolve();
} catch (err) {
reject(err);
} finally {
worker.terminate();
}
} else if (action === "error") {
reject(new Error(error));
worker.terminate();
}
};
worker.onerror = (error) => {
reject(error);
worker.terminate();
};
// Timeout after 30 seconds
setTimeout(() => {
reject(new Error("Worker timeout"));
worker.terminate();
}, 30000);
});
}
}// wasm-loader-worker.js (Web Worker)
self.onmessage = async function(event) {
const { action, url } = event.data;
if (action === "loadWasm") {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const arrayBuffer = await response.arrayBuffer();
self.postMessage({
action: "wasmLoaded",
data: arrayBuffer
});
} catch (error) {
self.postMessage({
action: "error",
error: error.message
});
}
}
};// Use specific entry points to reduce bundle size
import { init_client } from "@finos/perspective-viewer/dist/esm/bootstrap";
// Initialize without side effects
await init_client(wasmBinary);
// Only then import the components you need
const { PerspectiveViewerElement } = await import("@finos/perspective-viewer/dist/esm/perspective-viewer");import { init_client } from "@finos/perspective-viewer";
async function initializePerspective() {
const isDevelopment = process.env.NODE_ENV === "development";
if (isDevelopment) {
// Development: Use local file with source maps
const wasmResponse = fetch("/dev/perspective-viewer.wasm");
await init_client(wasmResponse);
} else {
// Production: Use CDN with compression
const wasmResponse = fetch("https://cdn.jsdelivr.net/npm/@finos/perspective-viewer/dist/cdn/perspective-viewer.wasm");
await init_client(wasmResponse);
}
}import { init_client } from "@finos/perspective-viewer";
async function robustInitialization() {
const wasmSources = [
"https://cdn.jsdelivr.net/npm/@finos/perspective-viewer/dist/cdn/perspective-viewer.wasm",
"https://unpkg.com/@finos/perspective-viewer/dist/cdn/perspective-viewer.wasm",
"/local/perspective-viewer.wasm"
];
for (const source of wasmSources) {
try {
console.log(`Attempting to load WASM from: ${source}`);
const response = await fetch(source);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
await init_client(response);
console.log(`Successfully initialized from: ${source}`);
return;
} catch (error) {
console.warn(`Failed to load from ${source}:`, error);
continue;
}
}
throw new Error("All WASM sources failed to load");
}enum InitializationState {
NotStarted = "not-started",
Loading = "loading",
Ready = "ready",
Failed = "failed"
}
class PerspectiveInitializer {
private state = InitializationState.NotStarted;
private initPromise: Promise<void> | null = null;
private error: Error | null = null;
getState(): InitializationState {
return this.state;
}
getError(): Error | null {
return this.error;
}
async initialize(): Promise<void> {
if (this.state === InitializationState.Ready) {
return;
}
if (this.state === InitializationState.Loading) {
return this.initPromise!;
}
this.state = InitializationState.Loading;
this.error = null;
this.initPromise = this.performInitialization();
try {
await this.initPromise;
this.state = InitializationState.Ready;
} catch (error) {
this.state = InitializationState.Failed;
this.error = error as Error;
throw error;
}
}
private async performInitialization(): Promise<void> {
const wasmResponse = await fetch("/wasm/perspective-viewer.wasm");
await init_client(wasmResponse);
}
}
// Usage with state management
const initializer = new PerspectiveInitializer();
// Check state before using
if (initializer.getState() !== InitializationState.Ready) {
await initializer.initialize();
}
const viewer = document.createElement("perspective-viewer");