@massimo-cassandro/minimo 1.36.2 → 1.38.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/dev-tools/design-tokens-builder/README.md +22 -0
- package/dev-tools/design-tokens-builder/build-tokens-src/build-source-modes.mjs +57 -14
- package/dev-tools/design-tokens-builder/build-tokens-src/config.mjs +42 -3
- package/dev-tools/design-tokens-builder/build-tokens-src/formats/css.mjs +259 -198
- package/dev-tools/design-tokens-builder/build-tokens-src/light-dark.mjs +126 -0
- package/dev-tools/design-tokens-builder/build-tokens-src/merge-css.mjs +25 -14
- package/dev-tools/design-tokens-builder/build-tokens-src/platforms.mjs +7 -2
- package/dev-tools/design-tokens-builder/build-tokens-src/source-prefixes.mjs +129 -0
- package/dev-tools/design-tokens-builder/build-tokens-src/transforms.mjs +20 -0
- package/dev-tools/design-tokens-builder/build-tokens.mjs +14 -0
- package/dev-tools/design-tokens-builder/check-unresolved-custom-props.mjs +13 -8
- package/dev-tools/design-tokens-builder/tokens-config-sample.mjs +55 -4
- package/dev-tools/starter-kit/starter-install.sh +1 -1
- package/package.json +1 -1
|
@@ -67,6 +67,12 @@ const matchBraces = (text, openIndex) => {
|
|
|
67
67
|
return -1;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
// Escapes a string for literal use inside a RegExp — needed because
|
|
71
|
+
// customPropsSelector (see config.mjs) can contain regex-special characters,
|
|
72
|
+
// e.g. ':where(html)'.
|
|
73
|
+
/** @param {string} str */
|
|
74
|
+
const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
75
|
+
|
|
70
76
|
// Finds every @media (prefers-color-scheme: <mode>) { ... } block in a
|
|
71
77
|
// previously generated sourceModes CSS destFile, with absolute character
|
|
72
78
|
// offsets into cssText. Used to split the file into per-mode scopes (see
|
|
@@ -93,22 +99,25 @@ export const findMediaModeBlocks = (cssText) => {
|
|
|
93
99
|
|
|
94
100
|
// Splits a previously generated sourceModes CSS destFile into per-mode
|
|
95
101
|
// scopes (see build-source-modes.mjs for the shape of the generated file):
|
|
96
|
-
// - the base scope is the top-level
|
|
97
|
-
// NOT nested inside a `@media (prefers-color-scheme: ...)` rule
|
|
98
|
-
// - every other scope is the
|
|
99
|
-
// `@media (prefers-color-scheme: <mode>) { ... }` rule
|
|
102
|
+
// - the base scope is the top-level `<selector> { ... }` block, i.e. the
|
|
103
|
+
// one NOT nested inside a `@media (prefers-color-scheme: ...)` rule
|
|
104
|
+
// - every other scope is the `<selector> { ... }` block nested inside its
|
|
105
|
+
// own `@media (prefers-color-scheme: <mode>) { ... }` rule
|
|
100
106
|
// @param {string} cssText
|
|
107
|
+
// @param {string} [selector] the customPropsSelector the file was generated
|
|
108
|
+
// with (default: ':root' — see config.mjs)
|
|
101
109
|
// @returns {Record<string,string>} mode → CSS body text (base mode keyed as '__base__')
|
|
102
|
-
const splitModeScopes = (cssText) => {
|
|
110
|
+
const splitModeScopes = (cssText, selector = ':root') => {
|
|
103
111
|
/** @type {Record<string,string>} */
|
|
104
112
|
const scopes = {};
|
|
113
|
+
const selectorRe = new RegExp(`${escapeRegExp(selector)}\\s*\\{`);
|
|
105
114
|
|
|
106
115
|
// 1. Every @media (prefers-color-scheme: <mode>) { ... } block, and the
|
|
107
|
-
//
|
|
116
|
+
// <selector> { ... } body nested inside each one.
|
|
108
117
|
const mediaBlocks = findMediaModeBlocks(cssText);
|
|
109
118
|
for (const { mode, start, end } of mediaBlocks) {
|
|
110
119
|
const body = cssText.slice(start + 1, end);
|
|
111
|
-
const rootMatch =
|
|
120
|
+
const rootMatch = selectorRe.exec(body);
|
|
112
121
|
if (rootMatch) {
|
|
113
122
|
const rootOpen = rootMatch.index + rootMatch[0].length - 1;
|
|
114
123
|
const rootClose = matchBraces(body, rootOpen);
|
|
@@ -118,9 +127,9 @@ const splitModeScopes = (cssText) => {
|
|
|
118
127
|
}
|
|
119
128
|
}
|
|
120
129
|
|
|
121
|
-
// 2. The base
|
|
122
|
-
// the media blocks found above.
|
|
123
|
-
const ROOT_RE =
|
|
130
|
+
// 2. The base <selector> { ... } block: the first one not nested inside
|
|
131
|
+
// any of the media blocks found above.
|
|
132
|
+
const ROOT_RE = new RegExp(`${escapeRegExp(selector)}\\s*\\{`, 'g');
|
|
124
133
|
for (const match of cssText.matchAll(ROOT_RE)) {
|
|
125
134
|
const openIdx = match.index + match[0].length - 1;
|
|
126
135
|
const insideMedia = mediaBlocks.some((b) => openIdx > b.start && openIdx < b.end);
|
|
@@ -188,22 +197,24 @@ export const loadExistingCustomProps = (absDestPath, sources = true) => {
|
|
|
188
197
|
/**
|
|
189
198
|
* sourceModes equivalent of loadExistingCustomProps(): reads the CSS
|
|
190
199
|
* destination file (if it exists) and stores a per-mode name → value map,
|
|
191
|
-
* scoped to each mode's own
|
|
200
|
+
* scoped to each mode's own `<selector> { ... }` block (see splitModeScopes()).
|
|
192
201
|
* @param {string} absDestPath absolute path of the CSS file to read
|
|
193
202
|
* @param {string} baseMode mode whose declarations live in the top-level,
|
|
194
|
-
* non-nested
|
|
203
|
+
* non-nested `<selector> { ... }` block (no default — always passed explicitly
|
|
195
204
|
* by build-source-modes.mjs, from the resolved sourceModesBase)
|
|
196
205
|
* @param {true|(string|RegExp)[]} [sources] `true` merges every source, an
|
|
197
206
|
* array only the token source files matching one of its entries (default: true)
|
|
207
|
+
* @param {string} [selector] the customPropsSelector the pre-existing file was
|
|
208
|
+
* generated with (default: ':root' — see config.mjs)
|
|
198
209
|
* @returns {Record<string, Record<string,string>>|null}
|
|
199
210
|
*/
|
|
200
|
-
export const loadExistingCustomPropsScoped = (absDestPath, baseMode, sources = true) => {
|
|
211
|
+
export const loadExistingCustomPropsScoped = (absDestPath, baseMode, sources = true, selector = ':root') => {
|
|
201
212
|
mergeSources = sources;
|
|
202
213
|
if (!existsSync(absDestPath)) {
|
|
203
214
|
scopedExistingCustomProps = null;
|
|
204
215
|
return scopedExistingCustomProps;
|
|
205
216
|
}
|
|
206
|
-
const scopes = splitModeScopes(readFileSync(absDestPath, 'utf8'));
|
|
217
|
+
const scopes = splitModeScopes(readFileSync(absDestPath, 'utf8'), selector);
|
|
207
218
|
scopedExistingCustomProps = {};
|
|
208
219
|
if (scopes.__base__ !== undefined) {
|
|
209
220
|
scopedExistingCustomProps[baseMode] = parseCustomProps(scopes.__base__);
|
|
@@ -11,9 +11,10 @@ import { buildJsonFiles } from './formats/json.mjs';
|
|
|
11
11
|
// Transforms applied to the CSS platform.
|
|
12
12
|
// Exported so other scripts (e.g. check-unresolved-custom-props.mjs) can
|
|
13
13
|
// build a Style Dictionary instance with matching transformed token names.
|
|
14
|
+
// name/kebab-prefixed = name/kebab + per-source prefix (see source-prefixes.mjs).
|
|
14
15
|
export const CSS_TRANSFORMS = [
|
|
15
16
|
'attribute/cti',
|
|
16
|
-
'name/kebab',
|
|
17
|
+
'name/kebab-prefixed',
|
|
17
18
|
'time/seconds',
|
|
18
19
|
'asset/url',
|
|
19
20
|
'size/pxToRem-smart',
|
|
@@ -27,7 +28,8 @@ export const CSS_TRANSFORMS = [
|
|
|
27
28
|
// Transforms applied to the JSON platform.
|
|
28
29
|
// Kept minimal on purpose: consuming tools expect original values (e.g. "16px"
|
|
29
30
|
// not "1rem") and alias references ({...}) must be preserved to maintain
|
|
30
|
-
// token links.
|
|
31
|
+
// token links. Source prefixes are not applied here (plain name/kebab): the
|
|
32
|
+
// JSON output keeps the original token tree.
|
|
31
33
|
// Exported so build-source-modes.mjs can build a matching json platform per
|
|
32
34
|
// sourceModes mode.
|
|
33
35
|
export const JSON_TRANSFORMS = [
|
|
@@ -50,6 +52,7 @@ export const JSON_TRANSFORMS = [
|
|
|
50
52
|
* @param {{name: string, prefixes: string[]}[]} opts.customPropsGroups Named groups of name prefixes, moved to the top of the CSS output in list order.
|
|
51
53
|
* @param {boolean} opts.pxToRem If false, skips the px→rem transform on the CSS platform. Default: true.
|
|
52
54
|
* @param {string|null} opts.addLayer Wraps the generated custom properties inside `@layer <addLayer> { ... }`. null = no layer.
|
|
55
|
+
* @param {string} opts.selector CSS selector wrapping the generated block (default: ':root').
|
|
53
56
|
* @returns {Record<string, PlatformConfig>} platforms object ready for Style Dictionary config
|
|
54
57
|
*/
|
|
55
58
|
export const buildPlatforms = ({
|
|
@@ -63,6 +66,7 @@ export const buildPlatforms = ({
|
|
|
63
66
|
customPropsGroups = [],
|
|
64
67
|
pxToRem = true,
|
|
65
68
|
addLayer = null,
|
|
69
|
+
selector = ':root',
|
|
66
70
|
}) => {
|
|
67
71
|
const cssTransforms = pxToRem
|
|
68
72
|
? CSS_TRANSFORMS
|
|
@@ -80,6 +84,7 @@ export const buildPlatforms = ({
|
|
|
80
84
|
options: {
|
|
81
85
|
outputReferences: true,
|
|
82
86
|
showFileHeader: true,
|
|
87
|
+
selector,
|
|
83
88
|
customPropsGroups,
|
|
84
89
|
addLayer,
|
|
85
90
|
},
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// build-tokens-src/source-prefixes.mjs
|
|
2
|
+
// Supports per-source custom property prefixes: an entry of `source` (or of a
|
|
3
|
+
// `sourceModes` mode) can be an object `{ src, prefix }` instead of a plain
|
|
4
|
+
// string, e.g.
|
|
5
|
+
//
|
|
6
|
+
// { src: 'node_modules/open-props/open-props.style-dictionary-tokens.json', prefix: 'op' }
|
|
7
|
+
//
|
|
8
|
+
// so that every token defined by those files is rendered as `--op-<name>`.
|
|
9
|
+
//
|
|
10
|
+
// How it works: the prefix is applied to the token NAME only (see the
|
|
11
|
+
// name/kebab-prefixed transform in transforms.mjs), never to its path.
|
|
12
|
+
// {references} are resolved through the token path and rendered with the
|
|
13
|
+
// already-prefixed name, so a token of another source referencing e.g.
|
|
14
|
+
// {gray.5} becomes `var(--op-gray-5)` with no change to the token files. The
|
|
15
|
+
// JSON output keeps the original (unprefixed) tree structure.
|
|
16
|
+
//
|
|
17
|
+
// The name transform only knows the concrete file a token comes from
|
|
18
|
+
// (token.filePath), while `src` may contain glob patterns: registerSourcePrefixes()
|
|
19
|
+
// expands them (through Style Dictionary itself, so the result matches
|
|
20
|
+
// token.filePath exactly) into a file path -> prefix map.
|
|
21
|
+
|
|
22
|
+
import StyleDictionary from 'style-dictionary';
|
|
23
|
+
import { LEGACY_TOKENS_PARSER_NAME } from './legacy-tokens-parser.mjs';
|
|
24
|
+
import { resolveSourcePaths } from './resolve-source-paths.mjs';
|
|
25
|
+
import { collectConcreteFilePaths } from './formats/json.mjs';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} PrefixedSource
|
|
29
|
+
* @property {string[]} src Source paths/globs, already resolved (see resolveSourcePaths)
|
|
30
|
+
* @property {string} prefix Normalised prefix, without leading `--` or trailing `-`
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** @type {Map<string, string>} */
|
|
34
|
+
let prefixByFile = new Map();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Normalises a user-provided prefix: 'op', 'op-' and '--op-' all become 'op'.
|
|
38
|
+
* @param {unknown} prefix
|
|
39
|
+
* @returns {string}
|
|
40
|
+
* @throws {Error} if the result is not a valid custom property name segment
|
|
41
|
+
*/
|
|
42
|
+
export const normalizePrefix = (prefix) => {
|
|
43
|
+
const value = String(prefix).trim().replace(/^-+/, '').replace(/-+$/, '');
|
|
44
|
+
if (!/^[a-z_][\w-]*$/i.test(value)) {
|
|
45
|
+
throw new Error(`[build-tokens] config: invalid source prefix "${prefix}"`);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Splits a `source` (or `sourceModes[mode]`) array, whose entries can be
|
|
52
|
+
* strings or `{ src, prefix }` objects, into the flat list of patterns to pass
|
|
53
|
+
* to Style Dictionary and the list of the prefixed entries. Patterns are
|
|
54
|
+
* resolved relative to baseDir (see resolveSourcePaths).
|
|
55
|
+
* @param {(string|{src: string|string[], prefix?: string})[]} entries
|
|
56
|
+
* @param {string} baseDir
|
|
57
|
+
* @returns {{patterns: string[], prefixed: PrefixedSource[]}}
|
|
58
|
+
*/
|
|
59
|
+
export const splitSourceEntries = (entries, baseDir) => {
|
|
60
|
+
/** @type {string[]} */
|
|
61
|
+
const patterns = [];
|
|
62
|
+
/** @type {PrefixedSource[]} */
|
|
63
|
+
const prefixed = [];
|
|
64
|
+
|
|
65
|
+
for (const entry of entries ?? []) {
|
|
66
|
+
if (typeof entry === 'string') {
|
|
67
|
+
patterns.push(...resolveSourcePaths([entry], baseDir));
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const isObjectEntry = entry !== null && typeof entry === 'object'
|
|
72
|
+
&& (typeof entry.src === 'string' || Array.isArray(entry.src));
|
|
73
|
+
if (!isObjectEntry) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`[build-tokens] config: invalid source entry ${JSON.stringify(entry)} `
|
|
76
|
+
+ '(expected a string or an object { src, prefix })'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const src = resolveSourcePaths([].concat(entry.src), baseDir);
|
|
81
|
+
patterns.push(...src);
|
|
82
|
+
|
|
83
|
+
if (entry.prefix !== undefined && entry.prefix !== null && entry.prefix !== '') {
|
|
84
|
+
prefixed.push({ src, prefix: normalizePrefix(entry.prefix) });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { patterns, prefixed };
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Expands the `src` patterns of every prefixed entry into concrete file paths
|
|
93
|
+
* and stores the file path -> prefix map used by getSourcePrefix().
|
|
94
|
+
* Must be called after registerLegacyTokensParser() and before any build.
|
|
95
|
+
* @param {PrefixedSource[]} prefixedSources
|
|
96
|
+
* @returns {Promise<void>}
|
|
97
|
+
* @throws {Error} if the same file is given two different prefixes
|
|
98
|
+
*/
|
|
99
|
+
export const registerSourcePrefixes = async (prefixedSources) => {
|
|
100
|
+
/** @type {Map<string, string>} */
|
|
101
|
+
const map = new Map();
|
|
102
|
+
|
|
103
|
+
for (const { src, prefix } of prefixedSources) {
|
|
104
|
+
const sd = new StyleDictionary({
|
|
105
|
+
source: src,
|
|
106
|
+
parsers: [LEGACY_TOKENS_PARSER_NAME],
|
|
107
|
+
log: { verbosity: 'silent' },
|
|
108
|
+
platforms: {},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
for (const filePath of await collectConcreteFilePaths(sd)) {
|
|
112
|
+
const existing = map.get(filePath);
|
|
113
|
+
if (existing !== undefined && existing !== prefix) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`[build-tokens] config: "${filePath}" has two different prefixes ("${existing}" and "${prefix}")`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
map.set(filePath, prefix);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
prefixByFile = map;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {string|undefined} filePath token.filePath
|
|
127
|
+
* @returns {string|undefined} the prefix registered for that file, if any
|
|
128
|
+
*/
|
|
129
|
+
export const getSourcePrefix = (filePath) => (filePath ? prefixByFile.get(filePath) : undefined);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Imported once by build-tokens.mjs before buildAllPlatforms() is called.
|
|
4
4
|
|
|
5
5
|
import StyleDictionary from 'style-dictionary';
|
|
6
|
+
import { getSourcePrefix } from './source-prefixes.mjs';
|
|
6
7
|
|
|
7
8
|
const BASE_FONT_SIZE = 16;
|
|
8
9
|
|
|
@@ -212,3 +213,22 @@ StyleDictionary.registerTransform({
|
|
|
212
213
|
filter: (token) => token.$type === 'typography' || token.type === 'typography',
|
|
213
214
|
transform: (token) => token.$value ?? token.value,
|
|
214
215
|
});
|
|
216
|
+
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// 7. Kebab-case name with per-source prefix
|
|
219
|
+
// Same as the built-in name/kebab, but prepends the prefix registered for the
|
|
220
|
+
// token's source file (`{ src, prefix }` entries of `source`/`sourceModes`, see
|
|
221
|
+
// source-prefixes.mjs), e.g. Open Props' gray.0 with prefix 'op' -> op-gray-0.
|
|
222
|
+
// Only the name changes: the token path is untouched, so {references} keep
|
|
223
|
+
// resolving and are rendered as var(--<prefixed-name>). Files without a
|
|
224
|
+
// prefix get exactly the built-in name/kebab result.
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
StyleDictionary.registerTransform({
|
|
227
|
+
name: 'name/kebab-prefixed',
|
|
228
|
+
type: 'name',
|
|
229
|
+
transform: (token, options) => {
|
|
230
|
+
const kebab = StyleDictionary.hooks.transforms['name/kebab'].transform;
|
|
231
|
+
const prefix = getSourcePrefix(token.filePath);
|
|
232
|
+
return kebab(prefix ? { ...token, path: [prefix, ...token.path] } : token, options);
|
|
233
|
+
},
|
|
234
|
+
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// build-tokens-src/platforms.mjs <- builds the platforms object (css + optional json)
|
|
15
15
|
// build-tokens-src/build-source-modes.mjs <- alternative build path used when sourceModes is set
|
|
16
16
|
// build-tokens-src/merge-css.mjs <- supports the mergeCustomProps option (flat and per-mode)
|
|
17
|
+
// build-tokens-src/source-prefixes.mjs <- supports `{ src, prefix }` entries in source/sourceModes
|
|
17
18
|
|
|
18
19
|
import StyleDictionary from 'style-dictionary';
|
|
19
20
|
import * as path from 'node:path';
|
|
@@ -35,10 +36,13 @@ import {
|
|
|
35
36
|
source,
|
|
36
37
|
sourceModes,
|
|
37
38
|
sourceModesBase,
|
|
39
|
+
prefixedSources,
|
|
38
40
|
mergeCustomProps,
|
|
39
41
|
customPropsGroups,
|
|
40
42
|
pxToRem,
|
|
41
43
|
addLayer,
|
|
44
|
+
useLightDarkFunc,
|
|
45
|
+
customPropsSelector,
|
|
42
46
|
} from './build-tokens-src/config.mjs';
|
|
43
47
|
|
|
44
48
|
// ── 2. Custom transforms ─────────────────────────────────────────────────────
|
|
@@ -50,6 +54,13 @@ import './build-tokens-src/transforms.mjs';
|
|
|
50
54
|
import { LEGACY_TOKENS_PARSER_NAME, registerLegacyTokensParser } from './build-tokens-src/legacy-tokens-parser.mjs';
|
|
51
55
|
registerLegacyTokensParser();
|
|
52
56
|
|
|
57
|
+
// ── 2c. Per-source custom property prefixes ──────────────────────────────────
|
|
58
|
+
// Expands the `{ src, prefix }` entries of source/sourceModes into the file ->
|
|
59
|
+
// prefix map read by the name/kebab-prefixed transform (needs the legacy
|
|
60
|
+
// parser above, to load legacy .json sources) — see source-prefixes.mjs.
|
|
61
|
+
import { registerSourcePrefixes } from './build-tokens-src/source-prefixes.mjs';
|
|
62
|
+
await registerSourcePrefixes(prefixedSources);
|
|
63
|
+
|
|
53
64
|
// ── 3. CSS format ─────────────────────────────────────────────────────────────
|
|
54
65
|
// customPropsCount is updated by the format at render time
|
|
55
66
|
// loadExistingCustomProps supports the mergeCustomProps option (see below)
|
|
@@ -105,6 +116,8 @@ if (sourceModes) {
|
|
|
105
116
|
pxToRem,
|
|
106
117
|
addLayer,
|
|
107
118
|
mergeCustomProps,
|
|
119
|
+
useLightDarkFunc,
|
|
120
|
+
customPropsSelector,
|
|
108
121
|
});
|
|
109
122
|
|
|
110
123
|
} else {
|
|
@@ -157,6 +170,7 @@ if (sourceModes) {
|
|
|
157
170
|
customPropsGroups,
|
|
158
171
|
pxToRem,
|
|
159
172
|
addLayer,
|
|
173
|
+
selector: customPropsSelector,
|
|
160
174
|
}),
|
|
161
175
|
});
|
|
162
176
|
|
|
@@ -24,7 +24,7 @@ import { styleText } from 'node:util';
|
|
|
24
24
|
import StyleDictionary from 'style-dictionary';
|
|
25
25
|
import './build-tokens-src/transforms.mjs';
|
|
26
26
|
import { CSS_TRANSFORMS } from './build-tokens-src/platforms.mjs';
|
|
27
|
-
import {
|
|
27
|
+
import { splitSourceEntries, registerSourcePrefixes } from './build-tokens-src/source-prefixes.mjs';
|
|
28
28
|
import { findMediaModeBlocks } from './build-tokens-src/merge-css.mjs';
|
|
29
29
|
import { LEGACY_TOKENS_PARSER_NAME, registerLegacyTokensParser } from './build-tokens-src/legacy-tokens-parser.mjs';
|
|
30
30
|
|
|
@@ -202,17 +202,22 @@ async function run() {
|
|
|
202
202
|
/** @type {Map<string, Map<string, string>>} */
|
|
203
203
|
const sourceFileByPropByMode = new Map();
|
|
204
204
|
|
|
205
|
-
//
|
|
205
|
+
// includeEntries: base-mode sources, passed as `include` for non-base
|
|
206
206
|
// modes so cross-mode references resolve (same as build-source-modes.mjs).
|
|
207
|
-
/** @param {
|
|
208
|
-
|
|
207
|
+
/** @param {Parameters<typeof splitSourceEntries>[0]} sourceEntries @param {Parameters<typeof splitSourceEntries>[0]} [includeEntries] @returns {Promise<Map<string,string>>} */
|
|
208
|
+
// Entries can be strings or `{ src, prefix }` objects (see
|
|
209
|
+
// build-tokens-src/source-prefixes.mjs): the prefixes are registered
|
|
210
|
+
// before the transform pipeline runs, so token names match the CSS.
|
|
211
|
+
const resolveSourceFileMap = async (sourceEntries, includeEntries = []) => {
|
|
209
212
|
/** @type {Map<string, string>} */
|
|
210
213
|
const map = new Map();
|
|
211
|
-
const resolvedPaths =
|
|
214
|
+
const { patterns: resolvedPaths, prefixed } = splitSourceEntries(sourceEntries, configDir);
|
|
215
|
+
const { patterns: includePaths, prefixed: includePrefixed } = splitSourceEntries(includeEntries, configDir);
|
|
212
216
|
if (!resolvedPaths.length) return map;
|
|
213
217
|
try {
|
|
218
|
+
await registerSourcePrefixes([...prefixed, ...includePrefixed]);
|
|
214
219
|
const sd = new StyleDictionary({
|
|
215
|
-
include:
|
|
220
|
+
include: includePaths,
|
|
216
221
|
source: resolvedPaths,
|
|
217
222
|
parsers: [LEGACY_TOKENS_PARSER_NAME],
|
|
218
223
|
log: { verbosity: 'silent' },
|
|
@@ -230,8 +235,8 @@ async function run() {
|
|
|
230
235
|
|
|
231
236
|
if (checkUnused && config.sourceModes) {
|
|
232
237
|
for (const [mode, modeSource] of Object.entries(config.sourceModes)) {
|
|
233
|
-
const
|
|
234
|
-
sourceFileByPropByMode.set(mode, await resolveSourceFileMap(modeSource,
|
|
238
|
+
const includeEntries = mode === sourceModesBase ? [] : config.sourceModes[sourceModesBase];
|
|
239
|
+
sourceFileByPropByMode.set(mode, await resolveSourceFileMap(modeSource, includeEntries));
|
|
235
240
|
}
|
|
236
241
|
} else if (checkUnused && Array.isArray(config.source) && config.source.length) {
|
|
237
242
|
const map = await resolveSourceFileMap(config.source);
|
|
@@ -51,15 +51,24 @@ const config = {
|
|
|
51
51
|
// the pattern syntax. Use template literals instead:
|
|
52
52
|
// OK: `${minimo_path}/**/*.{json,mjs}`
|
|
53
53
|
// NO: path.join(minimo_path, '/**/*.{json,mjs}')
|
|
54
|
+
//
|
|
55
|
+
// An entry can also be an object `{ src, prefix }` (src: string or array of
|
|
56
|
+
// strings/globs), to render the custom properties of those files with a
|
|
57
|
+
// prefix: e.g. { src: '...open-props...json', prefix: 'op' } turns Open
|
|
58
|
+
// Props' gray.0 into --op-gray-0. Only the property name changes:
|
|
59
|
+
// {references} to those tokens are rendered as var(--op-gray-0), and the JSON
|
|
60
|
+
// output stays unprefixed. 'op', 'op-' and '--op-' are equivalent. The same
|
|
61
|
+
// syntax works inside sourceModes. Two different prefixes for the same file
|
|
62
|
+
// make the build fail.
|
|
54
63
|
source: [
|
|
55
64
|
`${minimo_path}/design-tokens/_src/**/*.tokens.{mjs,jsonc}`, // main tokens
|
|
56
65
|
`${minimo_path}/src/**/*.tokens.{mjs,jsonc}`, // components tokens
|
|
57
66
|
|
|
58
67
|
// optional extra token source (in this example, openProps, https://open-props.style/)
|
|
59
|
-
// `${node_modules_path}/open-props/open-props.style-dictionary-tokens.json`,
|
|
68
|
+
// { src: `${node_modules_path}/open-props/open-props.style-dictionary-tokens.json`, prefix: 'op' },
|
|
60
69
|
|
|
61
70
|
// your project tokens
|
|
62
|
-
'./project-tokens/*.{js,mjs,jsonc,json}',
|
|
71
|
+
{src: './project-tokens/*.{js,mjs,jsonc,json}', prefix: 'my-project'}
|
|
63
72
|
],
|
|
64
73
|
|
|
65
74
|
// Optional light/dark (or other) custom properties split — alternative to
|
|
@@ -101,12 +110,54 @@ const config = {
|
|
|
101
110
|
// },
|
|
102
111
|
sourceModes: null,
|
|
103
112
|
|
|
113
|
+
// With sourceModes set, and only if it defines both a `light` and a `dark`
|
|
114
|
+
// key: custom properties present in BOTH — matched by name — are written
|
|
115
|
+
// once as `--name: light-dark(<light-value>, <dark-value>);`, instead of
|
|
116
|
+
// being split across the base mode's top-level block and a
|
|
117
|
+
// `@media (prefers-color-scheme: dark) { ... }` rule.
|
|
118
|
+
//
|
|
119
|
+
// Unaffected:
|
|
120
|
+
// - any other key in sourceModes besides light/dark (e.g. a third,
|
|
121
|
+
// custom mode) — always stays on its own
|
|
122
|
+
// `@media (prefers-color-scheme: <mode>) { ... }` rule
|
|
123
|
+
// - a property present in only one of `light`/`dark` — stays in its own
|
|
124
|
+
// mode's block (still behind `@media`, unless that mode happens to be
|
|
125
|
+
// sourceModesBase)
|
|
126
|
+
//
|
|
127
|
+
// Whenever this option is active (sourceModes has both a light and a dark
|
|
128
|
+
// key), the base mode's `color-scheme` declaration is always normalised to
|
|
129
|
+
// read "light dark [...any other mode]", with light before dark — matching
|
|
130
|
+
// light-dark()'s own (light-value, dark-value) argument order — regardless
|
|
131
|
+
// of the order sourceModes keys were declared in, or of which of the two
|
|
132
|
+
// is configured as sourceModesBase below.
|
|
133
|
+
//
|
|
134
|
+
// Interaction with mergeCustomProps: no special handling needed — the
|
|
135
|
+
// per-mode merge with pre-existing custom properties always runs first;
|
|
136
|
+
// this option then reconciles whatever value ends up in each mode
|
|
137
|
+
// (freshly generated or preserved from a pre-existing file) into
|
|
138
|
+
// light-dark(), matched by name. This also means that re-running a build
|
|
139
|
+
// with this option newly turned on, over a destFile previously generated
|
|
140
|
+
// with it off (classic `@media (prefers-color-scheme: dark)` split) and
|
|
141
|
+
// mergeCustomProps: true, converts the preserved values into light-dark()
|
|
142
|
+
// calls too — as with any mergeCustomProps case, a resulting mismatch is
|
|
143
|
+
// left for you to fix.
|
|
144
|
+
//
|
|
145
|
+
// Default: true. Ignored when sourceModes is not set (null/undefined).
|
|
146
|
+
useLightDarkFunc: true,
|
|
147
|
+
|
|
104
148
|
// With sourceModes set: the mode whose declarations go in the top-level
|
|
105
149
|
// `:root { ... }` block, instead of nested inside its own
|
|
106
150
|
// `@media (prefers-color-scheme: <mode>) { ... }` rule (default: the first
|
|
107
151
|
// key of sourceModes, e.g. 'light'). Ignored when sourceModes is not set.
|
|
108
152
|
sourceModesBase: 'light',
|
|
109
153
|
|
|
154
|
+
// CSS selector wrapping the generated custom properties block(s), instead
|
|
155
|
+
// of the default `:root`. Applies uniformly whether using a single
|
|
156
|
+
// `source` or `sourceModes` (the base mode's top-level block and every
|
|
157
|
+
// other mode's block nested under its own
|
|
158
|
+
// `@media (prefers-color-scheme: <mode>)` rule all use this same
|
|
159
|
+
// selector). Falsy or blank values fall back to ':root'.
|
|
160
|
+
customPropsSelector: ':where(html)',
|
|
110
161
|
|
|
111
162
|
// If true (default), dimension token values expressed in px are converted
|
|
112
163
|
// to rem in the generated CSS. Values in other units (em, %, vh, dvw, ...)
|
|
@@ -132,7 +183,7 @@ const config = {
|
|
|
132
183
|
// Comments at the end of a custom property line are preserved; full-line
|
|
133
184
|
// comments are lost, and a multi-line comment is cut at the end of its first
|
|
134
185
|
// line (and closed, to keep the CSS valid).
|
|
135
|
-
mergeCustomProps:
|
|
186
|
+
mergeCustomProps: true,
|
|
136
187
|
|
|
137
188
|
// Wraps the generated custom properties inside a CSS `@layer` at-rule,
|
|
138
189
|
// e.g. addLayer: 'project' -> `@layer project { :root { ... } }`.
|
|
@@ -146,7 +197,7 @@ const config = {
|
|
|
146
197
|
customPropsGroups: [
|
|
147
198
|
{
|
|
148
199
|
name: 'COLORS',
|
|
149
|
-
prefixes: ['accent', 'primary', 'secondary', 'neutral'],
|
|
200
|
+
prefixes: ['accent', 'primary', 'secondary', 'neutral', 'color'],
|
|
150
201
|
},
|
|
151
202
|
{
|
|
152
203
|
name: 'LAYOUT',
|
|
@@ -415,7 +415,7 @@ done
|
|
|
415
415
|
# installato in node_modules (stessa versione che userà il progetto)
|
|
416
416
|
MINIMO_PKG_DIR=./node_modules/@massimo-cassandro/minimo
|
|
417
417
|
|
|
418
|
-
force_cat "${MINIMO_PKG_DIR}/src/custom-properties.css" "${FRONTEND_INSTALL_PATH}/css/custom-properties.css"
|
|
418
|
+
force_cat "${MINIMO_PKG_DIR}/src/custom-properties.css" "${FRONTEND_INSTALL_PATH}/css/minimo-custom-properties.css"
|
|
419
419
|
force_cat "${MINIMO_PKG_DIR}/src/custom-media.css" "${FRONTEND_INSTALL_PATH}/css/custom-media.css"
|
|
420
420
|
|
|
421
421
|
# entry css principale del progetto: copia diretta di minimo.css, rinominato
|