Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions libraries/js/README-external-assets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# External Assets Implementation

As of version 0.5.0, Shellviz has separated embedded assets to dramatically reduce bundle sizes.

## Bundle Size Improvements

| Bundle | Before | After | Reduction |
|--------|--------|-------|-----------|
| browser_client.mjs | 1.3MB | 34KB | 97% |
| browser_client.umd.js | 1.4MB | 38KB | 97% |
| node_client.cjs | 1.5MB | 191KB | 87% |
| node_client.js | 1.4MB | 70KB | 95% |

## How It Works

The large React app assets (CSS and JS) are now bundled separately in `embedded-assets.mjs` (1.3MB). This file is loaded dynamically only when the browser widget is used.

## Usage Options

### 1. Auto-Loading (Recommended)
The library will automatically try to load embedded assets from:
- Same directory as the main script
- CDN locations (unpkg, jsdelivr)

```html
<script type="module">
import { shellviz } from './browser_client.mjs';
// Assets will be loaded automatically when widget is shown
const client = shellviz('http://localhost:8080');
client.showWidget();
</script>
```

### 2. Manual Asset Placement
Place `embedded-assets.mjs` in the same directory as your main bundle:

```
your-app/
├── browser_client.mjs
├── embedded-assets.mjs ← Include this file
└── index.html
```

### 3. CDN Usage
Assets are automatically served from CDN if local copies aren't found:

```html
<script type="module">
import { shellviz } from 'https://unpkg.com/shellviz@latest/build/browser_client.mjs';
// Will load embedded-assets.mjs from CDN automatically
</script>
```

## Migration Guide

### Before (v0.4.x)
```javascript
// Everything was bundled together (large files)
import { shellviz } from 'shellviz';
```

### After (v0.5.0+)
```javascript
// Main bundle is small, assets loaded on-demand
import { shellviz } from 'shellviz';
// No code changes needed! Assets load automatically.
```

## Build Process Changes

The build now generates:
- `browser_client.mjs` - Main ESM bundle (small)
- `browser_client.umd.js` - UMD bundle (small)
- `node_client.js` - Node ESM bundle (small)
- `node_client.cjs` - Node CJS bundle (small)
- `embedded-assets.mjs` - React app assets (large, loaded on-demand)

## Environment Support

- ✅ Browser ESM modules
- ✅ Browser UMD/script tags
- ✅ Node.js CJS
- ✅ Node.js ESM
- ✅ CDN usage (unpkg, jsdelivr)
- ✅ Bundlers (webpack, rollup, vite, etc.)

## Error Handling

If assets can't be loaded, the widget shows a helpful fallback UI with instructions for fixing the issue.
4 changes: 2 additions & 2 deletions libraries/js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 10 additions & 5 deletions libraries/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
},
"import": "./build/node_client.js",
"require": "./build/node_client.cjs"
},
"./embedded-assets": {
"import": "./build/embedded-assets.mjs",
"require": "./build/embedded-assets.mjs"
}
},
"files": [
Expand All @@ -26,11 +30,12 @@
"build:client": "cd ../../client && npm run build",
"copy:client": "cp -r ../../client/build/* build/client_build/ 2>/dev/null || echo 'Client build not found, skipping...'",
"embed:assets": "node scripts/embed-assets.js",
"build:node:client:cjs": "esbuild ./src/client.js --bundle --platform=node --format=cjs --outfile=build/node_client.cjs",
"build:node:client:esm": "esbuild ./src/client.js --bundle --external:ws --platform=node --format=esm --outfile=build/node_client.js",
"build:browser:esm": "esbuild ./src/client.js --bundle --platform=browser --format=esm --outfile=build/browser_client.mjs --banner:js=\"/* eslint-disable */\"",
"build:browser:umd": "esbuild ./src/client.js --bundle --platform=browser --format=iife --global-name=shellviz --outfile=build/browser_client.umd.js --banner:js=\"/* eslint-disable */\"",
"build": "npm-run-all --sequential build:client copy:client embed:assets build:node:client:cjs build:node:client:esm build:browser:esm build:browser:umd",
"build:embedded-assets": "esbuild ./src/embedded-assets.js --bundle --platform=browser --format=esm --outfile=build/embedded-assets.mjs --banner:js=\"/* eslint-disable */\"",
"build:node:client:cjs": "esbuild ./src/client.js --bundle --external:./embedded-assets.js --platform=node --format=cjs --outfile=build/node_client.cjs",
"build:node:client:esm": "esbuild ./src/client.js --bundle --external:ws --external:./embedded-assets.js --platform=node --format=esm --outfile=build/node_client.js",
"build:browser:esm": "esbuild ./src/client.js --bundle --external:./embedded-assets.js --platform=browser --format=esm --outfile=build/browser_client.mjs --banner:js=\"/* eslint-disable */\"",
"build:browser:umd": "esbuild ./src/client.js --bundle --external:./embedded-assets.js --platform=browser --format=iife --global-name=shellviz --outfile=build/browser_client.umd.js --banner:js=\"/* eslint-disable */\"",
"build": "npm-run-all --sequential build:client copy:client embed:assets build:embedded-assets build:node:client:cjs build:node:client:esm build:browser:esm build:browser:umd",
"postbuild": "node scripts/postbuild.js",
"pack": "npm run build && npm pack --pack-destination=dist"
},
Expand Down
63 changes: 35 additions & 28 deletions libraries/js/scripts/postbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,38 @@
// return;


