diff --git a/libraries/js/README-external-assets.md b/libraries/js/README-external-assets.md new file mode 100644 index 0000000..ec4524c --- /dev/null +++ b/libraries/js/README-external-assets.md @@ -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 + +``` + +### 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 + +``` + +## 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. \ No newline at end of file diff --git a/libraries/js/package-lock.json b/libraries/js/package-lock.json index b63f644..452e6fe 100644 --- a/libraries/js/package-lock.json +++ b/libraries/js/package-lock.json @@ -1,12 +1,12 @@ { "name": "shellviz", - "version": "0.2.15-beta.0", + "version": "0.5.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "shellviz", - "version": "0.2.15-beta.0", + "version": "0.5.0-beta.0", "license": "MIT", "dependencies": { "qrcode-terminal": "^0.12.0", diff --git a/libraries/js/package.json b/libraries/js/package.json index ef9a298..22beec1 100644 --- a/libraries/js/package.json +++ b/libraries/js/package.json @@ -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": [ @@ -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" }, diff --git a/libraries/js/scripts/postbuild.js b/libraries/js/scripts/postbuild.js index 621c1a9..9e170c2 100644 --- a/libraries/js/scripts/postbuild.js +++ b/libraries/js/scripts/postbuild.js @@ -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!'); diff --git a/libraries/js/src/browser-widget.js b/libraries/js/src/browser-widget.js index 4367fae..862f31f 100644 --- a/libraries/js/src/browser-widget.js +++ b/libraries/js/src/browser-widget.js @@ -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; } @@ -531,7 +587,23 @@ class BrowserWidget { _showFallbackUI(container, errorInfo) { container.innerHTML = ` -
Unable to load widget assets.
+ ${typeof errorInfo === 'string' ? `${errorInfo}
` : ''} +Option 1: Include embedded-assets.mjs alongside your main bundle
+Option 2: Ensure embedded-assets.mjs is accessible at one of these locations:
+Option 3: Use a bundled version that includes all assets
+