bundle-optimizer

Analyze and reduce bundle size, implement code splitting, optimize dependencies, and improve build performance. Use when bundle is too large, load times are slow, or implementing progressive loading.

$ Installer

git clone https://github.com/joshtol/emotive-engine /tmp/emotive-engine && cp -r /tmp/emotive-engine/skills/bundle-optimizer ~/.claude/skills/emotive-engine

// tip: Run this command in your terminal to install the skill


name: bundle-optimizer description: Analyze and reduce bundle size, implement code splitting, optimize dependencies, and improve build performance. Use when bundle is too large, load times are slow, or implementing progressive loading. trigger: bundle, package, size, optimization, tree-shaking, code splitting, build performance

Bundle Optimizer

You are an expert in optimizing JavaScript bundle sizes and build performance for the emotive-mascot platform.

When to Use This Skill

  • Bundle size exceeds targets (> 250 KB gzipped)
  • Slow initial page load times
  • Implementing code splitting
  • Analyzing dependency composition
  • Tree-shaking optimization
  • Progressive loading implementation

Current Bundle Targets

{
    "emotive-mascot.umd.js": {
        "uncompressed": "< 900 KB",
        "gzipped": "< 234 KB"
    },
    "emotive-mascot.minimal.js": {
        "uncompressed": "< 400 KB",
        "gzipped": "< 120 KB"
    },
    "emotive-mascot.audio.js": {
        "uncompressed": "< 700 KB",
        "gzipped": "< 200 KB"
    }
}

Analysis Tools

Check Current Size

# Build and check sizes
npm run build

# View bundle sizes
ls -lh dist/

# Check gzipped sizes
gzip -c dist/emotive-mascot.umd.js | wc -c

Analyze Bundle Composition

# Generate visual analysis
npm run build:analyze

# This creates bundle-analysis.html showing:
# - Module sizes
# - Duplicate dependencies
# - Large imports

NPM Package Analysis

# Audit dependencies
npm audit

# Check for unused dependencies
npx depcheck

# Analyze package sizes
npx bundle-phobia <package-name>

Optimization Techniques

1. Code Splitting

Split large features into separate chunks:

// Instead of direct import
import { AudioEngine } from '@joshtol/emotive-engine';

// Use dynamic import
const loadAudio = async () => {
    const { AudioEngine } = await import('@joshtol/emotive-engine/audio');
    return new AudioEngine();
};

2. Tree Shaking

Ensure dead code elimination works:

// rollup.config.js
export default {
    treeshake: {
        moduleSideEffects: false,
        propertyReadSideEffects: false,
        unknownGlobalSideEffects: false,
    },
};

// Use named imports (not default)
import { EmotiveMascot, emotions } from '@joshtol/emotive-engine';
// NOT: import EmotiveEngine from '@joshtol/emotive-engine'

3. Minification

Optimize Terser settings:

// rollup.config.js
import terser from '@rollup/plugin-terser';

terser({
    compress: {
        drop_console: true,
        drop_debugger: true,
        pure_funcs: ['console.log', 'console.debug'],
        passes: 2, // Multiple passes for better compression
    },
    mangle: {
        properties: {
            regex: /^_/, // Mangle private properties starting with _
        },
    },
    format: {
        comments: false, // Remove all comments
    },
});

4. Remove Unused Dependencies

# Find what can be removed
npx depcheck

# Uninstall unused packages
npm uninstall <package-name>

Progressive Loading Strategy

Minimal Initial Bundle

Create a minimal bundle for fast first paint:

// emotive-mascot.minimal.js includes ONLY:
// - Core engine
// - Basic emotions (5 most common)
// - Essential physics
// Total: ~120 KB gzipped

Lazy Load Features

Load features as needed:

// Load full emotion set on demand
const loadFullEmotions = async () => {
    const { extendedEmotions } = await import(
        '@joshtol/emotive-engine/emotions/extended'
    );
    mascot.addEmotions(extendedEmotions);
};

// Load audio module only when needed
const enableAudio = async () => {
    const { AudioEngine } = await import('@joshtol/emotive-engine/audio');
    mascot.enableAudio(new AudioEngine());
};

// Load LLM plugin for chat features
const enableChat = async () => {
    const { LLMEmotionPlugin } = await import(
        '@joshtol/emotive-engine/plugins/llm'
    );
    mascot.addPlugin(new LLMEmotionPlugin());
};

Build Configuration

Rollup Config Optimization

// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';

export default {
    input: 'src/index.js',
    output: {
        file: 'dist/emotive-mascot.umd.js',
        format: 'umd',
        name: 'EmotiveMascot',
        sourcemap: false, // Disable in production
    },
    plugins: [
        resolve({
            browser: true,
            preferBuiltins: false,
        }),
        commonjs(),
        terser({
            compress: {
                drop_console: true,
                passes: 2,
            },
        }),
    ],
    treeshake: {
        moduleSideEffects: false,
    },
};

Next.js Config Optimization

// next.config.js
module.exports = {
    swcMinify: true, // Use SWC for faster minification

    webpack: (config, { isServer }) => {
        if (!isServer) {
            // Bundle analyzer
            if (process.env.ANALYZE === 'true') {
                const {
                    BundleAnalyzerPlugin,
                } = require('webpack-bundle-analyzer');
                config.plugins.push(
                    new BundleAnalyzerPlugin({
                        analyzerMode: 'static',
                        openAnalyzer: true,
                    })
                );
            }

            // Optimize chunks
            config.optimization.splitChunks = {
                chunks: 'all',
                cacheGroups: {
                    default: false,
                    vendors: false,
                    // Separate emotive-mascot into its own chunk
                    emotive: {
                        name: 'emotive-mascot',
                        test: /[\\/]node_modules[\\/]@joshtol[\\/]emotive-engine/,
                        priority: 40,
                    },
                },
            };
        }

        return config;
    },
};

Monitoring Bundle Size

Package.json Configuration

{
    "bundlesize": [
        {
            "path": "./dist/emotive-mascot.umd.js",
            "maxSize": "900 KB",
            "compression": "none"
        },
        {
            "path": "./dist/emotive-mascot.umd.js",
            "maxSize": "250 KB",
            "compression": "gzip"
        }
    ],
    "scripts": {
        "size": "bundlesize",
        "build:check": "npm run build && npm run size"
    }
}

CI/CD Integration

# .github/workflows/bundle-size.yml
name: Bundle Size Check

on: [pull_request]

jobs:
    check-size:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v2
            - run: npm ci
            - run: npm run build
            - run: npm run size

Quick Wins Checklist

  • Remove unused dependencies (run npx depcheck)
  • Enable tree-shaking in build config
  • Use terser with aggressive settings
  • Remove console.logs in production
  • Disable sourcemaps in production
  • Use dynamic imports for large features
  • Compress images and assets
  • Remove duplicate dependencies
  • Use minimal build for simple use cases
  • Monitor bundle size in CI/CD

Common Issues

Issue: Bundle size suddenly increased Solution: Run npm run build:analyze to identify what changed

Issue: Tree-shaking not working Solution: Ensure all imports are named (not default) and check sideEffects: false in package.json

Issue: Dependencies duplicated Solution: Check npm/yarn lock file, use npm dedupe

Issue: Slow build times Solution: Enable caching, use esbuild or swc instead of Babel

Resources