Skip to content

Commit c4f2df5

Browse files
committed
Merge branch 'class-object-array'
2 parents 7b8d7b4 + 185c9df commit c4f2df5

File tree

7 files changed

+359
-46
lines changed

7 files changed

+359
-46
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Svelte preprocess CSS Modules, changelog
22

3+
## 3.0.1 (Feb 7 2025)
4+
5+
### Fixes
6+
7+
- Add support to class objects and arrays
8+
39
## 3.0.0 (Jan 17 2025)
410

511
### Update

README.md

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ for `svelte 4` and below, use version 2 of the preprocessor.
1212

1313
- [Usage](#usage)
1414
- [Approach](#approach)
15+
- [Class objects and arrays](#class-object-and-arrays)
1516
- [Class directive](#class-directive)
1617
- [Local selector](#local-selector)
1718
- [CSS binding](#css-binding)
@@ -109,6 +110,70 @@ _transformed to_
109110
</style>
110111
```
111112

113+
### Class objects and arrays
114+
115+
#### Object with thruthy values
116+
117+
```html
118+
<script>
119+
let active = $state(true);
120+
</script>
121+
122+
<div class={{ active, red: !active, 'bold': active }}>...</div>
123+
124+
<style module>
125+
.active { color: green; }
126+
.red { color: red; }
127+
.bold { font-weight: bold; }
128+
</style>
129+
```
130+
131+
*generating*
132+
133+
```html
134+
<div class="active-aft3ew bold-sfkje6">...</div>
135+
<!-- OR -->
136+
<div class="red-bft3ed">...</div>
137+
138+
<style>
139+
.active-aft3ew { color: green; }
140+
.red-bft3ed { color: red; }
141+
.bold-sfkje6 { font-weight: bold; }
142+
</style>
143+
```
144+
145+
#### Array with thruthy values
146+
147+
```html
148+
<script>
149+
let active = $state(true);
150+
</script>
151+
152+
<div class={[ active && 'green bold', 'italic', !active && 'red' ]}>...</div>
153+
154+
<style module>
155+
.green { color: green; }
156+
.red { color: red; }
157+
.bold { font-weight: bold; }
158+
.italic { font-style: italic; }
159+
</style>
160+
```
161+
162+
*generating*
163+
164+
```html
165+
<div class="green-dhy6wu bold-uytsge italic-b65wfq">...</div>
166+
<!-- OR -->
167+
<div class="red-uyqrw4 italic-b65wfq">...</div>
168+
169+
<style>
170+
.green-dhy6wu { color: green; }
171+
.red-uyqrw4 { color: red; }
172+
.bold-uytsge { font-weight: bold; }
173+
.italic-b65wfq { font-style: italic; }
174+
</style>
175+
```
176+
112177
### Class directive
113178

114179
Toggle a class on an element.
@@ -357,11 +422,11 @@ CSS Modules allows you to pass a scoped classname to a child component giving th
357422
```html
358423
<!-- Child Component Button.svelte -->
359424
<script>
360-
let { class: className } = $props();
425+
let props = $props();
361426
</script>
362427

363-
<button class="btn {className}">
364-
<slot />
428+
<button class={['btn', props.class]}>
429+
{@render props.children?.()}
365430
</button>
366431

367432
<style module>

example/webpack/package-lock.json

Lines changed: 17 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 19 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "svelte-preprocess-cssmodules",
3-
"version": "3.0.0",
3+
"version": "3.0.1",
44
"description": "Svelte preprocessor to generate CSS Modules classname on Svelte components",
55
"keywords": [
66
"svelte",

src/parsers/template.ts

Lines changed: 94 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ const updateMultipleClasses = (processor: Processor, classNames: string): string
3131
};
3232

3333
/**
34-
* Parse and update classes of a js expression element
34+
* Parse and update literal expression element
3535
* @param processor: The CSS Module Processor
36-
* @param expression The expression node (consequent, alternate)
36+
* @param expression The expression node
3737
*/
38-
const parseExpression = (
38+
const parseLiteralExpression = (
3939
processor: Processor,
40-
expression: AST.ExpressionTag['expression']
40+
expression: AST.ExpressionTag['expression'] | null
4141
): void => {
4242
const exp = expression as typeof expression & AST.BaseNode;
4343
if (exp.type === 'Literal' && typeof exp.value === 'string') {
@@ -46,6 +46,77 @@ const parseExpression = (
4646
}
4747
};
4848

49+
/**
50+
* Parse and update conditional expression
51+
* @param processor: The CSS Module Processor
52+
* @param expression The expression node
53+
*/
54+
const parseConditionalExpression = (
55+
processor: Processor,
56+
expression: AST.ExpressionTag['expression']
57+
): void => {
58+
if (expression.type === 'ConditionalExpression') {
59+
const { consequent, alternate } = expression;
60+
parseLiteralExpression(processor, consequent);
61+
parseLiteralExpression(processor, alternate);
62+
}
63+
};
64+
65+
/**
66+
* Parse and update object expression
67+
* @param processor: The CSS Module Processor
68+
* @param expression The expression node
69+
*/
70+
const parseObjectExpression = (
71+
processor: Processor,
72+
expression: AST.ExpressionTag['expression']
73+
): void => {
74+
if (expression.type === 'ObjectExpression') {
75+
expression?.properties.forEach((property) => {
76+
if (property.type === 'Property') {
77+
const key = property.key as (typeof property)['key'] & AST.BaseNode;
78+
79+
if (property.shorthand) {
80+
if (key.type === 'Identifier') {
81+
processor.magicContent.overwrite(
82+
key.start,
83+
key.end,
84+
`'${processor.cssModuleList[key.name]}': ${key.name}`
85+
);
86+
}
87+
} else if (key.type === 'Identifier') {
88+
processor.magicContent.overwrite(
89+
key.start,
90+
key.end,
91+
`'${processor.cssModuleList[key.name]}'`
92+
);
93+
} else if (key.type !== 'PrivateIdentifier') {
94+
parseLiteralExpression(processor, key);
95+
}
96+
}
97+
});
98+
}
99+
};
100+
/**
101+
* Parse and update array expression
102+
* @param processor: The CSS Module Processor
103+
* @param expression The expression node
104+
*/
105+
const parseArrayExpression = (
106+
processor: Processor,
107+
expression: AST.ExpressionTag['expression']
108+
): void => {
109+
if (expression.type === 'ArrayExpression') {
110+
expression.elements.forEach((el) => {
111+
if (el?.type === 'LogicalExpression') {
112+
parseLiteralExpression(processor, el.right);
113+
} else if (el?.type !== 'SpreadElement') {
114+
parseLiteralExpression(processor, el);
115+
}
116+
});
117+
}
118+
};
119+
49120
/**
50121
* Add the dynamic variables to elements
51122
* @param processor The CSS Module Processor
@@ -151,28 +222,25 @@ export default (processor: Processor): void => {
151222
(node as AST.Component | AST.RegularElement).attributes.length > 0
152223
) {
153224
(node as AST.Component | AST.RegularElement).attributes.forEach((item) => {
154-
if (
155-
item.type === 'Attribute' &&
156-
allowedAttributes.includes(item.name) &&
157-
Array.isArray(item.value)
158-
) {
159-
item.value.forEach((classItem) => {
160-
if (classItem.type === 'Text' && classItem.data.length > 0) {
161-
const generatedClassNames = updateMultipleClasses(processor, classItem.data);
162-
processor.magicContent.overwrite(
163-
classItem.start,
164-
classItem.start + classItem.data.length,
165-
generatedClassNames
166-
);
167-
} else if (
168-
classItem.type === 'ExpressionTag' &&
169-
classItem?.expression?.type === 'ConditionalExpression'
170-
) {
171-
const { consequent, alternate } = classItem.expression;
172-
parseExpression(processor, consequent);
173-
parseExpression(processor, alternate);
174-
}
175-
});
225+
if (item.type === 'Attribute' && allowedAttributes.includes(item.name)) {
226+
if (Array.isArray(item.value)) {
227+
item.value.forEach((classItem) => {
228+
if (classItem.type === 'Text' && classItem.data.length > 0) {
229+
const generatedClassNames = updateMultipleClasses(processor, classItem.data);
230+
processor.magicContent.overwrite(
231+
classItem.start,
232+
classItem.start + classItem.data.length,
233+
generatedClassNames
234+
);
235+
} else if (classItem.type === 'ExpressionTag') {
236+
parseConditionalExpression(processor, classItem.expression);
237+
}
238+
});
239+
} else if (typeof item.value === 'object' && item.value.type === 'ExpressionTag') {
240+
parseObjectExpression(processor, item.value.expression);
241+
parseArrayExpression(processor, item.value.expression);
242+
parseConditionalExpression(processor, item.value.expression);
243+
}
176244
}
177245
if (item.type === 'ClassDirective') {
178246
const classNames = item.name.split('.');

0 commit comments

Comments
 (0)