| name | bundle-analyzer |
| description | Analyze Kibana bundle size and performance with webpack-bundle-analyzer, duplicate detection, and tree-shaking optimization recommendations. |
Bundle Analyzer Agent
Description
Analyze Kibana bundle size and performance: run webpack-bundle-analyzer, identify large dependencies, suggest alternatives, recommend code splitting and tree-shaking optimizations, and measure bundle size reduction impact.
Trigger Patterns
- "analyze bundle size for [plugin]"
- "find large dependencies in [package]"
- "suggest code splitting for [feature]"
- "optimize bundle for [plugin]"
- "measure bundle size impact of [change]"
- "compare bundle sizes between [branch1] and [branch2]"
Capabilities
1. Bundle Analysis
- Run webpack-bundle-analyzer
- Generate visual bundle reports
- Identify largest modules and chunks
- Calculate gzipped sizes
2. Dependency Analysis
- List all dependencies by size
- Identify duplicate dependencies
- Find unused dependencies
- Suggest lighter alternatives
3. Optimization Recommendations
- Code splitting opportunities
- Tree-shaking improvements
- Dynamic imports for large modules
- Lazy loading strategies
4. Size Tracking
- Track bundle size over time
- Compare before/after changes
- Set size budgets and alerts
- Generate size impact reports
5. Performance Metrics
- Initial load time impact
- Chunk loading performance
- Cache effectiveness
- Network transfer size
Bundle Analysis Workflow
Step 1: Build with Analyzer
NODE_OPTIONS="--max-old-space-size=8192" \
node scripts/build_kibana_platform_plugins.js \
--focus @kbn/security-solution-plugin \
--dist \
--profile \
--no-cache
yarn workspace @kbn/security-solution-plugin build
Step 2: Open Report
open build/plugins/security_solution/stats.html
npx http-server build/plugins/security_solution -o /stats.html
Step 3: Analyze Report
Report Sections:
- Stat Size: Raw source code size
- Parsed Size: Minified size
- Gzipped Size: Compressed size (network transfer)
What to Look For:
-
Large modules (>500KB parsed)
- Often: lodash, moment, monaco-editor
- Action: Replace or split
-
Duplicate dependencies (same package, different versions)
- Often: react, @elastic/eui
- Action: Deduplicate via yarn resolutions
-
Unused code (large library, small usage)
- Often: Importing entire library for one function
- Action: Import specific functions
-
Large chunks (>1MB gzipped)
- Often: Security Solution, Canvas
- Action: Code split by route
Step 4: Generate Size Report
cat > bundle-report.md <<EOF
# Bundle Analysis: Security Solution Plugin
## Summary
- **Total Size (parsed):** 15.2 MB
- **Total Size (gzipped):** 4.8 MB
- **Largest Module:** monaco-editor (2.3 MB)
- **Chunk Count:** 25
## Top 10 Largest Modules
| Module | Parsed Size | Gzipped Size |
|--------|-------------|--------------|
| monaco-editor | 2.3 MB | 720 KB |
| lodash | 1.8 MB | 580 KB |
| @elastic/eui | 1.5 MB | 480 KB |
| react | 800 KB | 250 KB |
| d3 | 650 KB | 200 KB |
## Optimization Opportunities
1. **Monaco Editor:** Lazy load (route-based split)
2. **Lodash:** Replace with lodash-es (tree-shakeable)
3. **D3:** Import specific modules, not entire library
EOF
Dependency Analysis
List Dependencies by Size
node scripts/build_kibana_platform_plugins.js \
--focus @kbn/security-solution-plugin \
--dist \
--profile \
--json > bundle-stats.json
jq '.modules[] | {name: .name, size: .size}' bundle-stats.json \
| jq -s 'sort_by(.size) | reverse | .[:10]'
Find Duplicate Dependencies
yarn why <package-name>
cat > package.json <<EOF
{
"resolutions": {
"react": "^18.3.0"
}
}
EOF
yarn kbn bootstrap
Find Unused Dependencies
yarn add --dev depcheck
npx depcheck x-pack/solutions/security/plugins/security_solution
yarn workspace @kbn/security-solution-plugin remove unused-package-1
Suggest Lighter Alternatives
| Heavy Dependency | Lighter Alternative | Size Savings |
|---|
lodash (1.8 MB) | lodash-es (tree-shakeable) | ~80% |
moment (800 KB) | date-fns (modular) | ~70% |
axios (500 KB) | fetch (native) | 100% |
d3 (650 KB) | d3-* (specific modules) | ~60% |
monaco-editor (2.3 MB) | Lazy load + split | 0% initial |
Optimization Strategies
1. Code Splitting (Route-Based)
import { MonacoEditor } from 'monaco-editor';
export function RuleEditor() {
return <MonacoEditor />;
}
import { lazy, Suspense } from 'react';
const MonacoEditor = lazy(() => import('monaco-editor').then(m => ({ default: m.MonacoEditor })));
export function RuleEditor() {
return (
<Suspense fallback={<div>Loading editor...</div>}>
<MonacoEditor />
</Suspense>
);
}
2. Tree-Shaking (Import Specific Modules)
import _ from 'lodash';
const result = _.debounce(fn, 300);
import debounce from 'lodash-es/debounce';
const result = debounce(fn, 300);
3. Dynamic Imports (Conditional Loading)
import { Chart } from 'large-charting-lib';
export function Dashboard({ showChart }) {
return showChart ? <Chart /> : null;
}
export function Dashboard({ showChart }) {
const [Chart, setChart] = useState(null);
useEffect(() => {
if (showChart && !Chart) {
import('large-charting-lib').then(({ Chart }) => setChart(() => Chart));
}
}, [showChart, Chart]);
return showChart && Chart ? <Chart /> : null;
}
4. Externalize Dependencies (CDN)
module.exports = {
externals: {
react: 'React',
'react-dom': 'ReactDOM',
},
};
5. Remove Unused Code (Dead Code Elimination)
import { utilityA, utilityB, utilityC, utilityD } from './utils';
import { utilityA } from './utils/utility_a';
Size Tracking & Budgets
Track Bundle Size Over Time
node scripts/build_kibana_platform_plugins.js \
--focus @kbn/security-solution-plugin \
--dist \
--profile
BASELINE_SIZE=$(stat -f%z build/plugins/security_solution/target/public/security_solution.chunk.1.js)
echo "Baseline size: $BASELINE_SIZE bytes" > bundle-size-baseline.txt
node scripts/build_kibana_platform_plugins.js \
--focus @kbn/security-solution-plugin \
--dist \
--profile
NEW_SIZE=$(stat -f%z build/plugins/security_solution/target/public/security_solution.chunk.1.js)
DIFF=$((NEW_SIZE - BASELINE_SIZE))
PERCENT=$(awk "BEGIN {printf \"%.2f\", ($DIFF / $BASELINE_SIZE) * 100}")
echo "New size: $NEW_SIZE bytes"
echo "Diff: $DIFF bytes ($PERCENT%)"
Set Size Budgets
module.exports = {
performance: {
maxAssetSize: 500000,
maxEntrypointSize: 1000000,
hints: 'warning',
},
};
steps:
- label: "Check bundle size"
command: |
node scripts/build_kibana_platform_plugins.js --focus @kbn/security-solution-plugin --dist
SIZE=$(stat -f%z build/plugins/security_solution/target/public/security_solution.chunk.1.js)
MAX_SIZE=5000000 # 5 MB limit
if [ $SIZE -gt $MAX_SIZE ]; then
echo "❌ Bundle size exceeds limit: $SIZE > $MAX_SIZE"
exit 1
fi
echo "✅ Bundle size within limit: $SIZE <= $MAX_SIZE"
Compare Branches
git checkout main
yarn kbn bootstrap
node scripts/build_kibana_platform_plugins.js --focus @kbn/security-solution-plugin --dist
MAIN_SIZE=$(stat -f%z build/plugins/security_solution/target/public/security_solution.chunk.1.js)
git checkout feature/my-changes
yarn kbn bootstrap
node scripts/build_kibana_platform_plugins.js --focus @kbn/security-solution-plugin --dist
FEATURE_SIZE=$(stat -f%z build/plugins/security_solution/target/public/security_solution.chunk.1.js)
DIFF=$((FEATURE_SIZE - MAIN_SIZE))
PERCENT=$(awk "BEGIN {printf \"%.2f\", ($DIFF / $MAIN_SIZE) * 100}")
cat > bundle-comparison.md <<EOF
# Bundle Size Comparison
| Branch | Size | Diff | % Change |
|--------|------|------|----------|
| main | $(numfmt --to=iec $MAIN_SIZE) | - | - |
| feature/my-changes | $(numfmt --to=iec $FEATURE_SIZE) | $(numfmt --to=iec $DIFF) | $PERCENT% |
## Impact
$(if [ $DIFF -gt 0 ]; then echo "⚠️ Bundle size increased"; else echo "✅ Bundle size decreased"; fi)
EOF
Performance Metrics
Initial Load Time
performance.mark('plugin-load-start');
performance.mark('plugin-load-end');
performance.measure('plugin-load', 'plugin-load-start', 'plugin-load-end');
const measure = performance.getEntriesByName('plugin-load')[0];
console.log('Plugin load time:', measure.duration, 'ms');
Chunk Loading Performance
Cache Effectiveness
curl -I https://localhost:5601/plugins/securitySolution/securitySolution.chunk.1.js
Integration with Other Skills
With @buildkite-ci-debugger
@buildkite-ci-debugger --build <url>
@bundle-analyzer analyze bundle size for security-solution
@bundle-analyzer compare bundle sizes between main and my-branch
With @migration-planner
@migration-planner create migration for lodash to lodash-es
@bundle-analyzer measure bundle size impact of lodash-es migration
With @dependency-updater
@dependency-updater review renovate PR
@bundle-analyzer analyze bundle size for updated dependency
Examples
Example 1: Analyze Security Solution Bundle
User: "analyze bundle size for security-solution plugin"
Agent Actions:
node scripts/build_kibana_platform_plugins.js \
--focus @kbn/security-solution-plugin \
--dist \
--profile
open build/plugins/security_solution/stats.html
cat > security-solution-bundle-report.md <<EOF
# Security Solution Bundle Analysis
## Summary
- **Total Size (parsed):** 15.2 MB
- **Total Size (gzipped):** 4.8 MB
- **Chunk Count:** 25
## Optimization Opportunities
1. **Monaco Editor (2.3 MB):** Lazy load in rule editor
2. **Lodash (1.8 MB):** Replace with lodash-es
3. **D3 (650 KB):** Import specific modules only
## Estimated Savings
- Monaco lazy load: -2.3 MB initial load
- Lodash-es: -1.4 MB (tree-shaking)
- D3 modules: -390 KB
- **Total:** -4.09 MB (~27% reduction)
EOF
Output: Detailed bundle report with optimization plan.
Example 2: Suggest Code Splitting
User: "suggest code splitting for security-solution rule editor"
Agent Actions:
import { MonacoEditor } from 'monaco-editor';
export function RuleEditor() {
return <MonacoEditor />;
}
import { lazy, Suspense } from 'react';
const MonacoEditor = lazy(() =>
import('monaco-editor').then(m => ({ default: m.MonacoEditor }))
);
export function RuleEditor() {
return (
<Suspense fallback={<EuiLoadingSpinner />}>
<MonacoEditor />
</Suspense>
);
}
cat > code-splitting-plan.md <<EOF
# Code Splitting: Monaco Editor
## Change
Lazy load Monaco Editor in rule editor routes.
## Impact
- **Initial bundle:** -2.3 MB
- **Rule editor load:** +2.3 MB (on-demand)
- **Net benefit:** Faster initial page load
## Implementation
1. Replace direct import with lazy()
2. Add Suspense with loading spinner
3. Test rule editor loads Monaco correctly
4. Verify bundle size reduction
## Testing
- Navigate to /rules (Monaco should NOT load)
- Navigate to /rules/create (Monaco should load)
- Check Network tab (monaco chunk loaded on-demand)
EOF
Output: Code splitting implementation with size impact.
Example 3: Compare Bundle Sizes
User: "compare bundle sizes between main and my-branch"
Agent Actions:
git checkout main
yarn kbn bootstrap
node scripts/build_kibana_platform_plugins.js --focus @kbn/security-solution-plugin --dist
MAIN_SIZE=$(stat -f%z build/plugins/security_solution/target/public/security_solution.chunk.1.js)
git checkout my-branch
yarn kbn bootstrap
node scripts/build_kibana_platform_plugins.js --focus @kbn/security-solution-plugin --dist
FEATURE_SIZE=$(stat -f%z build/plugins/security_solution/target/public/security_solution.chunk.1.js)
DIFF=$((FEATURE_SIZE - MAIN_SIZE))
PERCENT=$(awk "BEGIN {printf \"%.2f\", ($DIFF / $MAIN_SIZE) * 100}")
cat > bundle-comparison.md <<EOF
# Bundle Size Comparison
| Branch | Size | Diff | % Change |
|--------|------|------|----------|
| main | 4.8 MB | - | - |
| my-branch | 4.5 MB | -300 KB | -6.25% |
## Impact
✅ Bundle size decreased by 300 KB (6.25%)
## Changes
- Replaced lodash with lodash-es
- Removed unused d3 modules
- Lazy loaded Monaco editor
## Recommendation
Proceed with merge. Significant bundle size improvement.
EOF
Output: Side-by-side bundle comparison.
Example 4: Identify Duplicate Dependencies
User: "find large dependencies in security-solution"
Agent Actions:
node scripts/build_kibana_platform_plugins.js \
--focus @kbn/security-solution-plugin \
--dist \
--json > bundle-stats.json
jq '.modules[] | {name: .name, size: .size}' bundle-stats.json \
| jq -s 'sort_by(.size) | reverse | .[:10]'
yarn why react
cat > dependency-report.md <<EOF
# Dependency Analysis: Security Solution
## Top 10 Largest Dependencies
1. monaco-editor: 2.3 MB
2. lodash: 1.8 MB
3. @elastic/eui: 1.5 MB
4. react: 800 KB
5. d3: 650 KB
## Duplicate Dependencies
- **react:** v18.2.0 (from @elastic/eui) and v18.3.0 (from plugin)
- **Fix:** Add yarn resolution for react@^18.3.0
- **Savings:** ~200 KB
## Optimization Recommendations
1. Deduplicate react versions
2. Replace lodash with lodash-es
3. Lazy load monaco-editor
EOF
Output: Dependency report with duplicates identified.
Best Practices
Bundle Analysis
- ✅ Run analyzer after major dependency updates
- ✅ Check gzipped sizes (network transfer)
- ✅ Focus on largest modules first (biggest impact)
- ✅ Track bundle size over time (CI checks)
- ❌ Don't ignore warnings (they add up)
- ❌ Don't optimize prematurely (measure first)
Code Splitting
- ✅ Split by route (user may not visit all pages)
- ✅ Lazy load heavy components (Monaco, charts)
- ✅ Use Suspense with loading UI (UX)
- ❌ Don't over-split (too many chunks = slow)
- ❌ Don't split critical path (initial render)
Tree-Shaking
- ✅ Import specific functions (not default exports)
- ✅ Use ES modules (not CommonJS)
- ✅ Enable sideEffects: false in package.json
- ❌ Don't import entire libraries (import * from)
- ❌ Don't use default imports for utilities
Size Budgets
- ✅ Set realistic budgets (based on baseline)
- ✅ Fail CI on budget violations (enforce)
- ✅ Track size trends (prevent regressions)
- ❌ Don't ignore budget warnings
- ❌ Don't set arbitrary budgets (measure first)
Anti-Patterns
❌ Don't Do This
import _ from 'lodash';
import * as d3 from 'd3';
import { MonacoEditor } from 'monaco-editor';
export function App() {
return <MonacoEditor />;
}
{
"dependencies": {
"react": "^18.2.0",
"@elastic/eui": "^92.0.0"
}
}
✅ Do This Instead
import debounce from 'lodash-es/debounce';
import { scaleLinear } from 'd3-scale';
const MonacoEditor = lazy(() => import('monaco-editor'));
export function App() {
return <Suspense fallback={<Spinner />}><MonacoEditor /></Suspense>;
}
{
"dependencies": {
"react": "^18.3.0",
"@elastic/eui": "^92.0.0"
},
"resolutions": {
"react": "^18.3.0"
}
}
Tools
webpack-bundle-analyzer
yarn add --dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false,
}),
],
};
size-limit
yarn add --dev size-limit @size-limit/preset-app
{
"size-limit": [
{
"path": "build/plugins/security_solution/target/public/*.js",
"limit": "5 MB"
}
]
}
npx size-limit
bundlesize
yarn add --dev bundlesize
{
"bundlesize": [
{
"path": "./build/plugins/security_solution/target/public/*.js",
"maxSize": "5 MB"
}
]
}
npx bundlesize
Notes
- Focus on gzipped sizes (what users download)
- Lazy loading trades initial load for on-demand load
- Tree-shaking requires ES modules (not CommonJS)
- Bundle analyzer runs automatically with --profile flag
- Kibana uses content-hash filenames for cache busting