@tenphi/tasty 0.5.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -16
- package/dist/config.d.ts +8 -0
- package/dist/config.js.map +1 -1
- package/dist/hooks/useStyles.js +5 -5
- package/dist/hooks/useStyles.js.map +1 -1
- package/dist/injector/injector.js +38 -25
- package/dist/injector/injector.js.map +1 -1
- package/dist/injector/sheet-manager.d.ts +12 -4
- package/dist/injector/sheet-manager.js +23 -9
- package/dist/injector/sheet-manager.js.map +1 -1
- package/dist/injector/types.d.ts +9 -0
- package/dist/properties/index.js +77 -17
- package/dist/properties/index.js.map +1 -1
- package/dist/properties/property-type-resolver.d.ts +24 -0
- package/dist/properties/property-type-resolver.js +83 -0
- package/dist/properties/property-type-resolver.js.map +1 -0
- package/dist/styles/fill.js +6 -5
- package/dist/styles/fill.js.map +1 -1
- package/dist/tasty.js +16 -1
- package/dist/tasty.js.map +1 -1
- package/dist/utils/styles.js +161 -0
- package/dist/utils/styles.js.map +1 -1
- package/dist/zero/babel.d.ts +5 -0
- package/dist/zero/babel.js +13 -7
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/extractor.js +66 -1
- package/dist/zero/extractor.js.map +1 -1
- package/docs/configuration.md +211 -0
- package/docs/debug.md +505 -0
- package/docs/injector.md +528 -0
- package/docs/styles.md +567 -0
- package/docs/tasty-static.md +376 -0
- package/docs/usage.md +643 -0
- package/package.json +5 -4
package/docs/injector.md
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
# Tasty Style Injector
|
|
2
|
+
|
|
3
|
+
A high-performance CSS-in-JS solution that powers the Tasty design system with efficient style injection, automatic cleanup, and first-class SSR support.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The Style Injector is the core engine behind Tasty's styling system, providing:
|
|
10
|
+
|
|
11
|
+
- **Hash-based deduplication** - Identical CSS gets the same className
|
|
12
|
+
- **Reference counting** - Automatic cleanup when components unmount (refCount = 0)
|
|
13
|
+
- **CSS nesting flattening** - Handles `&`, `.Class`, `SubElement` patterns
|
|
14
|
+
- **Keyframes injection** - First-class `@keyframes` support with immediate disposal
|
|
15
|
+
- **Smart cleanup** - CSS rules batched cleanup, keyframes disposed immediately
|
|
16
|
+
- **SSR support** - Deterministic class names and CSS extraction
|
|
17
|
+
- **Multiple roots** - Works with Document and ShadowRoot
|
|
18
|
+
- **Non-stacking cleanups** - Prevents timeout accumulation for better performance
|
|
19
|
+
|
|
20
|
+
> **Note:** This is internal infrastructure that powers Tasty components. Most developers will interact with the higher-level `tasty()` API instead.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
28
|
+
│ tasty() │────│ Style Injector │────│ Sheet Manager │
|
|
29
|
+
│ components │ │ │ │ │
|
|
30
|
+
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
31
|
+
│ │ │
|
|
32
|
+
│ │ │
|
|
33
|
+
▼ ▼ ▼
|
|
34
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
35
|
+
│ Style Results │ │ Keyframes Manager│ │ Root Registry │
|
|
36
|
+
│ (CSS rules) │ │ │ │ │
|
|
37
|
+
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
38
|
+
│ │
|
|
39
|
+
│ │
|
|
40
|
+
▼ ▼
|
|
41
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
42
|
+
│ Hash Cache │ │ <style> elements│
|
|
43
|
+
│ Deduplication │ │ CSSStyleSheet │
|
|
44
|
+
└─────────────────┘ └─────────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Core API
|
|
50
|
+
|
|
51
|
+
### `inject(rules, options?): InjectResult`
|
|
52
|
+
|
|
53
|
+
Injects CSS rules and returns a className with dispose function.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { inject } from '@tenphi/tasty';
|
|
57
|
+
|
|
58
|
+
// Component styling - generates tasty class names
|
|
59
|
+
const result = inject([{
|
|
60
|
+
selector: '.t-abc123',
|
|
61
|
+
declarations: 'color: red; padding: 10px;',
|
|
62
|
+
}]);
|
|
63
|
+
|
|
64
|
+
console.log(result.className); // 't-abc123'
|
|
65
|
+
|
|
66
|
+
// Cleanup when component unmounts (refCount decremented)
|
|
67
|
+
result.dispose();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `injectGlobal(rules, options?): { dispose: () => void }`
|
|
71
|
+
|
|
72
|
+
Injects global styles that don't reserve tasty class names.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// Global styles - for body, resets, etc.
|
|
76
|
+
const globalResult = injectGlobal([
|
|
77
|
+
{
|
|
78
|
+
selector: 'body',
|
|
79
|
+
declarations: 'margin: 0; font-family: Arial;',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
selector: '.header',
|
|
83
|
+
declarations: 'background: blue; color: white;',
|
|
84
|
+
atRules: ['@media (min-width: 768px)'],
|
|
85
|
+
}
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// Only returns dispose function - no className needed for global styles
|
|
89
|
+
globalResult.dispose();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `injectRawCSS(css, options?): { dispose: () => void }`
|
|
93
|
+
|
|
94
|
+
Injects raw CSS text directly without parsing. This is a low-overhead method for injecting CSS that doesn't need tasty processing.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { injectRawCSS } from '@tenphi/tasty';
|
|
98
|
+
|
|
99
|
+
// Inject raw CSS
|
|
100
|
+
const { dispose } = injectRawCSS(`
|
|
101
|
+
body {
|
|
102
|
+
margin: 0;
|
|
103
|
+
padding: 0;
|
|
104
|
+
font-family: sans-serif;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.my-class {
|
|
108
|
+
color: red;
|
|
109
|
+
}
|
|
110
|
+
`);
|
|
111
|
+
|
|
112
|
+
// Later, remove the injected CSS
|
|
113
|
+
dispose();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### `useRawCSS(css, options?)` or `useRawCSS(factory, deps, options?)`
|
|
117
|
+
|
|
118
|
+
React hook for injecting raw CSS. Uses `useInsertionEffect` for proper timing and cleanup.
|
|
119
|
+
|
|
120
|
+
Supports two overloads:
|
|
121
|
+
- **Static CSS**: `useRawCSS(cssString, options?)`
|
|
122
|
+
- **Factory function**: `useRawCSS(() => cssString, deps, options?)` - re-evaluates when deps change (like `useMemo`)
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { useRawCSS } from '@tenphi/tasty';
|
|
126
|
+
|
|
127
|
+
// Static CSS
|
|
128
|
+
function GlobalReset() {
|
|
129
|
+
useRawCSS(`
|
|
130
|
+
body { margin: 0; padding: 0; }
|
|
131
|
+
`);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Dynamic CSS with factory function (like useMemo)
|
|
136
|
+
function ThemeStyles({ theme }: { theme: 'dark' | 'light' }) {
|
|
137
|
+
useRawCSS(() => `
|
|
138
|
+
body {
|
|
139
|
+
margin: 0;
|
|
140
|
+
background: ${theme === 'dark' ? '#000' : '#fff'};
|
|
141
|
+
color: ${theme === 'dark' ? '#fff' : '#000'};
|
|
142
|
+
}
|
|
143
|
+
`, [theme]);
|
|
144
|
+
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `createInjector(config?): StyleInjector`
|
|
150
|
+
|
|
151
|
+
Creates an isolated injector instance with custom configuration.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { createInjector } from '@tenphi/tasty';
|
|
155
|
+
|
|
156
|
+
// Create isolated instance for testing
|
|
157
|
+
const testInjector = createInjector({
|
|
158
|
+
devMode: true,
|
|
159
|
+
forceTextInjection: true,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const result = testInjector.inject(rules);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `keyframes(steps, nameOrOptions?): KeyframesResult`
|
|
166
|
+
|
|
167
|
+
Injects CSS keyframes with automatic deduplication.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// Generated name (k0, k1, k2...)
|
|
171
|
+
const fadeIn = keyframes({
|
|
172
|
+
from: { opacity: 0 },
|
|
173
|
+
to: { opacity: 1 },
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Custom name
|
|
177
|
+
const slideIn = keyframes({
|
|
178
|
+
'0%': { transform: 'translateX(-100%)' },
|
|
179
|
+
'100%': { transform: 'translateX(0)' },
|
|
180
|
+
}, 'slideInAnimation');
|
|
181
|
+
|
|
182
|
+
// Use in tasty styles (recommended)
|
|
183
|
+
const AnimatedBox = tasty({
|
|
184
|
+
styles: {
|
|
185
|
+
animation: `${fadeIn} 300ms ease-in`,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Or use with injectGlobal for fixed selectors
|
|
190
|
+
injectGlobal([{
|
|
191
|
+
selector: '.my-animated-class',
|
|
192
|
+
declarations: `animation: ${slideIn} 500ms ease-out;`
|
|
193
|
+
}]);
|
|
194
|
+
|
|
195
|
+
// Cleanup keyframes (if needed)
|
|
196
|
+
fadeIn.dispose(); // Immediate keyframes deletion from DOM
|
|
197
|
+
slideIn.dispose(); // Immediate keyframes deletion from DOM
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### `configure(config): void`
|
|
201
|
+
|
|
202
|
+
Configures the Tasty style system. Must be called **before** any styles are generated (before first render).
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { configure } from '@tenphi/tasty';
|
|
206
|
+
|
|
207
|
+
configure({
|
|
208
|
+
devMode: true, // Enable development features (auto-detected)
|
|
209
|
+
maxRulesPerSheet: 8192, // Cap rules per stylesheet (default: 8192)
|
|
210
|
+
unusedStylesThreshold: 500, // Trigger cleanup threshold (CSS rules only)
|
|
211
|
+
bulkCleanupDelay: 5000, // Cleanup delay (ms) - ignored if idleCleanup is true
|
|
212
|
+
idleCleanup: true, // Use requestIdleCallback for cleanup
|
|
213
|
+
bulkCleanupBatchRatio: 0.5, // Clean up oldest 50% per batch
|
|
214
|
+
unusedStylesMinAgeMs: 10000, // Minimum age before cleanup (ms)
|
|
215
|
+
forceTextInjection: false, // Force textContent insertion (auto-detected for tests)
|
|
216
|
+
nonce: 'csp-nonce', // CSP nonce for security
|
|
217
|
+
states: { // Global predefined states for advanced state mapping
|
|
218
|
+
'@mobile': '@media(w < 768px)',
|
|
219
|
+
'@dark': '@root(theme=dark)',
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Auto-Detection Features:**
|
|
225
|
+
- `devMode`: Automatically enabled in development environments (detected via `isDevEnv()`)
|
|
226
|
+
- `forceTextInjection`: Automatically enabled in test environments (Jest, Vitest, Mocha, jsdom)
|
|
227
|
+
|
|
228
|
+
**Configuration Notes:**
|
|
229
|
+
- Most options have sensible defaults and auto-detection
|
|
230
|
+
- `configure()` is optional - the injector works with defaults
|
|
231
|
+
- **Configuration is locked after styles are generated** - calling `configure()` after first render will emit a warning and be ignored
|
|
232
|
+
- `unusedStylesMinAgeMs`: Minimum time (ms) a style must remain unused before being eligible for cleanup. Helps prevent removal of styles that might be quickly reactivated.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Advanced Features
|
|
237
|
+
|
|
238
|
+
### Style Result Format
|
|
239
|
+
|
|
240
|
+
The injector works with `StyleResult` objects from the tasty parser:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
interface StyleResult {
|
|
244
|
+
selector: string; // CSS selector
|
|
245
|
+
declarations: string; // CSS declarations
|
|
246
|
+
atRules?: string[]; // @media, @supports, etc.
|
|
247
|
+
nestingLevel?: number; // Nesting depth for specificity
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Example StyleResult
|
|
251
|
+
const styleRule: StyleResult = {
|
|
252
|
+
selector: '.t-button',
|
|
253
|
+
declarations: 'padding: 8px 16px; background: blue; color: white;',
|
|
254
|
+
atRules: ['@media (min-width: 768px)'],
|
|
255
|
+
nestingLevel: 0,
|
|
256
|
+
};
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Deduplication & Performance
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// Identical CSS rules get the same className
|
|
263
|
+
const button1 = inject([{
|
|
264
|
+
selector: '.t-btn1',
|
|
265
|
+
declarations: 'padding: 8px; color: red;'
|
|
266
|
+
}]);
|
|
267
|
+
|
|
268
|
+
const button2 = inject([{
|
|
269
|
+
selector: '.t-btn2',
|
|
270
|
+
declarations: 'padding: 8px; color: red;' // Same declarations
|
|
271
|
+
}]);
|
|
272
|
+
|
|
273
|
+
// Both get the same className due to deduplication
|
|
274
|
+
console.log(button1.className === button2.className); // true
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Reference Counting
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// Multiple components using the same styles
|
|
281
|
+
const comp1 = inject([commonStyle]);
|
|
282
|
+
const comp2 = inject([commonStyle]);
|
|
283
|
+
const comp3 = inject([commonStyle]);
|
|
284
|
+
|
|
285
|
+
// Style is kept alive while any component uses it
|
|
286
|
+
comp1.dispose(); // refCount: 3 → 2
|
|
287
|
+
comp2.dispose(); // refCount: 2 → 1
|
|
288
|
+
comp3.dispose(); // refCount: 1 → 0, eligible for bulk cleanup
|
|
289
|
+
|
|
290
|
+
// Rule exists but refCount = 0 means unused
|
|
291
|
+
// Next inject() with same styles will increment refCount and reuse immediately
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Smart Cleanup System
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { configure } from '@tenphi/tasty';
|
|
298
|
+
|
|
299
|
+
// CSS rules: Not immediately deleted, marked for bulk cleanup (refCount = 0)
|
|
300
|
+
// Keyframes: Disposed immediately when refCount = 0 (safer for global scope)
|
|
301
|
+
|
|
302
|
+
configure({
|
|
303
|
+
unusedStylesThreshold: 100, // Cleanup when 100+ unused CSS rules
|
|
304
|
+
bulkCleanupBatchRatio: 0.3, // Remove oldest 30% each time
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Benefits:
|
|
308
|
+
// - CSS rules: Batch cleanup prevents DOM manipulation overhead
|
|
309
|
+
// - Keyframes: Immediate cleanup prevents global namespace pollution
|
|
310
|
+
// - Unused styles can be instantly reactivated (just increment refCount)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Shadow DOM Support
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Works with Shadow DOM
|
|
317
|
+
const shadowRoot = document.createElement('div').attachShadow({ mode: 'open' });
|
|
318
|
+
|
|
319
|
+
const shadowStyles = inject([{
|
|
320
|
+
selector: '.shadow-component',
|
|
321
|
+
declarations: 'color: purple;'
|
|
322
|
+
}], { root: shadowRoot });
|
|
323
|
+
|
|
324
|
+
// Keyframes in Shadow DOM
|
|
325
|
+
const shadowAnimation = keyframes({
|
|
326
|
+
from: { opacity: 0 },
|
|
327
|
+
to: { opacity: 1 }
|
|
328
|
+
}, { root: shadowRoot, name: 'shadowFade' });
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## SSR & Testing
|
|
334
|
+
|
|
335
|
+
### Server-Side Rendering
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { getCssText, getCssTextForNode } from '@tenphi/tasty';
|
|
339
|
+
|
|
340
|
+
// Extract all CSS for SSR
|
|
341
|
+
const cssText = getCssText();
|
|
342
|
+
|
|
343
|
+
// Extract CSS for specific DOM subtree (like jest-styled-components)
|
|
344
|
+
const container = render(<MyComponent />);
|
|
345
|
+
const componentCSS = getCssTextForNode(container);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Test Environment Detection
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// Automatically detected test environments:
|
|
352
|
+
// - NODE_ENV === 'test'
|
|
353
|
+
// - Jest globals (jest, describe, it, expect)
|
|
354
|
+
// - jsdom user agent
|
|
355
|
+
// - Vitest globals (vitest)
|
|
356
|
+
// - Mocha globals (mocha)
|
|
357
|
+
|
|
358
|
+
import { configure, isTestEnvironment, resetConfig } from '@tenphi/tasty';
|
|
359
|
+
|
|
360
|
+
const isTest = isTestEnvironment();
|
|
361
|
+
|
|
362
|
+
// Reset config between tests to allow reconfiguration
|
|
363
|
+
beforeEach(() => {
|
|
364
|
+
resetConfig();
|
|
365
|
+
configure({
|
|
366
|
+
forceTextInjection: isTest, // More reliable in test environments
|
|
367
|
+
devMode: true, // Always enable dev features in tests
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Memory Management in Tests
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// Clean up between tests
|
|
376
|
+
afterEach(() => {
|
|
377
|
+
cleanup(); // Force cleanup of unused styles
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Full cleanup after test suite
|
|
381
|
+
afterAll(() => {
|
|
382
|
+
destroy(); // Destroy all stylesheets and reset state
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Development Features
|
|
389
|
+
|
|
390
|
+
### Performance Metrics
|
|
391
|
+
|
|
392
|
+
When `devMode` is enabled, the injector tracks comprehensive metrics:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { configure, injector } from '@tenphi/tasty';
|
|
396
|
+
|
|
397
|
+
configure({ devMode: true });
|
|
398
|
+
|
|
399
|
+
// Access metrics through the global injector
|
|
400
|
+
const metrics = injector.instance.getMetrics();
|
|
401
|
+
|
|
402
|
+
console.log({
|
|
403
|
+
cacheHits: metrics.hits, // Successful cache hits
|
|
404
|
+
cacheMisses: metrics.misses, // New styles injected
|
|
405
|
+
unusedHits: metrics.unusedHits, // Current unused styles (calculated on demand)
|
|
406
|
+
bulkCleanups: metrics.bulkCleanups, // Number of bulk cleanup operations
|
|
407
|
+
stylesCleanedUp: metrics.stylesCleanedUp, // Total styles removed in bulk cleanups
|
|
408
|
+
totalInsertions: metrics.totalInsertions, // Lifetime insertions
|
|
409
|
+
totalUnused: metrics.totalUnused, // Total styles marked as unused (refCount = 0)
|
|
410
|
+
startTime: metrics.startTime, // Metrics collection start timestamp
|
|
411
|
+
cleanupHistory: metrics.cleanupHistory, // Detailed cleanup operation history
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Debug Information
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
// Get detailed information about injected styles
|
|
419
|
+
const debugInfo = injector.instance.getDebugInfo();
|
|
420
|
+
|
|
421
|
+
console.log({
|
|
422
|
+
activeStyles: debugInfo.activeStyles, // Currently active styles
|
|
423
|
+
unusedStyles: debugInfo.unusedStyles, // Styles marked for cleanup
|
|
424
|
+
totalSheets: debugInfo.totalSheets, // Number of stylesheets
|
|
425
|
+
totalRules: debugInfo.totalRules, // Total CSS rules
|
|
426
|
+
});
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Cleanup History
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// Track cleanup operations over time
|
|
433
|
+
const metrics = injector.instance.getMetrics();
|
|
434
|
+
|
|
435
|
+
metrics.cleanupHistory.forEach(cleanup => {
|
|
436
|
+
console.log({
|
|
437
|
+
timestamp: new Date(cleanup.timestamp),
|
|
438
|
+
classesDeleted: cleanup.classesDeleted,
|
|
439
|
+
rulesDeleted: cleanup.rulesDeleted,
|
|
440
|
+
cssSize: cleanup.cssSize, // Total CSS size removed (bytes)
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Performance Optimizations
|
|
448
|
+
|
|
449
|
+
### Best Practices
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
// ✅ Reuse styles - identical CSS gets deduplicated
|
|
453
|
+
const buttonBase = { padding: '8px 16px', borderRadius: '4px' };
|
|
454
|
+
|
|
455
|
+
// ✅ Avoid frequent disposal and re-injection
|
|
456
|
+
// Let the reference counting system handle cleanup
|
|
457
|
+
|
|
458
|
+
// ✅ Use bulk operations for global styles
|
|
459
|
+
injectGlobal([
|
|
460
|
+
{ selector: 'body', declarations: 'margin: 0;' },
|
|
461
|
+
{ selector: '*', declarations: 'box-sizing: border-box;' },
|
|
462
|
+
{ selector: '.container', declarations: 'max-width: 1200px;' }
|
|
463
|
+
]);
|
|
464
|
+
|
|
465
|
+
// ✅ Configure appropriate thresholds for your app (BEFORE first render!)
|
|
466
|
+
import { configure } from '@tenphi/tasty';
|
|
467
|
+
|
|
468
|
+
configure({
|
|
469
|
+
unusedStylesThreshold: 500, // Default threshold (adjust based on app size)
|
|
470
|
+
bulkCleanupBatchRatio: 0.5, // Default: clean oldest 50% per batch
|
|
471
|
+
unusedStylesMinAgeMs: 10000, // Wait 10s before cleanup eligibility
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Memory Management
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
// The injector automatically manages memory through:
|
|
479
|
+
|
|
480
|
+
// 1. Hash-based deduplication - same CSS = same className
|
|
481
|
+
// 2. Reference counting - styles stay alive while in use (refCount > 0)
|
|
482
|
+
// 3. Immediate keyframes cleanup - disposed instantly when refCount = 0
|
|
483
|
+
// 4. Batch CSS cleanup - unused CSS rules (refCount = 0) cleaned in batches
|
|
484
|
+
// 5. Non-stacking cleanups - prevents timeout accumulation
|
|
485
|
+
|
|
486
|
+
// Manual cleanup is rarely needed but available:
|
|
487
|
+
cleanup(); // Force immediate cleanup of all unused CSS rules (refCount = 0)
|
|
488
|
+
destroy(); // Nuclear option: remove all stylesheets and reset
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Integration with Tasty
|
|
494
|
+
|
|
495
|
+
The Style Injector is seamlessly integrated with the higher-level Tasty API:
|
|
496
|
+
|
|
497
|
+
```jsx
|
|
498
|
+
// High-level tasty() API
|
|
499
|
+
const StyledButton = tasty({
|
|
500
|
+
styles: {
|
|
501
|
+
padding: '2x 4x',
|
|
502
|
+
fill: '#purple',
|
|
503
|
+
color: '#white',
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Internally uses the injector:
|
|
508
|
+
// 1. Styles are parsed into StyleResult objects
|
|
509
|
+
// 2. inject() is called with the parsed results
|
|
510
|
+
// 3. Component gets the returned className
|
|
511
|
+
// 4. dispose() is called when component unmounts
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
For most development, you'll use the [Tasty style system](./usage.md) rather than the injector directly. The injector provides the high-performance foundation that makes Tasty's declarative styling possible.
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## When to Use Direct Injection
|
|
519
|
+
|
|
520
|
+
Direct injector usage is recommended for:
|
|
521
|
+
|
|
522
|
+
- **Custom CSS-in-JS libraries** built on top of Tasty
|
|
523
|
+
- **Global styles** that don't fit the component model
|
|
524
|
+
- **Third-party integration** where you need low-level CSS control
|
|
525
|
+
- **Performance-critical scenarios** where you need direct control
|
|
526
|
+
- **Testing utilities** that need to inject or extract CSS
|
|
527
|
+
|
|
528
|
+
For regular component styling, prefer the [`tasty()` API](./usage.md) which provides a more developer-friendly interface.
|