// import fs from 'fs';
// import path from 'path';
// import { fileURLToPath } from 'url';

// // ESM-compatible __dirname
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = path.dirname(__filename);

// // Get the project root directory
// const rootDir = path.resolve(__dirname, '../');
// const clientDist = path.join(rootDir, '../', '../', 'client', 'build');
// const packageDist = path.join(rootDir, 'build', 'client_build');

// // Remove the dist directory if it exists
// if (fs.existsSync(packageDist)) {
// fs.rmSync(packageDist, { recursive: true, force: true });
// console.log(`Emptied directory: ${packageDist}`);
// }

// // Use fs.cpSync (Node.js 16+) to recursively copy everything from client/dist to dist
// fs.cpSync(clientDist, packageDist, { recursive: true });
// console.log(`Copied all files from ${clientDist} to ${packageDist}`);

// // Copy type definitions
// const typeSrc = path.join(rootDir, 'src', 'client.d.ts');
// const typeDest = path.join(rootDir, 'build', 'client.d.ts');
// fs.copyFileSync(typeSrc, typeDest);
// console.log(`Copied types from ${typeSrc} to ${typeDest}`);
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

// ESM-compatible __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Get the project root directory
const rootDir = path.resolve(__dirname, '../');

// Copy type definitions
const typeSrc = path.join(rootDir, 'src', 'client.d.ts');
const typeDest = path.join(rootDir, 'build', 'client.d.ts');

if (fs.existsSync(typeSrc)) {
fs.copyFileSync(typeSrc, typeDest);
console.log(`✅ Copied types from ${typeSrc} to ${typeDest}`);
}

// Check build sizes
const buildDir = path.join(rootDir, 'build');
const files = ['browser_client.mjs', 'browser_client.umd.js', 'node_client.cjs', 'node_client.js', 'embedded-assets.mjs'];

console.log('\n📊 Build sizes:');
files.forEach(file => {
const filePath = path.join(buildDir, file);
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
const sizeKB = (stats.size / 1024).toFixed(1);
console.log(` ${file}: ${sizeKB}KB`);
}
});

