esm-styles 0.1.12 → 0.2.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 +336 -87
- package/dist/lib/build.js +26 -9
- package/dist/lib/utils/content.js +7 -3
- package/doc/ai-guide.md +221 -0
- package/doc/api-reference.md +262 -0
- package/doc/css-variables.md +32 -0
- package/doc/translation.md +710 -0
- package/doc/usage-guide.md +600 -0
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# ESM Styles
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A CSS-in-JS solution for JavaScript/TypeScript projects.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- JavaScript to CSS conversion with an intuitive object syntax
|
|
8
|
+
- Build CSS from organized source files with a simple CLI
|
|
9
|
+
- CSS layering support for proper style encapsulation
|
|
10
|
+
- Media query and device/theme selectors with shorthands
|
|
11
|
+
- CSS variables with inheritance between themes
|
|
12
|
+
- Supporting modules for easy CSS variable usage
|
|
4
13
|
|
|
5
14
|
## Installation
|
|
6
15
|
|
|
@@ -10,146 +19,386 @@ npm install esm-styles
|
|
|
10
19
|
|
|
11
20
|
## Usage
|
|
12
21
|
|
|
13
|
-
### Basic
|
|
22
|
+
### Basic Concept
|
|
14
23
|
|
|
15
|
-
|
|
16
|
-
import { getCss } from 'esm-styles'
|
|
24
|
+
ESM Styles lets you write CSS in JavaScript objects with a natural syntax that converts to proper CSS:
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
```js
|
|
27
|
+
// component.styles.mjs
|
|
28
|
+
export default {
|
|
29
|
+
button: {
|
|
30
|
+
backgroundColor: '#4285f4',
|
|
31
|
+
color: 'white',
|
|
32
|
+
padding: '10px 20px',
|
|
33
|
+
borderRadius: '4px',
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
backgroundColor: '#
|
|
26
|
-
color: 'white',
|
|
27
|
-
padding: '1rem',
|
|
35
|
+
':hover': {
|
|
36
|
+
backgroundColor: '#3367d6',
|
|
28
37
|
},
|
|
29
38
|
|
|
30
|
-
'
|
|
31
|
-
|
|
32
|
-
margin: '0 auto',
|
|
33
|
-
padding: '1rem',
|
|
39
|
+
'@media (max-width: 768px)': {
|
|
40
|
+
padding: '8px 16px',
|
|
34
41
|
},
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### CLI Usage
|
|
47
|
+
|
|
48
|
+
Build your styles by creating a configuration file and running the CLI:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx build
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or specify a custom config:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx build path/to/config.js
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Watch for changes:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx watch
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Configuration
|
|
67
|
+
|
|
68
|
+
Create a `esm-styles.config.js` in your project root (or use a custom path):
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
export default {
|
|
72
|
+
basePath: './src/styles',
|
|
73
|
+
sourcePath: 'source',
|
|
74
|
+
outputPath: 'css',
|
|
75
|
+
sourceFilesSuffix: '.styles.mjs',
|
|
76
|
+
|
|
77
|
+
// Input layers
|
|
78
|
+
layers: ['defaults', 'components', 'layout'],
|
|
79
|
+
|
|
80
|
+
// Output
|
|
81
|
+
mainCssFile: 'styles.css',
|
|
82
|
+
|
|
83
|
+
// Global variables
|
|
84
|
+
globalVariables: 'global',
|
|
85
|
+
globalRootSelector: ':root',
|
|
86
|
+
|
|
87
|
+
// Media types and queries
|
|
88
|
+
media: {
|
|
89
|
+
theme: ['light', 'dark'],
|
|
90
|
+
device: ['mobile', 'tablet', 'desktop'],
|
|
91
|
+
},
|
|
35
92
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
93
|
+
mediaSelectors: {
|
|
94
|
+
theme: {
|
|
95
|
+
light: [
|
|
96
|
+
{
|
|
97
|
+
selector: '.light',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
selector: '.auto',
|
|
101
|
+
mediaQuery: 'screen and (prefers-color-scheme: light)',
|
|
102
|
+
prefix: 'auto',
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
dark: [
|
|
106
|
+
{
|
|
107
|
+
selector: '.dark',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
selector: '.auto',
|
|
111
|
+
mediaQuery: 'screen and (prefers-color-scheme: dark)',
|
|
112
|
+
prefix: 'auto',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
// Device selectors
|
|
117
|
+
device: {
|
|
118
|
+
mobile: [
|
|
119
|
+
{
|
|
120
|
+
mediaQuery: 'screen and (max-width: 767px)',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
tablet: [
|
|
124
|
+
{
|
|
125
|
+
mediaQuery: 'screen and (min-width: 768px) and (max-width: 1024px)',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
desktop: [
|
|
129
|
+
{
|
|
130
|
+
mediaQuery: 'screen and (min-width: 1025px)',
|
|
131
|
+
},
|
|
132
|
+
],
|
|
40
133
|
},
|
|
41
134
|
},
|
|
135
|
+
|
|
136
|
+
// Media query shorthands
|
|
137
|
+
mediaQueries: {
|
|
138
|
+
mobile: '(max-width: 767px)',
|
|
139
|
+
tablet: '(min-width: 768px) and (max-width: 1024px)',
|
|
140
|
+
desktop: '(min-width: 1025px)',
|
|
141
|
+
},
|
|
42
142
|
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## JS to CSS Translation
|
|
146
|
+
|
|
147
|
+
### Basic Selectors
|
|
43
148
|
|
|
44
|
-
|
|
45
|
-
|
|
149
|
+
```js
|
|
150
|
+
{
|
|
151
|
+
p: {
|
|
152
|
+
fontSize: '16px',
|
|
153
|
+
color: 'black',
|
|
154
|
+
|
|
155
|
+
a: {
|
|
156
|
+
color: 'blue'
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
strong: {
|
|
160
|
+
fontWeight: 'bold'
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
46
164
|
```
|
|
47
165
|
|
|
48
|
-
|
|
166
|
+
Compiles to:
|
|
49
167
|
|
|
50
168
|
```css
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
169
|
+
p {
|
|
170
|
+
font-size: 16px;
|
|
171
|
+
color: black;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
p a {
|
|
175
|
+
color: blue;
|
|
55
176
|
}
|
|
56
177
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
color: white;
|
|
60
|
-
padding: 1rem;
|
|
178
|
+
p strong {
|
|
179
|
+
font-weight: bold;
|
|
61
180
|
}
|
|
181
|
+
```
|
|
62
182
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
183
|
+
### Class Selectors
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
{
|
|
187
|
+
// Class selectors for non-HTML tag names
|
|
188
|
+
header: {
|
|
189
|
+
backgroundColor: '#f5f5f5',
|
|
190
|
+
padding: '20px'
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// Class on HTML tag using underscore prefix
|
|
194
|
+
p: {
|
|
195
|
+
_highlight: {
|
|
196
|
+
backgroundColor: 'yellow'
|
|
197
|
+
}
|
|
198
|
+
}
|
|
68
199
|
}
|
|
200
|
+
```
|
|
69
201
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
202
|
+
Compiles to:
|
|
203
|
+
|
|
204
|
+
```css
|
|
205
|
+
.header {
|
|
73
206
|
background-color: #f5f5f5;
|
|
207
|
+
padding: 20px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
p.highlight {
|
|
211
|
+
background-color: yellow;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Double Underscore for Descendant Class Selector
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
{
|
|
219
|
+
modal: {
|
|
220
|
+
position: 'relative',
|
|
221
|
+
|
|
222
|
+
__close: {
|
|
223
|
+
position: 'absolute',
|
|
224
|
+
top: '10px',
|
|
225
|
+
right: '10px'
|
|
226
|
+
}
|
|
227
|
+
}
|
|
74
228
|
}
|
|
75
229
|
```
|
|
76
230
|
|
|
77
|
-
|
|
231
|
+
Compiles to:
|
|
78
232
|
|
|
79
|
-
|
|
233
|
+
```css
|
|
234
|
+
.modal {
|
|
235
|
+
position: relative;
|
|
236
|
+
}
|
|
80
237
|
|
|
81
|
-
|
|
82
|
-
|
|
238
|
+
.modal .close {
|
|
239
|
+
position: absolute;
|
|
240
|
+
top: 10px;
|
|
241
|
+
right: 10px;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
83
244
|
|
|
84
|
-
|
|
85
|
-
body: {
|
|
86
|
-
fontSize: '16px',
|
|
245
|
+
### Multiple Selectors
|
|
87
246
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
247
|
+
```js
|
|
248
|
+
{
|
|
249
|
+
'button, .btn': {
|
|
250
|
+
padding: '10px 20px'
|
|
91
251
|
},
|
|
252
|
+
|
|
253
|
+
'input[type="text"], input[type="email"]': {
|
|
254
|
+
borderRadius: '4px'
|
|
255
|
+
}
|
|
92
256
|
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Nested Media Queries
|
|
93
260
|
|
|
94
|
-
|
|
261
|
+
```js
|
|
262
|
+
{
|
|
263
|
+
card: {
|
|
264
|
+
display: 'flex',
|
|
265
|
+
|
|
266
|
+
'@media (max-width: 768px)': {
|
|
267
|
+
flexDirection: 'column',
|
|
268
|
+
|
|
269
|
+
'@media (orientation: portrait)': {
|
|
270
|
+
padding: '10px'
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
95
275
|
```
|
|
96
276
|
|
|
97
|
-
|
|
277
|
+
### Named Media Queries
|
|
98
278
|
|
|
99
|
-
```
|
|
100
|
-
|
|
279
|
+
```js
|
|
280
|
+
{
|
|
281
|
+
main: {
|
|
282
|
+
display: 'grid',
|
|
101
283
|
|
|
102
|
-
const styles = {
|
|
103
|
-
container: {
|
|
104
|
-
width: '1200px',
|
|
105
|
-
'@tablet': {
|
|
106
|
-
width: '100%',
|
|
107
|
-
padding: '0 20px',
|
|
108
|
-
},
|
|
109
284
|
'@mobile': {
|
|
110
|
-
|
|
285
|
+
display: 'flex',
|
|
286
|
+
flexDirection: 'column'
|
|
111
287
|
},
|
|
112
|
-
|
|
288
|
+
|
|
289
|
+
'@desktop': {
|
|
290
|
+
gridTemplateColumns: 'repeat(3, 1fr)'
|
|
291
|
+
}
|
|
292
|
+
}
|
|
113
293
|
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Theme Support
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
{
|
|
300
|
+
card: {
|
|
301
|
+
backgroundColor: 'white',
|
|
302
|
+
color: 'black',
|
|
114
303
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
304
|
+
'@dark': {
|
|
305
|
+
backgroundColor: '#222',
|
|
306
|
+
color: 'white'
|
|
307
|
+
}
|
|
308
|
+
}
|
|
118
309
|
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### CSS Variables
|
|
313
|
+
|
|
314
|
+
Define variables in a global variables file:
|
|
119
315
|
|
|
120
|
-
|
|
316
|
+
```js
|
|
317
|
+
// global.styles.mjs
|
|
318
|
+
export default {
|
|
319
|
+
colors: {
|
|
320
|
+
primary: '#4285f4',
|
|
321
|
+
secondary: '#34a853',
|
|
322
|
+
error: '#ea4335',
|
|
323
|
+
},
|
|
324
|
+
spacing: {
|
|
325
|
+
sm: '8px',
|
|
326
|
+
md: '16px',
|
|
327
|
+
lg: '24px',
|
|
328
|
+
},
|
|
329
|
+
}
|
|
121
330
|
```
|
|
122
331
|
|
|
123
|
-
|
|
332
|
+
Define theme-specific variables:
|
|
124
333
|
|
|
125
|
-
```
|
|
126
|
-
|
|
334
|
+
```js
|
|
335
|
+
// light.styles.mjs
|
|
336
|
+
export default {
|
|
337
|
+
paper: {
|
|
338
|
+
bright: '#ffffff',
|
|
339
|
+
tinted: '#f0f0f0',
|
|
340
|
+
},
|
|
341
|
+
ink: {
|
|
342
|
+
bright: '#000000',
|
|
343
|
+
faded: '#333333',
|
|
344
|
+
accent: '#ff0000',
|
|
345
|
+
},
|
|
346
|
+
}
|
|
347
|
+
```
|
|
127
348
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
349
|
+
```js
|
|
350
|
+
// dark.styles.mjs
|
|
351
|
+
export default {
|
|
352
|
+
paper: {
|
|
353
|
+
bright: '#000000',
|
|
354
|
+
tinted: '#323232',
|
|
355
|
+
},
|
|
356
|
+
ink: {
|
|
357
|
+
bright: '#ffffff',
|
|
358
|
+
faded: '#b3b3b3',
|
|
359
|
+
},
|
|
360
|
+
}
|
|
361
|
+
```
|
|
132
362
|
|
|
133
|
-
|
|
134
|
-
p: {
|
|
135
|
-
lineHeight: 1.5,
|
|
136
|
-
},
|
|
363
|
+
Use with supporting modules:
|
|
137
364
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
},
|
|
365
|
+
```js
|
|
366
|
+
// component.styles.mjs
|
|
367
|
+
import $theme from './$theme.mjs'
|
|
142
368
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
369
|
+
export default {
|
|
370
|
+
button: {
|
|
371
|
+
backgroundColor: $theme.paper.bright,
|
|
372
|
+
color: $theme.ink.bright,
|
|
373
|
+
padding: '10px 20px',
|
|
147
374
|
},
|
|
148
375
|
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Advanced Features
|
|
149
379
|
|
|
150
|
-
|
|
380
|
+
### Layering
|
|
381
|
+
|
|
382
|
+
Organize your styles in layers for better control over specificity:
|
|
383
|
+
|
|
384
|
+
```js
|
|
385
|
+
// defaults.styles.mjs, components.styles.mjs, layout.styles.mjs
|
|
151
386
|
```
|
|
152
387
|
|
|
388
|
+
The build process wraps each in an appropriate layer and generates a main CSS file with proper import order.
|
|
389
|
+
|
|
390
|
+
### CSS Variable Inheritance
|
|
391
|
+
|
|
392
|
+
Missing variables in one theme automatically inherit from the previous theme in the configuration.
|
|
393
|
+
|
|
394
|
+
## Additional documentation
|
|
395
|
+
|
|
396
|
+
For humans: [doc/usage-guide.md](doc/usage-guide.md)
|
|
397
|
+
|
|
398
|
+
For AI assistants: [doc/ai-guide.md](doc/ai-guide.md)
|
|
399
|
+
|
|
400
|
+
API reference: [doc/api-reference.md](doc/api-reference.md)
|
|
401
|
+
|
|
153
402
|
## License
|
|
154
403
|
|
|
155
404
|
MIT
|
package/dist/lib/build.js
CHANGED
|
@@ -4,7 +4,6 @@ import { getMediaShorthands } from './utils/media-shorthand.js';
|
|
|
4
4
|
import { isEndValue } from './utils/index.js';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import fs from 'node:fs/promises';
|
|
7
|
-
import { inspect } from 'node:util';
|
|
8
7
|
import _ from 'lodash';
|
|
9
8
|
export async function build(configPath = 'esm-styles.config.js') {
|
|
10
9
|
// --- Supporting module generation ---
|
|
@@ -86,8 +85,8 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
86
85
|
}
|
|
87
86
|
// --- Supporting module generation ---
|
|
88
87
|
// Debug: log mergedSets and sets
|
|
89
|
-
console.log('[DEBUG] sets:', sets)
|
|
90
|
-
console.log('[DEBUG] mergedSets:', inspect(mergedSets, { depth: 10 }))
|
|
88
|
+
// console.log('[DEBUG] sets:', sets)
|
|
89
|
+
// console.log('[DEBUG] mergedSets:', inspect(mergedSets, { depth: 10 }))
|
|
91
90
|
// Define the recursive function here so it has access to sets and mergedSets
|
|
92
91
|
const buildSupportingModule = (path, isRoot) => {
|
|
93
92
|
const allKeys = new Set();
|
|
@@ -101,11 +100,24 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
102
|
// Debug: show allKeys and values at this path
|
|
104
|
-
console.log(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
// console.log(
|
|
104
|
+
// '[DEBUG] path:',
|
|
105
|
+
// path.join('.'),
|
|
106
|
+
// 'allKeys:',
|
|
107
|
+
// Array.from(allKeys)
|
|
108
|
+
// )
|
|
109
|
+
// for (const set of sets) {
|
|
110
|
+
// const v =
|
|
111
|
+
// path.length === 0 ? mergedSets[set] : _.get(mergedSets[set], path)
|
|
112
|
+
// console.log(
|
|
113
|
+
// '[DEBUG] set:',
|
|
114
|
+
// set,
|
|
115
|
+
// 'path:',
|
|
116
|
+
// path.join('.'),
|
|
117
|
+
// 'value:',
|
|
118
|
+
// JSON.stringify(v)
|
|
119
|
+
// )
|
|
120
|
+
// }
|
|
109
121
|
const result = {};
|
|
110
122
|
for (const key of allKeys) {
|
|
111
123
|
if (key === '') {
|
|
@@ -132,7 +144,12 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
132
144
|
}
|
|
133
145
|
}
|
|
134
146
|
// Debug log for each recursion
|
|
135
|
-
console.log(
|
|
147
|
+
// console.log(
|
|
148
|
+
// '[DEBUG] path:',
|
|
149
|
+
// path.join('.'),
|
|
150
|
+
// 'result:',
|
|
151
|
+
// JSON.stringify(result, null, 2)
|
|
152
|
+
// )
|
|
136
153
|
return result;
|
|
137
154
|
};
|
|
138
155
|
const supportingModuleObj = buildSupportingModule([], true);
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
export const contentValue = value => {
|
|
1
|
+
export const contentValue = (value) => {
|
|
2
2
|
if (typeof value !== 'string')
|
|
3
3
|
return value;
|
|
4
4
|
// If already quoted, return as is
|
|
5
5
|
if (/^'.*'$/.test(value) || /^".*"$/.test(value))
|
|
6
6
|
return value;
|
|
7
|
-
//
|
|
7
|
+
// If all characters are printable ASCII, return as quoted string
|
|
8
|
+
if (/^[\x20-\x7E]*$/.test(value)) {
|
|
9
|
+
return `'${value}'`;
|
|
10
|
+
}
|
|
11
|
+
// Otherwise, convert each character to CSS unicode escape: \00xxxx
|
|
8
12
|
const unicode = value
|
|
9
13
|
.split('')
|
|
10
|
-
.map(ch => {
|
|
14
|
+
.map((ch) => {
|
|
11
15
|
const code = ch.codePointAt(0);
|
|
12
16
|
if (!code)
|
|
13
17
|
return '';
|