@massimo-cassandro/minimo 1.33.6 → 1.34.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/design-tokens/README.md +2 -197
- package/dev-tools/README.md +1 -0
- package/dev-tools/design-tokens-builder/README.md +208 -0
- package/dev-tools/design-tokens-builder/build-tokens-src/build-source-modes.mjs +152 -0
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/config.mjs +35 -1
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/formats/css.mjs +16 -4
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/formats/json.mjs +9 -4
- package/dev-tools/design-tokens-builder/build-tokens-src/merge-css.mjs +192 -0
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/platforms.mjs +3 -1
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens.mjs +99 -45
- package/{design-tokens/utilities → dev-tools/design-tokens-builder}/check-unresolved-custom-props.mjs +66 -13
- package/{design-tokens → dev-tools/design-tokens-builder}/tokens-config-sample.mjs +39 -1
- package/dev-tools/starter-kit/source_files/root/package-tpl.json +2 -2
- package/dev-tools/starter-kit/starter-install.sh +2 -2
- package/package.json +6 -8
- package/src/web-components/s-datatable-component/src/parse-cols.js +1 -1
- package/design-tokens/utilities/build-tokens-src/merge-css.mjs +0 -60
- /package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/resolve-source-paths.mjs +0 -0
- /package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/transforms.mjs +0 -0
package/design-tokens/README.md
CHANGED
|
@@ -6,201 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
<!-- TODO modalità d'uso (da tokens penpots o figma, con o snza merge css ecc...) -->
|
|
8
8
|
|
|
9
|
-
## Design
|
|
9
|
+
## Design Tokens Builder
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
A set of Node.js scripts for managing CSS design tokens in a project. Built on top of [Style Dictionary v5](https://styledictionary.com/).
|
|
14
|
-
|
|
15
|
-
### Scripts
|
|
16
|
-
|
|
17
|
-
| Script | Purpose |
|
|
18
|
-
|---|---|
|
|
19
|
-
| `build-tokens.mjs` | Generates a CSS custom properties file from design token source files |
|
|
20
|
-
| `check-unresolved-custom-props.mjs` | Scans CSS files and reports any `var(--...)` references not found in the generated token file |
|
|
21
|
-
|
|
22
|
-
Both scripts share the same config file, passed via `--config`.
|
|
23
|
-
|
|
24
|
-
### Setup
|
|
25
|
-
|
|
26
|
-
The scripts are intended to be used as npm scripts. Add them to your `package.json`:
|
|
27
|
-
|
|
28
|
-
```json
|
|
29
|
-
{
|
|
30
|
-
"scripts": {
|
|
31
|
-
"build tokens": "npx buildTokens --config ./path/to/tokens-config.mjs",
|
|
32
|
-
"check unresolved props": "npx checkUnresolvedProps --config ./path/to/tokens-config.mjs"
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Dependencies
|
|
38
|
-
|
|
39
|
-
The following packages must be present in your project:
|
|
40
|
-
|
|
41
|
-
```json
|
|
42
|
-
{
|
|
43
|
-
"style-dictionary": "^5",
|
|
44
|
-
"stylelint": "^16"
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Configuration file
|
|
49
|
-
|
|
50
|
-
Both scripts read a shared config file passed with `--config`. Use `tokens-config-sample.mjs` as a starting point.
|
|
51
|
-
|
|
52
|
-
Read comments inside the sample file for more info.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
### `build-tokens.mjs`
|
|
56
|
-
|
|
57
|
-
Reads design token source files and generates:
|
|
58
|
-
- A **CSS file** containing all tokens as custom properties inside `:root { ... }`
|
|
59
|
-
- Optionally, one or more **W3C DTCG JSON/JSONC** files, independent of any specific consuming tool (usable in Penpot, Figma via plugin, Token Studio, ...).
|
|
60
|
-
|
|
61
|
-
#### Usage
|
|
62
|
-
|
|
63
|
-
```sh
|
|
64
|
-
npx buildTokens --config ./path/to/tokens-config.mjs
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
#### Source files
|
|
68
|
-
|
|
69
|
-
The `source` array accepts any combination of:
|
|
70
|
-
- Concrete file paths: `'./tokens/colors.jsonc'`
|
|
71
|
-
- Glob patterns: `'./tokens/**/*.{json,jsonc,mjs}'`
|
|
72
|
-
|
|
73
|
-
**Supported formats:**
|
|
74
|
-
- `.json` / `.jsonc` — parsed directly as token data
|
|
75
|
-
- `.mjs` / `.js` — imported as ES modules; must export a W3C DTCG-compliant object as `default`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#### Token file format (JS example)
|
|
79
|
-
|
|
80
|
-
When using `.mjs` source files, export a default object following the [W3C Design Token Community Group format](https://design-tokens.github.io/community-group/format/):
|
|
81
|
-
|
|
82
|
-
```js
|
|
83
|
-
// colors.tokens.mjs
|
|
84
|
-
export default {
|
|
85
|
-
color: {
|
|
86
|
-
brand: {
|
|
87
|
-
primary: { $type: 'color', $value: '#0057FF' },
|
|
88
|
-
secondary: { $type: 'color', $value: '#FF6B00' },
|
|
89
|
-
},
|
|
90
|
-
neutral: {
|
|
91
|
-
'100': { $type: 'color', $value: '#F5F5F5' },
|
|
92
|
-
'900': { $type: 'color', $value: '#1A1A1A' },
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
semantic: {
|
|
96
|
-
color: {
|
|
97
|
-
text: {
|
|
98
|
-
primary: { $type: 'color', $value: '{color.neutral.900}' }, // alias
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
The main advantage of `.mjs` over `.json` is the ability to use JavaScript variables, comments, and computed values within the token definition.
|
|
106
|
-
|
|
107
|
-
Values can be expressions like `"{size.base} * .25"`, see `tokens-config-sample.mjs` for more info.
|
|
108
|
-
|
|
109
|
-
#### Custom transforms
|
|
110
|
-
|
|
111
|
-
The following transforms are registered on top of Style Dictionary's built-ins:
|
|
112
|
-
|
|
113
|
-
| Name | Type | Description |
|
|
114
|
-
|---|---|---|
|
|
115
|
-
| `shadow/css` | value | Converts shadow objects (single or array) to a `box-shadow` CSS string. px values are converted to rem. |
|
|
116
|
-
| `size/pxToRem-smart` | value | Converts `dimension` token values from px to rem. Skips values already in rem. |
|
|
117
|
-
| `color/css-modern` | value | Color passthrough with transitive alias support. |
|
|
118
|
-
| `gradient/css` | value | Converts gradient objects to CSS `linear-gradient`, `radial-gradient` or `conic-gradient`. |
|
|
119
|
-
| `composite/css` | value | Converts `border`, `outline`, `transition` and `animation` objects to their CSS shorthand. |
|
|
120
|
-
| `typography/css` | value | Typography passthrough — the format handles exploding the value into individual properties. |
|
|
121
|
-
|
|
122
|
-
#### CSS output
|
|
123
|
-
|
|
124
|
-
All tokens are output as CSS custom properties inside `:root`, sorted alphabetically.
|
|
125
|
-
|
|
126
|
-
Typography tokens are expanded into multiple properties:
|
|
127
|
-
- `--token-name-font` — a `font` shorthand (when both `fontSize` and `fontFamily` are present)
|
|
128
|
-
- `--token-name-letter-spacing`, `--token-name-text-transform`, `--token-name-text-decoration` — individual properties
|
|
129
|
-
|
|
130
|
-
The generated file is linted and auto-fixed with stylelint using the config specified in `stylelintConfigPath`.
|
|
131
|
-
|
|
132
|
-
#### JSON output
|
|
133
|
-
|
|
134
|
-
When `jsonBuildPath` is set, the script also generates W3C DTCG token files, independent of any specific consuming tool (e.g. importable into Penpot via the [Design Tokens plugin](https://penpot.app/penpot-files/design-tokens), into Figma via a compatible plugin, or into Token Studio).
|
|
135
|
-
|
|
136
|
-
**Single file** (`jsonDestFile` is a string):
|
|
137
|
-
```
|
|
138
|
-
jsonBuildPath/tokens.jsonc ← all tokens in one file
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
**One file per source** (`jsonDestFile` is `null` or omitted):
|
|
142
|
-
```
|
|
143
|
-
jsonBuildPath/colors.tokens.jsonc
|
|
144
|
-
jsonBuildPath/spacing.tokens.jsonc
|
|
145
|
-
jsonBuildPath/typography.tokens.jsonc
|
|
146
|
-
```
|
|
147
|
-
All files are written flat in `jsonBuildPath`, regardless of the subdirectory structure of the source files — no subdirectories are created. If two source files with different paths would produce the same output filename, the build fails with an error listing the colliding files instead of silently overwriting one with the other.
|
|
148
|
-
|
|
149
|
-
The `jsonFormat` option controls the output:
|
|
150
|
-
|
|
151
|
-
| Value | Extension | Disclaimer header |
|
|
152
|
-
|---|---|---|
|
|
153
|
-
| `'jsonc'` | `.jsonc` | Yes |
|
|
154
|
-
| `'json'` | `.json` | No |
|
|
155
|
-
|
|
156
|
-
Alias references (`{color.brand.primary}`) are preserved in the output so consuming tools maintain token links between primitive and semantic tokens.
|
|
157
|
-
|
|
158
|
-
> JSON token files intentionally use a minimal set of transforms (no px→rem conversion, no shorthand expansion) because consuming tools generally expect original values and structured objects, not resolved CSS strings.
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
### `check-unresolved-custom-props.mjs`
|
|
163
|
-
|
|
164
|
-
Scans all `.css` files in `dirToCheck` (recursively) and reports:
|
|
165
|
-
|
|
166
|
-
- **unresolved properties** — `var(--...)` references that are not defined in the generated token CSS file or in any `extraCustomPropsFiles`
|
|
167
|
-
- **unused properties** — custom properties defined in the generated token CSS file (or an `extraCustomPropsFiles` entry) but never referenced via `var()` in any scanned CSS file. Only reported if `checkUnused: true` is set in the config, since it re-runs the Style Dictionary transform pipeline over the token sources to resolve, when possible, the `.mjs` source file for each unused property. When `ignoreUnusedInNodeModules` is true (default), properties sourced from a token file under `node_modules` (e.g. a third-party token package) are never reported as unused.
|
|
168
|
-
|
|
169
|
-
Results are written to `unresolved-unused-props.md` in the same directory as the config file.
|
|
170
|
-
|
|
171
|
-
#### Usage
|
|
172
|
-
|
|
173
|
-
```sh
|
|
174
|
-
npx checkUnresolvedProps --config ./path/to/tokens-config.mjs
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
#### Output
|
|
178
|
-
|
|
179
|
-
The script creates a `unresolved-unused-props.md` file next to the config file (only if there is at least one unresolved or unused property). Each unresolved entry links to the file and line number where the property is used; each unused entry links to its `.mjs` token source file when it can be resolved, otherwise just the property name is reported:
|
|
180
|
-
|
|
181
|
-
```markdown
|
|
182
|
-
## Unresolved custom properties
|
|
183
|
-
|
|
184
|
-
* [components.css](../src/components.css#L42) -> `--color-action-hover`
|
|
185
|
-
* [layout.css](../src/layout.css#L17) -> `--spacing-missing`
|
|
186
|
-
|
|
187
|
-
## Unused custom properties
|
|
188
|
-
|
|
189
|
-
* [colors.minimo.tokens.mjs](../_src/colors.minimo.tokens.mjs) -> `--color-legacy-accent`
|
|
190
|
-
* `--manually-added-prop`
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
In VS Code, Option/Alt-clicking on the URL opens the file at the required line.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
#### Excluding properties
|
|
197
|
-
|
|
198
|
-
Use `excludePattern` to suppress false positives — for example, internal/private custom properties that are defined dynamically or inline:
|
|
199
|
-
|
|
200
|
-
```js
|
|
201
|
-
// skips all custom props starting with `--_` or `--js-`
|
|
202
|
-
excludePattern: [
|
|
203
|
-
/^--_/,
|
|
204
|
-
/^--js-/,
|
|
205
|
-
]
|
|
206
|
-
```
|
|
11
|
+
The Node.js scripts that generate the CSS custom properties / JSON token files from the sources in this directory (`build-tokens.mjs`, `check-unresolved-custom-props.mjs`, exposed as the `buildTokens` / `checkUnresolvedProps` bins) live in [dev-tools/design-tokens-builder](../dev-tools/design-tokens-builder/README.md), alongside the other standalone dev-tools CLI utilities.
|
package/dev-tools/README.md
CHANGED
|
@@ -6,6 +6,7 @@ A collection of standalone Node.js CLI utilities to help manage a web project's
|
|
|
6
6
|
* [Svg Icons Tools](./svg-icons-tools/README.md) — optimizes SVG icons and combines/converts them into symbols, JSX, or SCSS variables
|
|
7
7
|
* [Create favicons](./create-favicons/README.md) — generates the full favicon set (SVG, PNG, ICO, webmanifest, HTML snippet) from a source image
|
|
8
8
|
* [upd@m](./upd@m/README.md) — updates all `@massimo-cassandro/*` packages installed in `node_modules`
|
|
9
|
+
* [Design Tokens Builder](./design-tokens-builder/README.md) — generates a CSS custom properties file (and optional JSON token files) from design token sources
|
|
9
10
|
* [starter-kit](#starter-kit) — script for installing minimo environment
|
|
10
11
|
|
|
11
12
|
## starter-kit
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Design Tokens Builder
|
|
2
|
+
|
|
3
|
+
<!-- TODO rivedere e semplificare -->
|
|
4
|
+
|
|
5
|
+
A set of Node.js scripts for managing CSS design tokens in a project. Built on top of [Style Dictionary v5](https://styledictionary.com/).
|
|
6
|
+
|
|
7
|
+
### Scripts
|
|
8
|
+
|
|
9
|
+
| Script | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `build-tokens.mjs` | Generates a CSS custom properties file from design token source files |
|
|
12
|
+
| `check-unresolved-custom-props.mjs` | Scans CSS files and reports any `var(--...)` references not found in the generated token file |
|
|
13
|
+
|
|
14
|
+
Both scripts share the same config file, passed via `--config`.
|
|
15
|
+
|
|
16
|
+
### Setup
|
|
17
|
+
|
|
18
|
+
The scripts are intended to be used as npm scripts. Add them to your `package.json`:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build tokens": "npx buildTokens --config ./path/to/tokens-config.mjs",
|
|
24
|
+
"check unresolved props": "npx checkUnresolvedProps --config ./path/to/tokens-config.mjs"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Dependencies
|
|
30
|
+
|
|
31
|
+
The following packages must be present in your project:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"style-dictionary": "^5",
|
|
36
|
+
"stylelint": "^16"
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Configuration file
|
|
41
|
+
|
|
42
|
+
Both scripts read a shared config file passed with `--config`. Use `tokens-config-sample.mjs` as a starting point.
|
|
43
|
+
|
|
44
|
+
Read comments inside the sample file for more info.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### `build-tokens.mjs`
|
|
48
|
+
|
|
49
|
+
Reads design token source files and generates:
|
|
50
|
+
- A **CSS file** containing all tokens as custom properties inside `:root { ... }`
|
|
51
|
+
- Optionally, one or more **W3C DTCG JSON/JSONC** files, independent of any specific consuming tool (usable in Penpot, Figma via plugin, Token Studio, ...).
|
|
52
|
+
|
|
53
|
+
#### Usage
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npx buildTokens --config ./path/to/tokens-config.mjs
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Source files
|
|
60
|
+
|
|
61
|
+
The `source` array accepts any combination of:
|
|
62
|
+
- Concrete file paths: `'./tokens/colors.jsonc'`
|
|
63
|
+
- Glob patterns: `'./tokens/**/*.{json,jsonc,mjs}'`
|
|
64
|
+
|
|
65
|
+
**Supported formats:**
|
|
66
|
+
- `.json` / `.jsonc` — parsed directly as token data
|
|
67
|
+
- `.mjs` / `.js` — imported as ES modules; must export a W3C DTCG-compliant object as `default`
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
#### Token file format (JS example)
|
|
71
|
+
|
|
72
|
+
When using `.mjs` source files, export a default object following the [W3C Design Token Community Group format](https://design-tokens.github.io/community-group/format/):
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// colors.tokens.mjs
|
|
76
|
+
export default {
|
|
77
|
+
color: {
|
|
78
|
+
brand: {
|
|
79
|
+
primary: { $type: 'color', $value: '#0057FF' },
|
|
80
|
+
secondary: { $type: 'color', $value: '#FF6B00' },
|
|
81
|
+
},
|
|
82
|
+
neutral: {
|
|
83
|
+
'100': { $type: 'color', $value: '#F5F5F5' },
|
|
84
|
+
'900': { $type: 'color', $value: '#1A1A1A' },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
semantic: {
|
|
88
|
+
color: {
|
|
89
|
+
text: {
|
|
90
|
+
primary: { $type: 'color', $value: '{color.neutral.900}' }, // alias
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The main advantage of `.mjs` over `.json` is the ability to use JavaScript variables, comments, and computed values within the token definition.
|
|
98
|
+
|
|
99
|
+
Values can be expressions like `"{size.base} * .25"`, see `tokens-config-sample.mjs` for more info.
|
|
100
|
+
|
|
101
|
+
#### Custom transforms
|
|
102
|
+
|
|
103
|
+
The following transforms are registered on top of Style Dictionary's built-ins:
|
|
104
|
+
|
|
105
|
+
| Name | Type | Description |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `shadow/css` | value | Converts shadow objects (single or array) to a `box-shadow` CSS string. px values are converted to rem. |
|
|
108
|
+
| `size/pxToRem-smart` | value | Converts `dimension` token values from px to rem. Skips values already in rem. |
|
|
109
|
+
| `color/css-modern` | value | Color passthrough with transitive alias support. |
|
|
110
|
+
| `gradient/css` | value | Converts gradient objects to CSS `linear-gradient`, `radial-gradient` or `conic-gradient`. |
|
|
111
|
+
| `composite/css` | value | Converts `border`, `outline`, `transition` and `animation` objects to their CSS shorthand. |
|
|
112
|
+
| `typography/css` | value | Typography passthrough — the format handles exploding the value into individual properties. |
|
|
113
|
+
|
|
114
|
+
#### CSS output
|
|
115
|
+
|
|
116
|
+
All tokens are output as CSS custom properties inside `:root`, sorted alphabetically.
|
|
117
|
+
|
|
118
|
+
Typography tokens are expanded into multiple properties:
|
|
119
|
+
- `--token-name-font` — a `font` shorthand (when both `fontSize` and `fontFamily` are present)
|
|
120
|
+
- `--token-name-letter-spacing`, `--token-name-text-transform`, `--token-name-text-decoration` — individual properties
|
|
121
|
+
|
|
122
|
+
The generated file is linted and auto-fixed with stylelint using the config specified in `stylelintConfigPath`.
|
|
123
|
+
|
|
124
|
+
#### JSON output
|
|
125
|
+
|
|
126
|
+
When `jsonBuildPath` is set, the script also generates W3C DTCG token files, independent of any specific consuming tool (e.g. importable into Penpot via the [Design Tokens plugin](https://penpot.app/penpot-files/design-tokens), into Figma via a compatible plugin, or into Token Studio).
|
|
127
|
+
|
|
128
|
+
**Single file** (`jsonDestFile` is a string):
|
|
129
|
+
```
|
|
130
|
+
jsonBuildPath/tokens.jsonc ← all tokens in one file
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**One file per source** (`jsonDestFile` is `null` or omitted):
|
|
134
|
+
```
|
|
135
|
+
jsonBuildPath/colors.tokens.jsonc
|
|
136
|
+
jsonBuildPath/spacing.tokens.jsonc
|
|
137
|
+
jsonBuildPath/typography.tokens.jsonc
|
|
138
|
+
```
|
|
139
|
+
All files are written flat in `jsonBuildPath`, regardless of the subdirectory structure of the source files — no subdirectories are created. If two source files with different paths would produce the same output filename, the build fails with an error listing the colliding files instead of silently overwriting one with the other.
|
|
140
|
+
|
|
141
|
+
The `jsonFormat` option controls the output:
|
|
142
|
+
|
|
143
|
+
| Value | Extension | Disclaimer header |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `'jsonc'` | `.jsonc` | Yes |
|
|
146
|
+
| `'json'` | `.json` | No |
|
|
147
|
+
|
|
148
|
+
Alias references (`{color.brand.primary}`) are preserved in the output so consuming tools maintain token links between primitive and semantic tokens.
|
|
149
|
+
|
|
150
|
+
> JSON token files intentionally use a minimal set of transforms (no px→rem conversion, no shorthand expansion) because consuming tools generally expect original values and structured objects, not resolved CSS strings.
|
|
151
|
+
|
|
152
|
+
#### sourceModes (light/dark split)
|
|
153
|
+
|
|
154
|
+
`sourceModes` is an alternative to `source`, for projects that need a light/dark (or other) custom properties split. See the comments in `tokens-config-sample.mjs` for the full option reference (`sourceModes`, `sourceModesBase`).
|
|
155
|
+
|
|
156
|
+
When set, each mode has its own `source` array, and the generated CSS composes all modes into a single `destFile`:
|
|
157
|
+
- the base mode's props go in a top-level `:root { ... }` block, with `color-scheme: <all modes>;` prepended
|
|
158
|
+
- every other mode's props go in `@media (prefers-color-scheme: <mode>) { :root { ... } }`, with `color-scheme: <mode>;` prepended
|
|
159
|
+
|
|
160
|
+
`customPropsGroups`, `mergeCustomProps`, `addLayer`, `pxToRem` etc. all apply the same way as with a single `source`. The JSON output (when `jsonBuildPath` is set) is produced once per mode, with the mode name appended to each filename (e.g. `tokens-light.jsonc` / `tokens-dark.jsonc`).
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### `check-unresolved-custom-props.mjs`
|
|
165
|
+
|
|
166
|
+
Scans all `.css` files in `dirToCheck` (recursively) and reports:
|
|
167
|
+
|
|
168
|
+
- **unresolved properties** — `var(--...)` references that are not defined in the generated token CSS file or in any `extraCustomPropsFiles`
|
|
169
|
+
- **unused properties** — custom properties defined in the generated token CSS file (or an `extraCustomPropsFiles` entry) but never referenced via `var()` in any scanned CSS file. Only reported if `checkUnused: true` is set in the config, since it re-runs the Style Dictionary transform pipeline over the token sources to resolve, when possible, the `.mjs` source file for each unused property. When `ignoreUnusedInNodeModules` is true (default), properties sourced from a token file under `node_modules` (e.g. a third-party token package) are never reported as unused.
|
|
170
|
+
|
|
171
|
+
Results are written to `unresolved-unused-props.md` in the same directory as the config file.
|
|
172
|
+
|
|
173
|
+
#### Usage
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
npx checkUnresolvedProps --config ./path/to/tokens-config.mjs
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### Output
|
|
180
|
+
|
|
181
|
+
The script creates a `unresolved-unused-props.md` file next to the config file (only if there is at least one unresolved or unused property). Each unresolved entry links to the file and line number where the property is used; each unused entry links to its `.mjs` token source file when it can be resolved, otherwise just the property name is reported:
|
|
182
|
+
|
|
183
|
+
```markdown
|
|
184
|
+
## Unresolved custom properties
|
|
185
|
+
|
|
186
|
+
* [components.css](../src/components.css#L42) -> `--color-action-hover`
|
|
187
|
+
* [layout.css](../src/layout.css#L17) -> `--spacing-missing`
|
|
188
|
+
|
|
189
|
+
## Unused custom properties
|
|
190
|
+
|
|
191
|
+
* [colors.minimo.tokens.mjs](../_src/colors.minimo.tokens.mjs) -> `--color-legacy-accent`
|
|
192
|
+
* `--manually-added-prop`
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
In VS Code, Option/Alt-clicking on the URL opens the file at the required line.
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
#### Excluding properties
|
|
199
|
+
|
|
200
|
+
Use `excludePattern` to suppress false positives — for example, internal/private custom properties that are defined dynamically or inline:
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
// skips all custom props starting with `--_` or `--js-`
|
|
204
|
+
excludePattern: [
|
|
205
|
+
/^--_/,
|
|
206
|
+
/^--js-/,
|
|
207
|
+
]
|
|
208
|
+
```
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// build-tokens-src/build-source-modes.mjs
|
|
2
|
+
// Alternative build path used when the project config sets `sourceModes`
|
|
3
|
+
// (see config.mjs) instead of a single `source` array — typically for a
|
|
4
|
+
// light/dark custom-properties split.
|
|
5
|
+
//
|
|
6
|
+
// For each mode, a separate StyleDictionary instance is built from that
|
|
7
|
+
// mode's own `source` array. The generated `:root { ... }` CSS blocks are
|
|
8
|
+
// then composed into a single destFile:
|
|
9
|
+
// - the base mode's block stays as a plain `:root { ... }` rule, with a
|
|
10
|
+
// `color-scheme: <all modes joined>;` declaration prepended
|
|
11
|
+
// (e.g. "light dark")
|
|
12
|
+
// - every other mode's block is wrapped in
|
|
13
|
+
// `@media (prefers-color-scheme: <mode>) { :root { ... } }`, with a
|
|
14
|
+
// `color-scheme: <mode>;` declaration prepended
|
|
15
|
+
// customPropsGroups, mergeCustomProps and pxToRem apply per mode, exactly as
|
|
16
|
+
// in the single-source build. addLayer (if set) wraps the whole composed
|
|
17
|
+
// result in a single `@layer`, rather than one per mode.
|
|
18
|
+
//
|
|
19
|
+
// JSON tokens (if jsonBuildPath is set) are produced per mode: one set of
|
|
20
|
+
// files per mode, with `-<mode>` appended to each filename (see
|
|
21
|
+
// buildJsonFiles() in formats/json.mjs), e.g. "tokens-light.jsonc" /
|
|
22
|
+
// "tokens-dark.jsonc", or "size-light.jsonc" / "size-dark.jsonc" in
|
|
23
|
+
// multi-file mode (jsonDestFile: null).
|
|
24
|
+
|
|
25
|
+
import StyleDictionary from 'style-dictionary';
|
|
26
|
+
import * as path from 'node:path';
|
|
27
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
28
|
+
import { CSS_TRANSFORMS, JSON_TRANSFORMS } from './platforms.mjs';
|
|
29
|
+
import { customPropsCount } from './formats/css.mjs';
|
|
30
|
+
import { buildJsonFiles, collectConcreteFilePaths } from './formats/json.mjs';
|
|
31
|
+
import { loadExistingCustomPropsScoped } from './merge-css.mjs';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {object} opts
|
|
35
|
+
* @param {Record<string,string[]>} opts.sourceModes Resolved source paths per mode (see config.mjs)
|
|
36
|
+
* @param {string} opts.baseMode Mode whose block stays a top-level `:root { ... }` rule
|
|
37
|
+
* @param {string} opts.buildPath Absolute path for CSS output directory
|
|
38
|
+
* @param {string} opts.destFile CSS output filename
|
|
39
|
+
* @param {string|null} opts.jsonBuildPath Absolute path for JSON output (null = disabled)
|
|
40
|
+
* @param {string|null} opts.jsonDestFile Base name for aggregated JSON file; null = one file per source
|
|
41
|
+
* @param {'json'|'jsonc'} opts.jsonFormat Output format for JSON files
|
|
42
|
+
* @param {'keep'|'calc'|'resolve'} opts.jsonExpression How to handle math expressions in dimension tokens
|
|
43
|
+
* @param {{name:string,prefixes:string[]}[]} opts.customPropsGroups
|
|
44
|
+
* @param {boolean} opts.pxToRem
|
|
45
|
+
* @param {string|null} opts.addLayer
|
|
46
|
+
* @param {boolean} opts.mergeCustomProps
|
|
47
|
+
* @returns {Promise<{cssDestPath: string, totalCustomProps: number, jsonFilesByMode: Record<string, object[]>}>}
|
|
48
|
+
*/
|
|
49
|
+
export const buildSourceModes = async ({
|
|
50
|
+
sourceModes,
|
|
51
|
+
baseMode,
|
|
52
|
+
buildPath,
|
|
53
|
+
destFile,
|
|
54
|
+
jsonBuildPath,
|
|
55
|
+
jsonDestFile,
|
|
56
|
+
jsonFormat,
|
|
57
|
+
jsonExpression,
|
|
58
|
+
customPropsGroups,
|
|
59
|
+
pxToRem,
|
|
60
|
+
addLayer,
|
|
61
|
+
mergeCustomProps,
|
|
62
|
+
}) => {
|
|
63
|
+
const modeNames = Object.keys(sourceModes);
|
|
64
|
+
|
|
65
|
+
// Read the pre-existing destFile (if any) BEFORE any Style Dictionary
|
|
66
|
+
// instance runs, since the first one to write would overwrite it.
|
|
67
|
+
if (mergeCustomProps) {
|
|
68
|
+
loadExistingCustomPropsScoped(path.join(buildPath, destFile), baseMode);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const cssTransforms = pxToRem
|
|
72
|
+
? CSS_TRANSFORMS
|
|
73
|
+
: CSS_TRANSFORMS.filter((name) => name !== 'size/pxToRem-smart');
|
|
74
|
+
|
|
75
|
+
/** @type {Record<string,string>} */
|
|
76
|
+
const modeBlocks = {};
|
|
77
|
+
let totalCustomProps = 0;
|
|
78
|
+
/** @type {Record<string, object[]>} */
|
|
79
|
+
const jsonFilesByMode = {};
|
|
80
|
+
|
|
81
|
+
for (const mode of modeNames) {
|
|
82
|
+
const sd = new StyleDictionary({
|
|
83
|
+
source: sourceModes[mode],
|
|
84
|
+
log: { verbosity: 'verbose' },
|
|
85
|
+
platforms: {
|
|
86
|
+
css: {
|
|
87
|
+
transforms: cssTransforms,
|
|
88
|
+
files: [
|
|
89
|
+
{
|
|
90
|
+
destination: destFile,
|
|
91
|
+
format: 'css/variables-sorted',
|
|
92
|
+
options: {
|
|
93
|
+
outputReferences: true,
|
|
94
|
+
customPropsGroups,
|
|
95
|
+
mode,
|
|
96
|
+
colorScheme: mode === baseMode ? modeNames.join(' ') : mode,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// formatPlatform() runs the format function without writing to disk —
|
|
105
|
+
// the composed multi-mode file is written once, at the end, below.
|
|
106
|
+
const [formatted] = await sd.formatPlatform('css');
|
|
107
|
+
modeBlocks[mode] = /** @type {string} */ (formatted.output);
|
|
108
|
+
// customPropsCount is a live binding, updated synchronously by the
|
|
109
|
+
// format function called above (see formats/css.mjs).
|
|
110
|
+
totalCustomProps += customPropsCount;
|
|
111
|
+
|
|
112
|
+
if (jsonBuildPath) {
|
|
113
|
+
const concreteFilePaths = jsonDestFile ? [] : await collectConcreteFilePaths(sd);
|
|
114
|
+
const files = buildJsonFiles(concreteFilePaths, jsonDestFile, jsonFormat, jsonExpression, `-${mode}`);
|
|
115
|
+
|
|
116
|
+
const jsonSd = new StyleDictionary({
|
|
117
|
+
source: sourceModes[mode],
|
|
118
|
+
log: { verbosity: 'silent' },
|
|
119
|
+
platforms: {
|
|
120
|
+
json: {
|
|
121
|
+
buildPath: jsonBuildPath + '/',
|
|
122
|
+
transforms: JSON_TRANSFORMS,
|
|
123
|
+
files,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
await jsonSd.buildAllPlatforms();
|
|
128
|
+
jsonFilesByMode[mode] = files;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Compose the final CSS ────────────────────────────────────────────────
|
|
133
|
+
const otherModes = modeNames.filter((mode) => mode !== baseMode);
|
|
134
|
+
|
|
135
|
+
let composed = modeBlocks[baseMode];
|
|
136
|
+
for (const mode of otherModes) {
|
|
137
|
+
composed += `\n@media (prefers-color-scheme: ${mode}) {\n${modeBlocks[mode]}}\n`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// addLayer wraps the whole composed result once, rather than once per
|
|
141
|
+
// mode. Indentation is left to stylelint's fix step (run right after the
|
|
142
|
+
// build), same as the single-source build.
|
|
143
|
+
const finalCss = addLayer
|
|
144
|
+
? `@layer ${addLayer} {\n\n${composed}}\n`
|
|
145
|
+
: composed;
|
|
146
|
+
|
|
147
|
+
await mkdir(buildPath, { recursive: true });
|
|
148
|
+
const cssDestPath = path.join(buildPath, destFile);
|
|
149
|
+
await writeFile(cssDestPath, finalCss);
|
|
150
|
+
|
|
151
|
+
return { cssDestPath, totalCustomProps, jsonFilesByMode };
|
|
152
|
+
};
|
package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/config.mjs
RENAMED
|
@@ -110,5 +110,39 @@ export const customPropsGroups = Array.isArray(buildConfig.customPropsGroups)
|
|
|
110
110
|
// path.join() — it may corrupt the pattern syntax. Use template literals:
|
|
111
111
|
// OK: `${minimo_path}/**/*.{json,mjs}`
|
|
112
112
|
// NO: path.join(minimo_path, '/**/*.{json,mjs}')
|
|
113
|
+
//
|
|
114
|
+
// sourceModes: alternative to `source`, for a light/dark (or other) custom
|
|
115
|
+
// properties split — see build-tokens-src/build-source-modes.mjs. When set
|
|
116
|
+
// (buildConfig.sourceModes is a non-null object), `source` is ignored and
|
|
117
|
+
// each mode's own array of source patterns is resolved the same way.
|
|
113
118
|
// ---------------------------------------------------------------------------
|
|
114
|
-
|
|
119
|
+
const sourceModesRaw = buildConfig.sourceModes ?? null;
|
|
120
|
+
|
|
121
|
+
export const sourceModes = sourceModesRaw
|
|
122
|
+
? Object.fromEntries(
|
|
123
|
+
Object.entries(sourceModesRaw).map(([mode, modeSource]) => [
|
|
124
|
+
mode,
|
|
125
|
+
resolveSourcePaths(modeSource, configDir),
|
|
126
|
+
])
|
|
127
|
+
)
|
|
128
|
+
: null;
|
|
129
|
+
|
|
130
|
+
// sourceModesBase: the mode whose declarations are written to the top-level
|
|
131
|
+
// `:root { ... }` block (the others are nested under
|
|
132
|
+
// `@media (prefers-color-scheme: <mode>) { ... }`). Default: the first key
|
|
133
|
+
// of sourceModes (insertion order), e.g. 'light'. Ignored when sourceModes
|
|
134
|
+
// is not set.
|
|
135
|
+
export const sourceModesBase = sourceModes
|
|
136
|
+
? (Object.prototype.hasOwnProperty.call(sourceModes, buildConfig.sourceModesBase)
|
|
137
|
+
? buildConfig.sourceModesBase
|
|
138
|
+
: Object.keys(sourceModes)[0])
|
|
139
|
+
: null;
|
|
140
|
+
|
|
141
|
+
if (sourceModes && buildConfig.sourceModesBase && sourceModesBase !== buildConfig.sourceModesBase) {
|
|
142
|
+
// eslint-disable-next-line no-console
|
|
143
|
+
console.warn(
|
|
144
|
+
`[build-tokens] config: sourceModesBase "${buildConfig.sourceModesBase}" is not a key of sourceModes — falling back to "${sourceModesBase}"`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const source = sourceModes ? null : resolveSourcePaths(buildConfig.source, configDir);
|
package/{design-tokens/utilities → dev-tools/design-tokens-builder}/build-tokens-src/formats/css.mjs
RENAMED
|
@@ -252,11 +252,14 @@ StyleDictionary.registerFormat({
|
|
|
252
252
|
|
|
253
253
|
// Build a name → declaration-tail map from the declarations generated
|
|
254
254
|
// above, then merge with any pre-existing custom properties loaded via
|
|
255
|
-
// loadExistingCustomProps() (
|
|
256
|
-
// (including trailing comments)
|
|
257
|
-
// pre-existing-only properties are kept.
|
|
255
|
+
// loadExistingCustomProps() / loadExistingCustomPropsScoped() (see
|
|
256
|
+
// ../merge-css.mjs). Pre-existing values (including trailing comments)
|
|
257
|
+
// take priority when both exist; pre-existing-only properties are kept.
|
|
258
|
+
// options.mode (sourceModes build only, see ../build-source-modes.mjs)
|
|
259
|
+
// scopes the merge to that mode, so same-named props with different
|
|
260
|
+
// values across modes are never mixed up.
|
|
258
261
|
const generatedProps = parseCustomProps(lines.join('\n'));
|
|
259
|
-
const finalProps = mergeCustomProps(generatedProps);
|
|
262
|
+
const finalProps = mergeCustomProps(generatedProps, options.mode ?? null);
|
|
260
263
|
|
|
261
264
|
// Final output is always sorted alphabetically ascending by property
|
|
262
265
|
// name, regardless of merge — this also keeps pre-existing-only
|
|
@@ -295,6 +298,15 @@ StyleDictionary.registerFormat({
|
|
|
295
298
|
// first hyphen-separated segment (e.g. all `btn-*` together), to visually
|
|
296
299
|
// group related custom properties in the generated file.
|
|
297
300
|
const outLines = [];
|
|
301
|
+
|
|
302
|
+
// options.colorScheme (sourceModes build only, see ../build-source-modes.mjs):
|
|
303
|
+
// prepends a `color-scheme: <value>;` declaration, e.g. "light dark" for
|
|
304
|
+
// the base mode's block, or the mode's own name (e.g. "dark") for a
|
|
305
|
+
// block nested inside its @media (prefers-color-scheme: <mode>) rule.
|
|
306
|
+
if (options.colorScheme) {
|
|
307
|
+
outLines.push(` color-scheme: ${options.colorScheme};`, '');
|
|
308
|
+
}
|
|
309
|
+
|
|
298
310
|
let prevPrefix = null;
|
|
299
311
|
const appendBlock = (props) => {
|
|
300
312
|
for (const [name, tail] of props) {
|