console.log('\n💡 Note: embedded-assets.mjs is now separate for smaller main bundles!');
78 changes: 75 additions & 3 deletions libraries/js/src/browser-widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,13 +457,69 @@ class BrowserWidget {

async _tryLoadEmbeddedAssets() {
try {
const assets = await import('./embedded-assets.js');
// First try to load from the current module location (bundled)
const assets = await import('./embedded-assets.mjs');
if (assets.hasEmbeddedAssets()) {
return assets;
}
} catch (e) {
// Embedded assets not available
// Embedded assets not bundled, try to load externally
console.log('Embedded assets not bundled, attempting external load...');
}

// Try to load from external URL (for when assets are split out)
try {
// Determine base URL for the library
const currentScript = document.currentScript;
const scriptSrc = currentScript ? currentScript.src : '';
console.log('scriptSrc', scriptSrc);

const possibleUrls = [];

// If we have a script source, try relative to that location
if (scriptSrc) {
try {
const baseUrl = new URL('./', scriptSrc).href;
console.log('baseUrl', baseUrl);
possibleUrls.push(
new URL('./embedded-assets.mjs', baseUrl).href
);
} catch (e) {
console.warn('Could not construct URLs relative to script:', e);
}
}

// Always try relative to current page
possibleUrls.push(
'./embedded-assets.mjs',
'./build/embedded-assets.mjs',
'../build/embedded-assets.mjs',
// CDN locations (if using unpkg/jsdelivr)
'https://unpkg.com/shellviz@latest/build/embedded-assets.mjs',
'https://cdn.jsdelivr.net/npm/shellviz@latest/build/embedded-assets.mjs'
);

console.log('possibleUrls', possibleUrls);

for (const url of possibleUrls) {
try {
const assets = await import(url);
if (assets.hasEmbeddedAssets()) {
console.log(`✅ Loaded embedded assets from: ${url}`);
return assets;
}
} catch (urlError) {
console.error('Error loading embedded assets:', urlError);
// Continue to next URL
continue;
}
}
} catch (e) {
console.error('Error loading embedded assets:', e);
// External loading failed
}

console.warn('⚠️ Could not load embedded assets. Widget will show fallback UI.');
return null;
}

Expand Down Expand Up @@ -531,7 +587,23 @@ class BrowserWidget {

_showFallbackUI(container, errorInfo) {
container.innerHTML = `
<div style="padding: 20px; text-align: center; color: #666;">Error: ${typeof errorInfo === 'string' ? errorInfo : 'Asset loading failed'}</div>
<div style="padding: 20px; text-align: center; color: #666; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;">
<h3 style="color: #333; margin-bottom: 10px;">⚠️ Shellviz Widget</h3>
<p style="margin-bottom: 15px;">Unable to load widget assets.</p>
${typeof errorInfo === 'string' ? `<p style="font-size: 0.9em; color: #888;">${errorInfo}</p>` : ''}
<details style="margin-top: 15px; text-align: left;">
<summary style="cursor: pointer; color: #666;">💡 How to fix this</summary>
<div style="margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 4px; font-size: 0.85em;">
<p><strong>Option 1:</strong> Include embedded-assets.mjs alongside your main bundle</p>
<p><strong>Option 2:</strong> Ensure embedded-assets.mjs is accessible at one of these locations:</p>
<ul style="margin: 5px 0; padding-left: 20px;">
<li>./embedded-assets.mjs (same directory as main script)</li>
<li>CDN: unpkg or jsdelivr</li>
</ul>
<p><strong>Option 3:</strong> Use a bundled version that includes all assets</p>
</div>
</details>
</div>
`;
}

Expand Down
2 changes: 1 addition & 1 deletion libraries/js/src/embedded-assets.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion libraries/js/test-simple-web/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<script src="http://localhost:4005/build/browser_client.umd.js"></script>
<script src="http://localhost:8000/build/browser_client.umd.js"></script>
<script>
const { log } = shellviz;
log('hello from the browser');
Expand Down
51 changes: 51 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"shellviz": "^0.5.0-beta.0"
}
}
5 changes: 3 additions & 2 deletions test-widget.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,17 @@ <h2>Test Different Data Types</h2>
</div>

<!-- Load ShellViz client -->

<script type="module">
// For this demo, we'll load from the built browser client
import * as shellviz from './libraries/js/build/browser_client.mjs';

// Make it available globally for the demo buttons
window.shellviz = shellviz;
// window.shellviz = shellviz;

// Also expose individual functions
window.initWidget = () => {
shellviz.renderInBrowser();
shellviz.show();
};

window.removeWidget = () => {
Expand Down