Design Token Tree Shaking
Design Token Tree Shaking
Design token tree shaking removes unused tokens from application bundles, delivering only the tokens actually referenced in code. Like tree shaking for JavaScript modules, token tree shaking analyzes usage and eliminates dead code. This optimization significantly reduces bundle sizes for applications using large token systems.
What Is Design Token Tree Shaking
Design token tree shaking is an optimization technique that analyzes application code to identify which tokens are used, then generates output containing only those tokens. Unused tokens never reach the final bundle, reducing file size without manual pruning.
Tree shaking automates what would otherwise require tedious manual tracking of token usage.
How Design Token Tree Shaking Works
Usage analysis:
function analyzeTokenUsage(codebase, allTokens) {
const usedTokens = new Set();
codebase.files.forEach(file => {
const content = fs.readFileSync(file, 'utf8');
allTokens.forEach(token => {
const patterns = [
`var(--${token.cssName})`,
`tokens.${token.jsPath}`,
`"${token.name}"`
];
if (patterns.some(p => content.includes(p))) {
usedTokens.add(token.name);
// Include referenced tokens
if (token.references) {
token.references.forEach(ref => usedTokens.add(ref));
}
}
});
});
return usedTokens;
}
Filtered output generation:
function generateTreeShakenOutput(allTokens, usedTokens) {
const filteredTokens = allTokens.filter(token =>
usedTokens.has(token.name)
);
return generateCSS(filteredTokens);
}
// Before: 450 tokens → 45KB
// After: 123 tokens → 12KB
Build integration:
// webpack.config.js
module.exports = {
plugins: [
new TokenTreeShakePlugin({
tokensPath: './tokens',
outputPath: './dist/tokens.css',
analyze: ['./src/**/*.{js,jsx,ts,tsx,css,scss}']
})
]
};
Key Considerations
- Analysis must catch all usage patterns
- Dynamic usage prevents effective tree shaking
- Reference chains must be preserved
- Development builds may skip tree shaking
- Source maps aid debugging tree-shaken bundles
- Tree shaking adds build complexity
- Verify correctness after tree shaking
- Consider granularity of tree shaking
Common Questions
How should reference chains be handled?
When a used token references other tokens, those references must also be included.
Reference resolution:
function resolveAllReferences(token, allTokens, included) {
included.add(token.name);
if (token.value && token.value.startsWith('{')) {
const refName = token.value.slice(1, -1);
const refToken = allTokens.find(t => t.name === refName);
if (refToken && !included.has(refName)) {
resolveAllReferences(refToken, allTokens, included);
}
}
return included;
}
// Usage
const usedTokens = new Set();
directlyUsedTokens.forEach(token => {
resolveAllReferences(token, allTokens, usedTokens);
});
Example chain:
// Used in code: color.action.primary
// Must include entire chain:
{
"color.action.primary": "{color.brand.primary}",
"color.brand.primary": "{color.primitive.blue.500}",
"color.primitive.blue.500": "#3B82F6"
}
How can dynamic usage be addressed?
Dynamic token access patterns defeat static analysis.
Problematic patterns:
// Dynamic property access - cannot tree shake
const tokenName = `color.${variant}`;
const value = tokens[tokenName];
// Computed CSS variable - cannot analyze
const cssVar = `var(--color-${state})`;
Mitigation strategies:
Include all tokens matching pattern:
function handleDynamicPattern(pattern, allTokens) {
const regex = new RegExp(pattern.replace('${variant}', '.*'));
return allTokens.filter(t => regex.test(t.name));
}
// Include all color.* tokens if dynamic color access detected
Whitelist for dynamic usage:
const whitelist = [
'color.status.*', // Dynamic status colors
'spacing.*' // Dynamic spacing
];
Refactor to static usage:
// Instead of dynamic access
const colors = {
primary: tokens.color.action.primary,
secondary: tokens.color.action.secondary,
destructive: tokens.color.action.destructive
};
const value = colors[variant];
What about CSS-only tree shaking?
CSS tokens present unique challenges since CSS is not module-based.
PurgeCSS integration:
// postcss.config.js
module.exports = {
plugins: [
require('@fullhuman/postcss-purgecss')({
content: ['./src/**/*.{js,jsx,html}'],
css: ['./tokens.css'],
safelist: {
// Keep tokens that might be dynamically used
greedy: [/^--color-status/]
}
})
]
};
Custom CSS tree shaking:
const postcss = require('postcss');
function treeShakeCSS(css, usedVariables) {
return postcss.parse(css).nodes.filter(node => {
if (node.type === 'decl' && node.prop.startsWith('--')) {
return usedVariables.has(node.prop);
}
return true;
});
}
Limitations:
- CSS variables in
:roothave global scope - Cannot determine if variable is used elsewhere
- May need whitelist for shared/dynamic usage
Summary
Design token tree shaking removes unused tokens from bundles by analyzing code for token references. Implementation requires tracking reference chains to include transitively needed tokens. Dynamic usage patterns complicate analysis and may require whitelisting or code refactoring. CSS-specific tree shaking integrates with tools like PurgeCSS but has limitations due to CSS’s global scope. Effective tree shaking can dramatically reduce token bundle sizes.
Buoy scans your codebase for design system inconsistencies before they ship
Detect Design Drift Free