@bookklik/senangstart-css 0.2.5 ā 0.2.7
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/.agent/skills/add-utility/scripts/scaffold-utility.js +209 -0
- package/.agent/skills/compiler-development/SKILL.md +272 -0
- package/.agent/skills/jit-engine/SKILL.md +241 -0
- package/.agent/skills/jit-engine/examples/add-visual-utility.js +117 -0
- package/.agent/skills/jit-engine/examples/scale-based-utilities.js +130 -0
- package/.agent/skills/jit-engine/examples/state-responsive-handling.js +177 -0
- package/.agent/skills/senangstart-architecture/SKILL.md +163 -0
- package/.agent/skills/tailwind-conversion/SKILL.md +264 -0
- package/.agent/workflows/add-utility.md +155 -0
- package/.agent/workflows/build.md +97 -0
- package/.agent/workflows/dev.md +58 -0
- package/.agent/workflows/docs.md +113 -0
- package/.agent/workflows/test.md +103 -0
- package/dist/senangstart-css.js +99 -56
- package/dist/senangstart-css.min.js +36 -27
- package/dist/senangstart-tw.js +71 -2
- package/dist/senangstart-tw.min.js +1 -1
- package/docs/.vitepress/config.js +10 -2
- package/docs/guide/getting-started.md +6 -0
- package/docs/ms/guide/getting-started.md +6 -0
- package/docs/ms/reference/space/height.md +42 -7
- package/docs/ms/reference/space/width.md +40 -5
- package/docs/ms/reference/visual/divide-reverse.md +66 -0
- package/docs/ms/reference/visual/divide-style.md +80 -0
- package/docs/ms/reference/visual/divide-width.md +89 -0
- package/docs/ms/reference/visual/divide.md +115 -0
- package/docs/public/assets/senangstart-css.min.js +36 -27
- package/docs/public/llms.txt +40 -1
- package/docs/reference/space/height.md +42 -7
- package/docs/reference/space/width.md +40 -5
- package/docs/reference/visual/divide-reverse.md +66 -0
- package/docs/reference/visual/divide-style.md +80 -0
- package/docs/reference/visual/divide-width.md +89 -0
- package/docs/reference/visual/divide.md +115 -0
- package/docs/reference/visual.md +8 -2
- package/package.json +1 -1
- package/scripts/convert-tailwind.js +105 -2
- package/scripts/generate-llms-txt.js +2 -1
- package/src/cdn/senangstart-engine.js +128 -40
- package/src/cdn/tw-conversion-engine.js +92 -3
- package/src/compiler/generators/css.js +107 -3
- package/src/definitions/index.js +3 -0
- package/src/definitions/space.js +53 -17
- package/src/definitions/visual-divide.js +225 -0
- package/tests/unit/compiler/generators/css.test.js +75 -3
- package/tests/unit/convert-tailwind.test.js +71 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scaffold New Utility
|
|
5
|
+
*
|
|
6
|
+
* Helper script to generate boilerplate for a new SenangStart utility.
|
|
7
|
+
* Creates definition, adds stub to JIT engine, and generates test file.
|
|
8
|
+
*
|
|
9
|
+
* Usage: node .agent/skills/add-utility/scripts/scaffold-utility.js <name> <category>
|
|
10
|
+
*
|
|
11
|
+
* Example: node .agent/skills/add-utility/scripts/scaffold-utility.js glow visual
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
|
|
18
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const rootDir = path.resolve(__dirname, '../../../../');
|
|
20
|
+
|
|
21
|
+
// Parse arguments
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const utilityName = args[0];
|
|
24
|
+
const category = args[1] || 'visual';
|
|
25
|
+
|
|
26
|
+
if (!utilityName) {
|
|
27
|
+
console.log(`
|
|
28
|
+
š¦ SenangStart Utility Scaffold
|
|
29
|
+
|
|
30
|
+
Usage: node scaffold-utility.js <name> [category]
|
|
31
|
+
|
|
32
|
+
Arguments:
|
|
33
|
+
name Utility name (e.g., 'glow', 'outline-offset')
|
|
34
|
+
category Category: layout | space | visual (default: visual)
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
node scaffold-utility.js glow visual
|
|
38
|
+
node scaffold-utility.js gap-x space
|
|
39
|
+
node scaffold-utility.js grid-flow layout
|
|
40
|
+
`);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`\nš§ Scaffolding utility: ${utilityName} (${category})\n`);
|
|
45
|
+
|
|
46
|
+
// ============================================
|
|
47
|
+
// 1. Generate Definition Template
|
|
48
|
+
// ============================================
|
|
49
|
+
|
|
50
|
+
const definitionTemplate = `/**
|
|
51
|
+
* ${utilityName} - SenangStart CSS Utility Definition
|
|
52
|
+
* Category: ${category}
|
|
53
|
+
*
|
|
54
|
+
* TODO: Update values, descriptions, and examples
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
export const ${toCamelCase(utilityName)}Definition = {
|
|
58
|
+
name: '${utilityName}',
|
|
59
|
+
property: 'TODO-css-property',
|
|
60
|
+
category: '${category}',
|
|
61
|
+
attribute: '${category}',
|
|
62
|
+
description: 'TODO: English description',
|
|
63
|
+
descriptionMs: 'TODO: Malay description',
|
|
64
|
+
syntax: '${utilityName}:<value>',
|
|
65
|
+
values: [
|
|
66
|
+
{ name: 'small', value: 'TODO', description: 'Small value' },
|
|
67
|
+
{ name: 'medium', value: 'TODO', description: 'Medium value' },
|
|
68
|
+
{ name: 'big', value: 'TODO', description: 'Big value' },
|
|
69
|
+
{ name: 'none', value: 'none', description: 'Disable' }
|
|
70
|
+
],
|
|
71
|
+
examples: [
|
|
72
|
+
{ code: '${category}="${utilityName}:small"', description: 'Basic usage' },
|
|
73
|
+
{ code: '${category}="hover:${utilityName}:medium"', description: 'On hover' }
|
|
74
|
+
],
|
|
75
|
+
notes: 'TODO: Additional notes for documentation'
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default { ${toCamelCase(utilityName)}Definition };
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
// ============================================
|
|
82
|
+
// 2. Generate Test Template
|
|
83
|
+
// ============================================
|
|
84
|
+
|
|
85
|
+
const testTemplate = `/**
|
|
86
|
+
* ${utilityName} Utility Tests
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
import { test, describe } from 'node:test';
|
|
90
|
+
import assert from 'node:assert';
|
|
91
|
+
// import { generateVisualRule } from '../../../../src/compiler/generators/css.js';
|
|
92
|
+
|
|
93
|
+
const mockConfig = {
|
|
94
|
+
theme: {
|
|
95
|
+
// TODO: Add required theme scales
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
describe('${utilityName} utility', () => {
|
|
100
|
+
test('generates correct CSS for small value', () => {
|
|
101
|
+
const token = {
|
|
102
|
+
property: '${utilityName}',
|
|
103
|
+
value: 'small',
|
|
104
|
+
attrType: '${category}'
|
|
105
|
+
};
|
|
106
|
+
// TODO: Uncomment and update when generator is implemented
|
|
107
|
+
// const result = generate${capitalize(category)}Rule(token, mockConfig);
|
|
108
|
+
// assert.strictEqual(result, 'TODO-expected-css');
|
|
109
|
+
assert.ok(true, 'TODO: Implement test');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('generates correct CSS for none value', () => {
|
|
113
|
+
const token = {
|
|
114
|
+
property: '${utilityName}',
|
|
115
|
+
value: 'none',
|
|
116
|
+
attrType: '${category}'
|
|
117
|
+
};
|
|
118
|
+
// TODO: Implement
|
|
119
|
+
assert.ok(true, 'TODO: Implement test');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('handles arbitrary values', () => {
|
|
123
|
+
const token = {
|
|
124
|
+
property: '${utilityName}',
|
|
125
|
+
value: '10px',
|
|
126
|
+
attrType: '${category}',
|
|
127
|
+
isArbitrary: true
|
|
128
|
+
};
|
|
129
|
+
// TODO: Implement
|
|
130
|
+
assert.ok(true, 'TODO: Implement test');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
// ============================================
|
|
136
|
+
// 3. Generate JIT Engine Stub
|
|
137
|
+
// ============================================
|
|
138
|
+
|
|
139
|
+
const jitStubTemplate = `
|
|
140
|
+
// ============================================
|
|
141
|
+
// ${utilityName.toUpperCase()} UTILITY
|
|
142
|
+
// Add this to generate${capitalize(category)}Rule() in src/cdn/senangstart-engine.js
|
|
143
|
+
// ============================================
|
|
144
|
+
|
|
145
|
+
/*
|
|
146
|
+
if (prop === '${utilityName}') {
|
|
147
|
+
if (value === 'none') {
|
|
148
|
+
return 'TODO-css-property: none';
|
|
149
|
+
}
|
|
150
|
+
// TODO: Add scale lookup
|
|
151
|
+
// const scaled = config.theme.TODO_SCALE?.[value];
|
|
152
|
+
// if (scaled) return \`TODO-css-property: \${scaled}\`;
|
|
153
|
+
|
|
154
|
+
// Arbitrary value support
|
|
155
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
156
|
+
return \`TODO-css-property: \${value.slice(1, -1)}\`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
*/
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
// ============================================
|
|
163
|
+
// Write Files
|
|
164
|
+
// ============================================
|
|
165
|
+
|
|
166
|
+
// Create output directory
|
|
167
|
+
const outputDir = path.join(rootDir, '.agent', 'skills', 'add-utility', 'generated');
|
|
168
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
169
|
+
|
|
170
|
+
// Write definition
|
|
171
|
+
const defPath = path.join(outputDir, `${utilityName}-definition.js`);
|
|
172
|
+
fs.writeFileSync(defPath, definitionTemplate);
|
|
173
|
+
console.log(`ā Created definition template: ${path.relative(rootDir, defPath)}`);
|
|
174
|
+
|
|
175
|
+
// Write test
|
|
176
|
+
const testPath = path.join(outputDir, `${utilityName}.test.js`);
|
|
177
|
+
fs.writeFileSync(testPath, testTemplate);
|
|
178
|
+
console.log(`ā Created test template: ${path.relative(rootDir, testPath)}`);
|
|
179
|
+
|
|
180
|
+
// Write JIT stub
|
|
181
|
+
const jitPath = path.join(outputDir, `${utilityName}-jit-stub.js`);
|
|
182
|
+
fs.writeFileSync(jitPath, jitStubTemplate);
|
|
183
|
+
console.log(`ā Created JIT stub: ${path.relative(rootDir, jitPath)}`);
|
|
184
|
+
|
|
185
|
+
console.log(`
|
|
186
|
+
š Next Steps:
|
|
187
|
+
|
|
188
|
+
1. Move definition to src/definitions/${category}.js or create new file
|
|
189
|
+
2. Export from src/definitions/index.js
|
|
190
|
+
3. Add handler to src/cdn/senangstart-engine.js (use stub as reference)
|
|
191
|
+
4. Add handler to src/compiler/generators/css.js (same logic)
|
|
192
|
+
5. Move test to tests/unit/compiler/generators/
|
|
193
|
+
6. Run: npm run docs:generate
|
|
194
|
+
7. Run: npm test
|
|
195
|
+
|
|
196
|
+
Generated files are in: .agent/skills/add-utility/generated/
|
|
197
|
+
`);
|
|
198
|
+
|
|
199
|
+
// ============================================
|
|
200
|
+
// Helper Functions
|
|
201
|
+
// ============================================
|
|
202
|
+
|
|
203
|
+
function toCamelCase(str) {
|
|
204
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function capitalize(str) {
|
|
208
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
209
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Compiler Development
|
|
3
|
+
description: Understanding and extending the CLI compiler with parser, tokenizer, and CSS generators
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Compiler Development
|
|
7
|
+
|
|
8
|
+
This skill covers the build-time compiler used by the CLI commands (`senangstart build` and `senangstart dev`).
|
|
9
|
+
|
|
10
|
+
## Compiler Architecture
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
src/compiler/
|
|
14
|
+
āāā index.js # Main compiler entry point
|
|
15
|
+
āāā parser.js # HTML/JSX attribute extraction
|
|
16
|
+
āāā tokenizer.js # Re-exports from core tokenizer
|
|
17
|
+
āāā generators/
|
|
18
|
+
āāā css.js # Main CSS generator (~2200 lines)
|
|
19
|
+
āāā preflight.js # Base reset styles
|
|
20
|
+
āāā typescript.js # TypeScript definitions
|
|
21
|
+
āāā ai-context.js # AI context generation
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Compilation Pipeline
|
|
25
|
+
|
|
26
|
+
```mermaid
|
|
27
|
+
flowchart LR
|
|
28
|
+
A[Source Files] --> B[Parser]
|
|
29
|
+
B --> C[Tokenizer]
|
|
30
|
+
C --> D[Generator]
|
|
31
|
+
D --> E[CSS Output]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
1. **Parser** extracts `layout`, `space`, `visual` attribute values from HTML/JSX
|
|
35
|
+
2. **Tokenizer** parses raw strings into structured token objects
|
|
36
|
+
3. **Generator** transforms tokens into CSS rules
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Parser (`src/compiler/parser.js`)
|
|
41
|
+
|
|
42
|
+
Extracts SenangStart attributes from source files using regex.
|
|
43
|
+
|
|
44
|
+
### Key Functions
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
// Parse single file content
|
|
48
|
+
parseSource(content) ā { layout: Set, space: Set, visual: Set }
|
|
49
|
+
|
|
50
|
+
// Parse multiple files
|
|
51
|
+
parseMultipleSources([{path, content}]) ā combined results
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Regex Patterns
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
const ATTRIBUTE_PATTERNS = {
|
|
58
|
+
layout: /layout\s*=\s*["']([^"']*)\["']/g,
|
|
59
|
+
space: /space\s*=\s*["']([^"']*)\["']/g,
|
|
60
|
+
visual: /visual\s*=\s*["']([^"']*)\["']/g
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Security Features
|
|
65
|
+
- Value length limits (10,000 chars max)
|
|
66
|
+
- Token length limits (500 chars max)
|
|
67
|
+
- Fresh regex per parse to prevent state issues
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Tokenizer (`src/core/tokenizer-core.js`)
|
|
72
|
+
|
|
73
|
+
Transforms raw attribute strings into structured token objects.
|
|
74
|
+
|
|
75
|
+
### Token Structure
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
{
|
|
79
|
+
raw: 'tab:hover:bg:blue-500', // Original string
|
|
80
|
+
breakpoint: 'tab', // null | mob | tab | lap | desk
|
|
81
|
+
state: 'hover', // null | hover | focus | active | ...
|
|
82
|
+
property: 'bg', // Utility name
|
|
83
|
+
value: 'blue-500', // Utility value
|
|
84
|
+
isArbitrary: false, // true if [bracketed]
|
|
85
|
+
attrType: 'visual' // layout | space | visual
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Key Functions
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// Tokenize single attribute string
|
|
93
|
+
tokenize(raw, attrType) ā token object
|
|
94
|
+
|
|
95
|
+
// Tokenize all parsed attributes
|
|
96
|
+
tokenizeAll(parsed) ā token array
|
|
97
|
+
|
|
98
|
+
// Parse token without validation (internal)
|
|
99
|
+
parseToken(raw) ā token object
|
|
100
|
+
|
|
101
|
+
// Validate token structure
|
|
102
|
+
isValidToken(token) ā boolean
|
|
103
|
+
|
|
104
|
+
// Sanitize value (remove semicolons)
|
|
105
|
+
sanitizeValue(value) ā string
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Parsing Logic
|
|
109
|
+
|
|
110
|
+
1. Split by `:` separator
|
|
111
|
+
2. Check first part for breakpoint prefix
|
|
112
|
+
3. Check next part for state prefix
|
|
113
|
+
4. Remaining parts are property and value
|
|
114
|
+
5. Detect arbitrary values with `[brackets]`
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## CSS Generator (`src/compiler/generators/css.js`)
|
|
119
|
+
|
|
120
|
+
The core generator transforms tokens into CSS rules.
|
|
121
|
+
|
|
122
|
+
### Main Functions
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
// Generate complete CSS from tokens
|
|
126
|
+
generateCSS(tokens, config) ā string
|
|
127
|
+
|
|
128
|
+
// Generate CSS variables from config
|
|
129
|
+
generateCSSVariables(config) ā string
|
|
130
|
+
|
|
131
|
+
// Generate single rule from token
|
|
132
|
+
generateRule(token, config) ā string
|
|
133
|
+
|
|
134
|
+
// Minify CSS output
|
|
135
|
+
minifyCSS(css) ā string
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Generator Functions by Category
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// Layout utilities (display, flex, grid, position)
|
|
142
|
+
generateLayoutRule(token, config) ā string
|
|
143
|
+
|
|
144
|
+
// Space utilities (padding, margin, gap, width, height)
|
|
145
|
+
generateSpaceRule(token, config) ā string
|
|
146
|
+
|
|
147
|
+
// Visual utilities (colors, borders, shadows, transforms, etc.)
|
|
148
|
+
generateVisualRule(token, config) ā string
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Adding a New Generator Pattern
|
|
152
|
+
|
|
153
|
+
In the appropriate function (e.g., `generateVisualRule`):
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
// 1. Simple keyword mapping
|
|
157
|
+
if (prop === 'my-utility') {
|
|
158
|
+
if (value === 'on') return 'my-property: value-on';
|
|
159
|
+
if (value === 'off') return 'my-property: value-off';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 2. Scale-based value
|
|
163
|
+
if (prop === 'my-scale-utility') {
|
|
164
|
+
const v = config.theme.myScale?.[value];
|
|
165
|
+
if (v) return `my-property: ${v}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 3. Arbitrary value support
|
|
169
|
+
if (prop === 'my-arbitrary-utility') {
|
|
170
|
+
if (token.isArbitrary) {
|
|
171
|
+
return `my-property: ${sanitizeArbitraryValue(value)}`;
|
|
172
|
+
}
|
|
173
|
+
return `my-property: ${config.theme.myScale?.[value] || value}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 4. Multi-property output
|
|
177
|
+
if (prop === 'inset') {
|
|
178
|
+
const v = config.theme.spacing[value] || value;
|
|
179
|
+
return `top: ${v}; right: ${v}; bottom: ${v}; left: ${v}`;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### CSS Selector Generation
|
|
184
|
+
|
|
185
|
+
Selectors use attribute selectors with proper escaping:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
// Simple: [visual~="bg:blue-500"]
|
|
189
|
+
// With state: [visual~="hover:bg:blue-500"]:hover
|
|
190
|
+
// With breakpoint: @media (min-width: 768px) { [layout~="tab:flex"] }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Preflight Generator (`src/compiler/generators/preflight.js`)
|
|
196
|
+
|
|
197
|
+
Generates base reset styles similar to Tailwind's preflight.
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
generatePreflight() ā string // Returns CSS reset
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Other Generators
|
|
206
|
+
|
|
207
|
+
### TypeScript (`typescript.js`)
|
|
208
|
+
Generates TypeScript declarations for IntelliSense.
|
|
209
|
+
|
|
210
|
+
### AI Context (`ai-context.js`)
|
|
211
|
+
Generates context files for AI assistants.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Testing Generators
|
|
216
|
+
|
|
217
|
+
Unit tests in `tests/unit/compiler/generators/`:
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
import { test, describe } from 'node:test';
|
|
221
|
+
import { generateVisualRule } from '../../../src/compiler/generators/css.js';
|
|
222
|
+
|
|
223
|
+
describe('my-utility', () => {
|
|
224
|
+
test('generates correct CSS', () => {
|
|
225
|
+
const token = { property: 'my-utility', value: 'on', attrType: 'visual' };
|
|
226
|
+
const result = generateVisualRule(token, config);
|
|
227
|
+
assert.strictEqual(result, 'my-property: value-on');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Run tests:
|
|
233
|
+
```bash
|
|
234
|
+
npm run test:unit
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Common Patterns
|
|
240
|
+
|
|
241
|
+
### State-Aware Generation
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
function generateRule(token, config) {
|
|
245
|
+
const css = generateVisualRule(token, config);
|
|
246
|
+
|
|
247
|
+
if (token.state) {
|
|
248
|
+
// Wrap with pseudo-selector
|
|
249
|
+
return `${selector}:${token.state} { ${css} }`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return `${selector} { ${css} }`;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Responsive Wrapping
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
if (token.breakpoint) {
|
|
260
|
+
const minWidth = config.theme.screens[token.breakpoint];
|
|
261
|
+
return `@media (min-width: ${minWidth}) { ${rule} }`;
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Dark Mode Handling
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
if (token.state === 'dark') {
|
|
269
|
+
const darkSelector = getDarkModeSelector(config);
|
|
270
|
+
return `${darkSelector} ${selector} { ${css} }`;
|
|
271
|
+
}
|
|
272
|
+
```
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: JIT Engine Development
|
|
3
|
+
description: Developing and extending the browser-based JIT CSS engine
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JIT Engine Development
|
|
7
|
+
|
|
8
|
+
This skill covers development of `src/cdn/senangstart-engine.js` - the browser-based Just-In-Time CSS compilation engine.
|
|
9
|
+
|
|
10
|
+
## Engine Overview
|
|
11
|
+
|
|
12
|
+
The JIT engine is an IIFE (~83KB unminified) that runs in the browser with zero configuration. It:
|
|
13
|
+
|
|
14
|
+
1. Observes DOM for elements with `layout`, `space`, or `visual` attributes
|
|
15
|
+
2. Parses attribute values into utility tokens
|
|
16
|
+
3. Generates CSS rules on-demand
|
|
17
|
+
4. Injects styles via a `<style>` element
|
|
18
|
+
5. Watches for DOM mutations to handle dynamic content
|
|
19
|
+
|
|
20
|
+
## File Structure
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
// src/cdn/senangstart-engine.js
|
|
24
|
+
|
|
25
|
+
// Imports from core modules
|
|
26
|
+
import { BREAKPOINTS, STATES, LAYOUT_KEYWORDS } from '../core/constants.js';
|
|
27
|
+
import { tokenize, parseToken } from '../core/tokenizer-core.js';
|
|
28
|
+
|
|
29
|
+
(function() {
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
// 1. Default Configuration
|
|
33
|
+
const defaultConfig = { theme: {...}, darkMode: 'media', preflight: true };
|
|
34
|
+
|
|
35
|
+
// 2. Config Loader
|
|
36
|
+
function validateConfig(config) {...}
|
|
37
|
+
function loadInlineConfig() {...}
|
|
38
|
+
function mergeConfig(user) {...}
|
|
39
|
+
|
|
40
|
+
// 3. CSS Variable Generator
|
|
41
|
+
function generateCSSVariables(config) {...}
|
|
42
|
+
|
|
43
|
+
// 4. Utility Generators
|
|
44
|
+
function generateLayoutRule(prop, value, config) {...}
|
|
45
|
+
function generateSpaceRule(prop, value, config) {...}
|
|
46
|
+
function generateVisualRule(prop, value, config) {...}
|
|
47
|
+
|
|
48
|
+
// 5. DOM Observer & Style Injection
|
|
49
|
+
function processElement(element) {...}
|
|
50
|
+
function observeDOM() {...}
|
|
51
|
+
|
|
52
|
+
// 6. Initialize
|
|
53
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
54
|
+
})();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Default Configuration
|
|
58
|
+
|
|
59
|
+
Located at the top of the engine, `defaultConfig` defines all theme scales:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const defaultConfig = {
|
|
63
|
+
theme: {
|
|
64
|
+
spacing: {
|
|
65
|
+
'none': '0px',
|
|
66
|
+
'thin': '1px',
|
|
67
|
+
// ... full scale
|
|
68
|
+
'vast-10x': '384px'
|
|
69
|
+
},
|
|
70
|
+
radius: { 'none': '0px', 'small': '4px', ... },
|
|
71
|
+
shadow: { 'none': 'none', 'small': '...', ... },
|
|
72
|
+
fontSize: { 'tiny': '0.75rem', ... },
|
|
73
|
+
fontWeight: { 'normal': '400', 'medium': '500', 'bold': '700' },
|
|
74
|
+
screens: { 'mob': '480px', 'tab': '768px', ... },
|
|
75
|
+
colors: { 'white': '#FFFFFF', 'primary': '#3B82F6', ... },
|
|
76
|
+
zIndex: { 'base': '0', 'low': '10', ... },
|
|
77
|
+
ring: { 'none': '0px', 'thin': '1px', ... }
|
|
78
|
+
},
|
|
79
|
+
darkMode: 'media',
|
|
80
|
+
preflight: true
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Adding a New Utility Handler
|
|
85
|
+
|
|
86
|
+
### Step 1: Identify the Category
|
|
87
|
+
|
|
88
|
+
- `generateLayoutRule()` - For layout utilities
|
|
89
|
+
- `generateSpaceRule()` - For spacing utilities
|
|
90
|
+
- `generateVisualRule()` - For visual utilities
|
|
91
|
+
|
|
92
|
+
### Step 2: Add Pattern Matching
|
|
93
|
+
|
|
94
|
+
In the appropriate generator function:
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
function generateVisualRule(prop, value, config) {
|
|
98
|
+
// Existing handlers...
|
|
99
|
+
|
|
100
|
+
// ADD: New utility
|
|
101
|
+
if (prop === 'my-utility') {
|
|
102
|
+
// Option 1: Simple keyword mapping
|
|
103
|
+
if (value === 'on') return 'my-css-property: value-on';
|
|
104
|
+
if (value === 'off') return 'my-css-property: value-off';
|
|
105
|
+
|
|
106
|
+
// Option 2: Scale-based
|
|
107
|
+
const scaleValue = config.theme.myScale?.[value];
|
|
108
|
+
if (scaleValue) return `my-css-property: ${scaleValue}`;
|
|
109
|
+
|
|
110
|
+
// Option 3: Arbitrary value with [brackets]
|
|
111
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
112
|
+
return `my-css-property: ${value.slice(1, -1)}`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ... rest of handlers
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Step 3: Add Scale (if needed)
|
|
121
|
+
|
|
122
|
+
Add to `defaultConfig.theme`:
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
myScale: {
|
|
126
|
+
'small': '4px',
|
|
127
|
+
'medium': '8px',
|
|
128
|
+
'big': '16px'
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
And update `generateCSSVariables()` if CSS variables are needed:
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
// In generateCSSVariables():
|
|
136
|
+
Object.entries(config.theme.myScale).forEach(([name, value]) => {
|
|
137
|
+
vars += ` --ss-my-scale-${name}: ${value};\n`;
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## State Handling
|
|
142
|
+
|
|
143
|
+
States (hover, focus, etc.) are handled automatically. The engine:
|
|
144
|
+
|
|
145
|
+
1. Detects state prefix: `hover:bg:blue-500`
|
|
146
|
+
2. Generates CSS with pseudo-selector: `.hover\:bg\:blue-500:hover { ... }`
|
|
147
|
+
|
|
148
|
+
State parsing occurs in the main processing loop before calling generators.
|
|
149
|
+
|
|
150
|
+
## Responsive Handling
|
|
151
|
+
|
|
152
|
+
Breakpoints wrap rules in media queries:
|
|
153
|
+
|
|
154
|
+
1. Detects breakpoint prefix: `mob:flex`
|
|
155
|
+
2. Generates: `@media (min-width: 480px) { .mob\:flex { display: flex } }`
|
|
156
|
+
|
|
157
|
+
## CSS Variable System
|
|
158
|
+
|
|
159
|
+
The engine generates CSS variables for all theme values:
|
|
160
|
+
|
|
161
|
+
```css
|
|
162
|
+
:root {
|
|
163
|
+
--ss-spacing-none: 0px;
|
|
164
|
+
--ss-spacing-thin: 1px;
|
|
165
|
+
--ss-radius-small: 4px;
|
|
166
|
+
--ss-color-primary: #3B82F6;
|
|
167
|
+
/* ... */
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Utilities can reference variables:
|
|
172
|
+
```javascript
|
|
173
|
+
return `border-radius: var(--ss-radius-${value})`;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Testing Engine Changes
|
|
177
|
+
|
|
178
|
+
After modifying the engine:
|
|
179
|
+
|
|
180
|
+
1. **Rebuild distribution:**
|
|
181
|
+
```bash
|
|
182
|
+
npm run build:dist
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
2. **Test in playground:**
|
|
186
|
+
Open `playground/index.html` in browser
|
|
187
|
+
|
|
188
|
+
3. **Run unit tests:**
|
|
189
|
+
```bash
|
|
190
|
+
npm run test:unit
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
4. **Check sync tests:**
|
|
194
|
+
```bash
|
|
195
|
+
npm run test:sync
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Common Patterns
|
|
199
|
+
|
|
200
|
+
### Color Handler
|
|
201
|
+
```javascript
|
|
202
|
+
if (prop === 'text') {
|
|
203
|
+
const color = config.theme.colors[value];
|
|
204
|
+
if (color) return `color: ${color}`;
|
|
205
|
+
// Support arbitrary colors
|
|
206
|
+
if (value.startsWith('[')) return `color: ${value.slice(1, -1)}`;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Spacing Handler
|
|
211
|
+
```javascript
|
|
212
|
+
if (prop === 'p') { // padding shorthand
|
|
213
|
+
const space = config.theme.spacing[value];
|
|
214
|
+
if (space) return `padding: ${space}`;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Multi-Property Handler
|
|
219
|
+
```javascript
|
|
220
|
+
if (prop === 'inset') {
|
|
221
|
+
const v = config.theme.spacing[value] || value;
|
|
222
|
+
return `top: ${v}; right: ${v}; bottom: ${v}; left: ${v}`;
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Transform Handler
|
|
227
|
+
```javascript
|
|
228
|
+
if (prop === 'rotate') {
|
|
229
|
+
return `--ss-rotate: ${value}deg; transform: var(--ss-transform)`;
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Debugging
|
|
234
|
+
|
|
235
|
+
Enable debug logging by adding early in the IIFE:
|
|
236
|
+
```javascript
|
|
237
|
+
const DEBUG = true;
|
|
238
|
+
function log(...args) { if (DEBUG) console.log('[SS]', ...args); }
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Then sprinkle `log()` calls in key functions.
|