/** * NDK Browser Bundle Builder * * Creates a browser-compatible IIFE bundle of NDK with: * - Core NDK functionality * - Browser cache * - SQLite WASM cache * - Sessions * - Messages * - Web of Trust (WoT) * - Blossom * - Uses the existing window.NostrTools from nostr.bundle.js * - Exposes NDK as window.NDK */ const esbuild = require('esbuild'); const path = require('path'); const fs = require('fs'); async function buildNDKBundle() { console.log('🔧 Building NDK browser bundle with additional packages...'); console.log('📦 Including: core, wallet, cache-browser, cache-dexie, cache-sqlite-wasm, sessions, messages, wot, blossom'); const __dirname = path.resolve(); // Create a virtual entry point that imports all packages using absolute paths const entryContent = ` // Import the default NDK class import NDK from '${path.join(__dirname, 'ndk/core/src/index.ts')}'; // Import default exports from cache packages import NDKCacheBrowser from '${path.join(__dirname, 'ndk/cache-browser/src/index.ts')}'; import NDKCacheAdapterDexie from '${path.join(__dirname, 'ndk/cache-dexie/src/index.ts')}'; import NDKCacheAdapterSqliteWasm from '${path.join(__dirname, 'ndk/cache-sqlite-wasm/src/index.ts')}'; import { CashuMint, CashuWallet, getDecodedToken, getEncodedTokenV4 } from '${path.join(__dirname, 'ndk/node_modules/@cashu/cashu-ts/lib/cashu-ts.es.js')}'; // Re-export everything as named exports export * from '${path.join(__dirname, 'ndk/core/src/index.ts')}'; export * from '${path.join(__dirname, 'ndk/cache-browser/src/index.ts')}'; export * from '${path.join(__dirname, 'ndk/cache-dexie/src/index.ts')}'; export * from '${path.join(__dirname, 'ndk/cache-sqlite-wasm/src/index.ts')}'; export * from '${path.join(__dirname, 'ndk/sessions/src/index.ts')}'; export * from '${path.join(__dirname, 'ndk/messages/src/index.ts')}'; export * from '${path.join(__dirname, 'ndk/wot/src/index.ts')}'; export * from '${path.join(__dirname, 'ndk/blossom/src/index.ts')}'; export * from '${path.join(__dirname, 'ndk/wallet/src/index.ts')}'; // Re-export default exports as named exports export { NDKCacheBrowser, NDKCacheAdapterDexie, NDKCacheAdapterSqliteWasm, CashuMint, CashuWallet, getDecodedToken, getEncodedTokenV4 }; // Export the main NDK class as default export default NDK; `; // Write the virtual entry point const entryPath = path.join(__dirname, 'build/ndk-entry.js'); fs.mkdirSync('build', { recursive: true }); fs.writeFileSync(entryPath, entryContent); try { const result = await esbuild.build({ entryPoints: [entryPath], bundle: true, format: 'iife', globalName: 'NDK', outfile: 'build/ndk-core.bundle.js', platform: 'browser', target: 'es2020', minify: false, // Set to true for production sourcemap: true, metafile: true, // Enable metafile for analysis // Plugin to map nostr-tools imports to window.NostrTools plugins: [{ name: 'nostr-tools-external', setup(build) { // Intercept all nostr-tools imports (including subpaths like nostr-tools/nip49) build.onResolve({ filter: /^nostr-tools/ }, args => { return { path: args.path, namespace: 'nostr-tools-external' }; }); // Replace with window.NostrTools reference build.onLoad({ filter: /.*/, namespace: 'nostr-tools-external' }, (args) => { // Extract subpath if present (e.g., "nostr-tools/nip49" -> "nip49") const subpath = args.path.replace(/^nostr-tools\/?/, ''); return { contents: ` // Map nostr-tools to window.NostrTools if (typeof window === 'undefined' || !window.NostrTools) { throw new Error('NDK: nostr.bundle.js must be loaded before ndk-core.bundle.js'); } ${subpath ? ` // Access subpath: ${subpath} if (!window.NostrTools.${subpath}) { console.warn('nostr-tools/${subpath} not found in window.NostrTools'); module.exports = {}; } else { module.exports = window.NostrTools.${subpath}; } ` : ` module.exports = window.NostrTools; `} `, loader: 'js' }; }); } }], // Define environment variables for browser define: { 'process.env.NODE_ENV': '"production"', 'global': 'window' }, // Banner to add at the top of the bundle banner: { js: `/** * NDK (Nostr Development Kit) - Browser Bundle * Version: 3.0.0-beta.64 * * IMPORTANT: This bundle requires nostr.bundle.js to be loaded first! * * Usage: * * * * Then access via: window.NDK * * Generated: ${new Date().toISOString()} */ ` } }); // Save metafile for analysis fs.writeFileSync('build/meta.json', JSON.stringify(result.metafile, null, 2)); const stats = fs.statSync('build/ndk-core.bundle.js'); const sizeKB = (stats.size / 1024).toFixed(2); console.log('✅ NDK bundle created successfully!'); console.log(`📏 Bundle size: ${sizeKB} KB`); console.log('📄 Output: build/ndk-core.bundle.js'); console.log('📄 Source map: build/ndk-core.bundle.js.map'); console.log(''); // Analyze bundle composition console.log('📊 Bundle Composition Analysis:'); console.log(''); const inputs = result.metafile.inputs; const packageSizes = {}; for (const [file, info] of Object.entries(inputs)) { let packageName = 'other'; if (file.includes('ndk/core/')) packageName = 'core'; else if (file.includes('ndk/cache-browser/')) packageName = 'cache-browser'; else if (file.includes('ndk/cache-dexie/')) packageName = 'cache-dexie'; else if (file.includes('ndk/cache-sqlite-wasm/')) packageName = 'cache-sqlite-wasm'; else if (file.includes('ndk/sessions/')) packageName = 'sessions'; else if (file.includes('ndk/messages/')) packageName = 'messages'; else if (file.includes('ndk/wot/')) packageName = 'wot'; else if (file.includes('ndk/blossom/')) packageName = 'blossom'; else if (file.includes('node_modules/@noble/')) packageName = 'noble-crypto'; else if (file.includes('node_modules/')) packageName = 'dependencies'; if (!packageSizes[packageName]) packageSizes[packageName] = 0; packageSizes[packageName] += info.bytes; } // Sort by size const sorted = Object.entries(packageSizes) .sort((a, b) => b[1] - a[1]) .map(([name, bytes]) => ({ name, kb: (bytes / 1024).toFixed(2), percent: ((bytes / stats.size) * 100).toFixed(1) })); for (const item of sorted) { const bar = '█'.repeat(Math.floor(item.percent / 2)); console.log(` ${item.name.padEnd(20)} ${item.kb.padStart(8)} KB ${item.percent.padStart(5)}% ${bar}`); } console.log(''); console.log('📋 Usage:'); console.log(' 1. Load nostr.bundle.js first'); console.log(' 2. Load ndk-core.bundle.js second'); console.log(' 3. Access via window.NDK'); } catch (error) { console.error('❌ Build failed:', error); process.exit(1); } } // Run the build buildNDKBundle();