@massimo-cassandro/minimo 1.34.0 → 1.35.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 +4 -0
- package/dev-tools/design-tokens-builder/build-tokens-src/build-source-modes.mjs +3 -0
- package/dev-tools/design-tokens-builder/build-tokens-src/legacy-tokens-parser.mjs +105 -0
- package/dev-tools/design-tokens-builder/build-tokens.mjs +13 -1
- package/dev-tools/design-tokens-builder/check-unresolved-custom-props.mjs +4 -0
- package/dev-tools/design-tokens-builder/tokens-config-sample.mjs +16 -2
- package/dev-tools/starter-kit/source_files/root/__project__.code-workspace +4 -0
- package/package.json +1 -1
|
@@ -66,6 +66,10 @@ The `source` array accepts any combination of:
|
|
|
66
66
|
- `.json` / `.jsonc` — parsed directly as token data
|
|
67
67
|
- `.mjs` / `.js` — imported as ES modules; must export a W3C DTCG-compliant object as `default`
|
|
68
68
|
|
|
69
|
+
**Legacy token syntax (auto-detected, `.json` only):** `.json` source files written in the older, non-DTCG Style Dictionary syntax (`value`/`type` instead of `$value`/`$type`, references as `{group.token.value}` instead of `{group.token}` — e.g. a file exported from [Open Props](https://open-props.style/)) are converted to DTCG v5 syntax automatically at parse time, node by node. Files already using `$value`/`$type` are left untouched, so no config flag or file list is needed — mixing legacy and DTCG sources in the same `source` array just works. `.jsonc` is not covered (reserved for hand-authored DTCG sources, parsed by Style Dictionary's own loader) and these `.json` files are parsed with plain `JSON.parse` (no comments, no trailing commas). See `build-tokens-src/legacy-tokens-parser.mjs`.
|
|
70
|
+
|
|
71
|
+
Known limitation: a legacy node that is at the same time a token (own `value`/`type`) *and* a group with further nested children (e.g. Open Props' `other.ease.out`, which has its own value plus `out.1` ... `out.5`) can't be represented in DTCG v5 — a node with `$value` is always a leaf, Style Dictionary doesn't descend into its children. Only the node's own value is converted; the nested children are silently lost.
|
|
72
|
+
|
|
69
73
|
|
|
70
74
|
#### Token file format (JS example)
|
|
71
75
|
|
|
@@ -29,6 +29,7 @@ import { CSS_TRANSFORMS, JSON_TRANSFORMS } from './platforms.mjs';
|
|
|
29
29
|
import { customPropsCount } from './formats/css.mjs';
|
|
30
30
|
import { buildJsonFiles, collectConcreteFilePaths } from './formats/json.mjs';
|
|
31
31
|
import { loadExistingCustomPropsScoped } from './merge-css.mjs';
|
|
32
|
+
import { LEGACY_TOKENS_PARSER_NAME } from './legacy-tokens-parser.mjs';
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* @param {object} opts
|
|
@@ -81,6 +82,7 @@ export const buildSourceModes = async ({
|
|
|
81
82
|
for (const mode of modeNames) {
|
|
82
83
|
const sd = new StyleDictionary({
|
|
83
84
|
source: sourceModes[mode],
|
|
85
|
+
parsers: [LEGACY_TOKENS_PARSER_NAME],
|
|
84
86
|
log: { verbosity: 'verbose' },
|
|
85
87
|
platforms: {
|
|
86
88
|
css: {
|
|
@@ -115,6 +117,7 @@ export const buildSourceModes = async ({
|
|
|
115
117
|
|
|
116
118
|
const jsonSd = new StyleDictionary({
|
|
117
119
|
source: sourceModes[mode],
|
|
120
|
+
parsers: [LEGACY_TOKENS_PARSER_NAME],
|
|
118
121
|
log: { verbosity: 'silent' },
|
|
119
122
|
platforms: {
|
|
120
123
|
json: {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// build-tokens-src/legacy-tokens-parser.mjs
|
|
2
|
+
// Registers a Style Dictionary parser that bridges "legacy" token files
|
|
3
|
+
// (Style Dictionary v3/v4-style syntax: no `$` prefix on `value`/`type`,
|
|
4
|
+
// references written as `{group.token.value}`) into the W3C DTCG syntax
|
|
5
|
+
// (`$value`/`$type`, references as `{group.token}`) required by Style
|
|
6
|
+
// Dictionary v5.
|
|
7
|
+
//
|
|
8
|
+
// Why this is needed: Style Dictionary v5 decides whether a build "usesDtcg"
|
|
9
|
+
// ONCE per build, from whichever source file is processed first (see
|
|
10
|
+
// detectDtcgSyntax() called inside combineJSON.js in style-dictionary) — it
|
|
11
|
+
// is a single global flag, not a per-file check. Mixing a legacy-syntax file
|
|
12
|
+
// (e.g. a file exported from Open Props, or from an old Style Dictionary
|
|
13
|
+
// v3/v4 project) together with DTCG sources therefore causes the legacy
|
|
14
|
+
// file's tokens to be silently dropped regardless of file order: they never
|
|
15
|
+
// carry the `$value` key combineJSON's own tagging (and the rest of the
|
|
16
|
+
// pipeline) expects.
|
|
17
|
+
//
|
|
18
|
+
// This parser intercepts every `.json` source file (not `.jsonc` — that
|
|
19
|
+
// extension is reserved in this project for hand-authored DTCG sources,
|
|
20
|
+
// already handled correctly by Style Dictionary's own built-in loader) and
|
|
21
|
+
// converts, node by node:
|
|
22
|
+
// value -> $value, type -> $type, comment/description -> $description
|
|
23
|
+
// "{group.token.value}" -> "{group.token}" inside reference strings
|
|
24
|
+
// Nodes that already use `$value` (DTCG v5 syntax) are left untouched, so
|
|
25
|
+
// the parser is a safe no-op for files that don't need conversion — no
|
|
26
|
+
// config flag or file list is required, detection is fully automatic.
|
|
27
|
+
//
|
|
28
|
+
// Limitation: `.json` files handled by this parser are parsed with plain
|
|
29
|
+
// JSON.parse (no comments, no trailing commas), unlike Style Dictionary's
|
|
30
|
+
// own built-in loader (which uses JSON5 for .json/.jsonc/.json5). This keeps
|
|
31
|
+
// the builder dependency-free — legacy token exports (Open Props, old Style
|
|
32
|
+
// Dictionary v3/v4 projects) are virtually always strict JSON anyway. Use
|
|
33
|
+
// `.jsonc` or `.mjs` for hand-authored DTCG sources that need comments.
|
|
34
|
+
|
|
35
|
+
import StyleDictionary from 'style-dictionary';
|
|
36
|
+
|
|
37
|
+
export const LEGACY_TOKENS_PARSER_NAME = 'minimo/legacy-tokens';
|
|
38
|
+
|
|
39
|
+
// Matches "{some.token.path.value}" -> captures "some.token.path"
|
|
40
|
+
const LEGACY_REF = /\{([^}]+)\.value\}/g;
|
|
41
|
+
|
|
42
|
+
/** @param {unknown} value @returns {unknown} */
|
|
43
|
+
const convertLegacyRefs = (value) => {
|
|
44
|
+
if (typeof value === 'string') {
|
|
45
|
+
return value.replace(LEGACY_REF, '{$1}');
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
return value.map(convertLegacyRefs);
|
|
49
|
+
}
|
|
50
|
+
if (value !== null && typeof value === 'object') {
|
|
51
|
+
return Object.fromEntries(
|
|
52
|
+
Object.entries(value).map(([key, v]) => [key, convertLegacyRefs(v)])
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** @param {unknown} node @returns {node is Record<string, unknown>} */
|
|
59
|
+
const isPlainObject = (node) => node !== null && typeof node === 'object' && !Array.isArray(node);
|
|
60
|
+
|
|
61
|
+
/** @param {unknown} node @returns {unknown} */
|
|
62
|
+
const convertLegacyNode = (node) => {
|
|
63
|
+
if (!isPlainObject(node)) return node;
|
|
64
|
+
|
|
65
|
+
// Already DTCG v5 syntax: leave untouched.
|
|
66
|
+
if (Object.hasOwn(node, '$value')) return node;
|
|
67
|
+
|
|
68
|
+
if (Object.hasOwn(node, 'value')) {
|
|
69
|
+
// Legacy token leaf. Some legacy files (e.g. Open Props) also attach
|
|
70
|
+
// sibling numbered/named children to a node that carries its own
|
|
71
|
+
// `value`/`type` (a group that is also a default token) — those
|
|
72
|
+
// children are converted too, even though Style Dictionary itself
|
|
73
|
+
// cannot represent "group + own $value" ambiguity natively.
|
|
74
|
+
const { value, type, comment, description, ...rest } = node;
|
|
75
|
+
const convertedRest = Object.fromEntries(
|
|
76
|
+
Object.entries(rest).map(([key, child]) => [key, convertLegacyNode(child)])
|
|
77
|
+
);
|
|
78
|
+
const desc = comment ?? description;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
...convertedRest,
|
|
82
|
+
$value: convertLegacyRefs(value),
|
|
83
|
+
...(type !== undefined ? { $type: type } : {}),
|
|
84
|
+
...(desc !== undefined ? { $description: desc } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Group node: recurse into children.
|
|
89
|
+
return Object.fromEntries(
|
|
90
|
+
Object.entries(node).map(([key, child]) => [key, convertLegacyNode(child)])
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Registers the parser globally on the StyleDictionary class. To actually
|
|
95
|
+
// run it, a Style Dictionary instance must also opt in via
|
|
96
|
+
// `parsers: [LEGACY_TOKENS_PARSER_NAME]` in its config (registering alone
|
|
97
|
+
// does not activate it for every instance — see Style Dictionary's parser
|
|
98
|
+
// hooks docs).
|
|
99
|
+
export const registerLegacyTokensParser = () => {
|
|
100
|
+
StyleDictionary.registerParser({
|
|
101
|
+
name: LEGACY_TOKENS_PARSER_NAME,
|
|
102
|
+
pattern: /\.json$/,
|
|
103
|
+
parser: ({ contents }) => convertLegacyNode(JSON.parse(contents)),
|
|
104
|
+
});
|
|
105
|
+
};
|
|
@@ -44,6 +44,12 @@ import {
|
|
|
44
44
|
// ── 2. Custom transforms ─────────────────────────────────────────────────────
|
|
45
45
|
import './build-tokens-src/transforms.mjs';
|
|
46
46
|
|
|
47
|
+
// ── 2b. Legacy token syntax bridge ───────────────────────────────────────────
|
|
48
|
+
// Converts legacy (non-DTCG, e.g. Open Props) .json/.jsonc source files to
|
|
49
|
+
// DTCG v5 syntax at parse time — see build-tokens-src/legacy-tokens-parser.mjs.
|
|
50
|
+
import { LEGACY_TOKENS_PARSER_NAME, registerLegacyTokensParser } from './build-tokens-src/legacy-tokens-parser.mjs';
|
|
51
|
+
registerLegacyTokensParser();
|
|
52
|
+
|
|
47
53
|
// ── 3. CSS format ─────────────────────────────────────────────────────────────
|
|
48
54
|
// customPropsCount is updated by the format at render time
|
|
49
55
|
// loadExistingCustomProps supports the mergeCustomProps option (see below)
|
|
@@ -127,12 +133,18 @@ if (sourceModes) {
|
|
|
127
133
|
const singleSource = /** @type {string[]} */ (source);
|
|
128
134
|
|
|
129
135
|
if (jsonBuildPath && !jsonDestFile) {
|
|
130
|
-
const sdInit = new StyleDictionary({
|
|
136
|
+
const sdInit = new StyleDictionary({
|
|
137
|
+
source: singleSource,
|
|
138
|
+
parsers: [LEGACY_TOKENS_PARSER_NAME],
|
|
139
|
+
log: { verbosity: 'silent' },
|
|
140
|
+
platforms: {},
|
|
141
|
+
});
|
|
131
142
|
concreteFilePaths = await collectConcreteFilePaths(sdInit);
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
const sd = new StyleDictionary({
|
|
135
146
|
source: singleSource,
|
|
147
|
+
parsers: [LEGACY_TOKENS_PARSER_NAME],
|
|
136
148
|
log: { verbosity: 'verbose' },
|
|
137
149
|
platforms: buildPlatforms({
|
|
138
150
|
buildPath,
|
|
@@ -26,6 +26,9 @@ import './build-tokens-src/transforms.mjs';
|
|
|
26
26
|
import { CSS_TRANSFORMS } from './build-tokens-src/platforms.mjs';
|
|
27
27
|
import { resolveSourcePaths } from './build-tokens-src/resolve-source-paths.mjs';
|
|
28
28
|
import { findMediaModeBlocks } from './build-tokens-src/merge-css.mjs';
|
|
29
|
+
import { LEGACY_TOKENS_PARSER_NAME, registerLegacyTokensParser } from './build-tokens-src/legacy-tokens-parser.mjs';
|
|
30
|
+
|
|
31
|
+
registerLegacyTokensParser();
|
|
29
32
|
|
|
30
33
|
// const __filename = fileURLToPath(import.meta.url);
|
|
31
34
|
// const __dirname = dirname(__filename);
|
|
@@ -208,6 +211,7 @@ async function run() {
|
|
|
208
211
|
try {
|
|
209
212
|
const sd = new StyleDictionary({
|
|
210
213
|
source: resolvedPaths,
|
|
214
|
+
parsers: [LEGACY_TOKENS_PARSER_NAME],
|
|
211
215
|
log: { verbosity: 'silent' },
|
|
212
216
|
platforms: { css: { transforms: CSS_TRANSFORMS } },
|
|
213
217
|
});
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
// minimo package path in node_modules
|
|
16
|
-
const
|
|
16
|
+
const node_modules_path = '../../node_modules',
|
|
17
|
+
minimo_path = `${node_modules_path}/@massimo-cassandro/minimo`;
|
|
17
18
|
|
|
18
19
|
const config = {
|
|
19
20
|
|
|
@@ -33,6 +34,19 @@ const config = {
|
|
|
33
34
|
// Source token files — accept paths, globs, or a mix of both.
|
|
34
35
|
// Supported formats: .json, .jsonc, .mjs (must export a DTCG-compliant default object)
|
|
35
36
|
//
|
|
37
|
+
// Legacy (non-DTCG) .json files — e.g. exported from Open Props — are
|
|
38
|
+
// auto-detected and converted to DTCG v5 syntax at parse time, so they can
|
|
39
|
+
// be freely mixed with regular DTCG sources (see README.md). Not covered:
|
|
40
|
+
// .jsonc, reserved for hand-authored DTCG sources. These .json files are
|
|
41
|
+
// parsed with plain JSON.parse (no comments, no trailing commas).
|
|
42
|
+
//
|
|
43
|
+
// LIMITATION: a legacy node that is at the same time a token (own
|
|
44
|
+
// value/type) AND a group with further nested children (e.g. Open Props'
|
|
45
|
+
// other.ease.out, which has its own value plus out.1 ... out.5) cannot be
|
|
46
|
+
// represented in DTCG v5 — a node with $value is always a leaf, Style
|
|
47
|
+
// Dictionary does not descend into its children. In that case only the
|
|
48
|
+
// node's own value is converted; the nested children are silently lost.
|
|
49
|
+
//
|
|
36
50
|
// WARNING: do not use path.join() to build glob patterns — it may corrupt
|
|
37
51
|
// the pattern syntax. Use template literals instead:
|
|
38
52
|
// OK: `${minimo_path}/**/*.{json,mjs}`
|
|
@@ -42,7 +56,7 @@ const config = {
|
|
|
42
56
|
`${minimo_path}/src/**/*.tokens.{mjs,jsonc}`, // components tokens
|
|
43
57
|
|
|
44
58
|
// optional extra token source (in this example, openProps, https://open-props.style/)
|
|
45
|
-
//
|
|
59
|
+
// `${node_modules_path}/open-props/open-props.style-dictionary-tokens.json`,
|
|
46
60
|
|
|
47
61
|
// your project tokens
|
|
48
62
|
'./project-tokens/*.{js,mjs,jsonc,json}',
|