1- import fs from 'fs' ;
2- import path from 'path' ;
1+ import fs from 'node: fs' ;
2+ import path from 'node: path' ;
33import process from 'process' ;
4+ import { fileURLToPath } from 'node:url' ;
45
56const buildPath = 'dist/' ;
67const sourcePath = 'src/' ;
@@ -11,7 +12,7 @@ function resolve(relativePath) {
1112
1213// ---
1314
14- function readJSONFile ( jsonFilePath ) {
15+ export function readJSONFile ( jsonFilePath ) {
1516 try {
1617 const fileContents = fs . readFileSync ( jsonFilePath , 'utf-8' ) ;
1718 return JSON . parse ( fileContents ) ;
@@ -20,7 +21,7 @@ function readJSONFile(jsonFilePath) {
2021 }
2122}
2223
23- function writeJSONFile ( jsonFilePath , data ) {
24+ export function writeJSONFile ( jsonFilePath , data ) {
2425 try {
2526 fs . writeFileSync ( jsonFilePath , JSON . stringify ( data , null , 2 ) ) ;
2627 } catch ( error ) {
@@ -30,13 +31,13 @@ function writeJSONFile(jsonFilePath, data) {
3031
3132// ---
3233
33- function isObject ( value ) {
34+ export function isObject ( value ) {
3435 return typeof value === 'object' && value !== null ;
3536}
3637
3738// ---
3839
39- function visitTokenTree ( tokens , visitor , prefix = '' ) {
40+ export function visitTokenTree ( tokens , visitor , prefix = '' ) {
4041 for ( const [ key , value ] of Object . entries ( tokens ) ) {
4142 const path = key !== '@' ? `${ prefix } ${ key } ` : prefix . slice ( 0 , - 1 ) ;
4243 if ( isObject ( value ) ) {
@@ -49,7 +50,7 @@ function visitTokenTree(tokens, visitor, prefix = '') {
4950 }
5051}
5152
52- function loadTokenDictionary ( tokenJsonFilePath ) {
53+ export function loadTokenDictionary ( tokenJsonFilePath ) {
5354 const tokensByPath = { } ;
5455 const tokensJson = readJSONFile ( resolve ( tokenJsonFilePath ) ) ;
5556 visitTokenTree ( tokensJson , ( path , token ) => {
@@ -64,31 +65,25 @@ function loadTokenDictionary(tokenJsonFilePath) {
6465const REFERENCE_PATTERN = / \{ ( [ ^ } ] * ) \} / g;
6566
6667// Resolve all of the token value's path references (including resolution of their resolved values' path references).
67- function resolveTokenValue ( value , tokenDictionary ) {
68- while ( value . match ( REFERENCE_PATTERN ) ) {
69- value = value . replaceAll ( REFERENCE_PATTERN , ( _ , referencedPath ) => {
70- const referencedValue = tokenDictionary [ referencedPath ] ?. value ;
71- if ( referencedValue === undefined ) {
72- throw new Error ( `Unable to resolve a referenced token for path: "${ referencedPath } "` ) ;
73- }
74- return referencedValue ;
75- } ) ;
76- }
77- return value ;
78- }
68+ export function resolveTokenValue ( value , tokenDictionary , referencePath = [ ] ) {
69+ return value . replaceAll ( REFERENCE_PATTERN , ( _ , referencedPath ) => {
70+ if ( referencePath . includes ( referencedPath ) ) {
71+ throw new Error ( `Cyclic token reference: ${ [ ...referencePath , referencedPath ] . join ( ' -> ' ) } ` ) ;
72+ }
7973
80- // ---
74+ const referencedValue = tokenDictionary [ referencedPath ] ?. value ;
75+ if ( referencedValue === undefined ) {
76+ throw new Error ( `Unable to resolve a referenced token for path: "${ referencedPath } "` ) ;
77+ }
8178
82- const baseTokenDictionary = loadTokenDictionary ( `${ sourcePath } /index.json` ) ;
83- const compactThemeTokenDictionary = loadTokenDictionary ( `${ sourcePath } /compact.json` ) ;
84- const darkThemeTokenDictionary = loadTokenDictionary ( `${ sourcePath } /dark.json` ) ;
85- const highContrastThemeTokenDictionary = loadTokenDictionary ( `${ sourcePath } /high-contrast.json` ) ;
86- const reducedMotionThemeTokenDictionary = loadTokenDictionary ( `${ sourcePath } /reduced-motion.json` ) ;
79+ return resolveTokenValue ( referencedValue , tokenDictionary , [ ...referencePath , referencedPath ] ) ;
80+ } ) ;
81+ }
8782
88- const categorizedTokens = { } ;
83+ // ---
8984
9085// Collect a categorized token value for the specified category and path.
91- function collectCategorizedToken ( path , details , category , value ) {
86+ function collectCategorizedToken ( categorizedTokens , path , details , category , value ) {
9287 if ( categorizedTokens [ path ] === undefined ) {
9388 categorizedTokens [ path ] = { ...details , values : { [ category ] : value } } ;
9489 } else {
@@ -97,22 +92,16 @@ function collectCategorizedToken(path, details, category, value) {
9792}
9893
9994// Collect categorized token values relative to the specified token dictionaries (last definition wins).
100- function collectCategorizedTokens ( category , ...tokenDictionaries ) {
95+ function collectCategorizedTokens ( categorizedTokens , category , ...tokenDictionaries ) {
10196 const mergedTokenDictionary = Object . assign ( { } , ...tokenDictionaries ) ;
10297 for ( const [ path , token ] of Object . entries ( mergedTokenDictionary ) ) {
10398 const { value, ...details } = token ;
104- const resolvedValue = resolveTokenValue ( value , mergedTokenDictionary ) ;
105- collectCategorizedToken ( path , details , category , resolvedValue ) ;
99+ const resolvedValue = resolveTokenValue ( value , mergedTokenDictionary , [ path ] ) ;
100+ collectCategorizedToken ( categorizedTokens , path , details , category , resolvedValue ) ;
106101 }
107102}
108103
109- collectCategorizedTokens ( 'light' , baseTokenDictionary ) ;
110- collectCategorizedTokens ( 'dark' , baseTokenDictionary , darkThemeTokenDictionary ) ;
111- collectCategorizedTokens ( 'high-contrast' , baseTokenDictionary , highContrastThemeTokenDictionary ) ;
112- collectCategorizedTokens ( 'compact' , baseTokenDictionary , compactThemeTokenDictionary ) ;
113- collectCategorizedTokens ( 'reduced-motion' , baseTokenDictionary , reducedMotionThemeTokenDictionary ) ;
114-
115- function valuesMatch ( values ) {
104+ export function valuesMatch ( values ) {
116105 if ( values . length === 0 ) {
117106 return true ;
118107 }
@@ -125,37 +114,78 @@ function valuesMatch(values) {
125114 return true ;
126115}
127116
128- function categoryValuesMatch ( values , categories ) {
117+ export function categoryValuesMatch ( values , categories ) {
129118 return valuesMatch ( categories . map ( category => values [ category ] ) ) ;
130119}
131120
132- // Consolidate categorized token values that are the same.
133- for ( const token of Object . values ( categorizedTokens ) ) {
134- const values = token . values ;
135- if ( categoryValuesMatch ( values , [ 'light' , 'dark' ] ) ) {
136- values [ '' ] = values [ 'light' ] ;
137- delete values [ 'light' ] ;
138- delete values [ 'dark' ] ;
139- }
140- if ( categoryValuesMatch ( values , [ '' , 'high-contrast' ] ) || categoryValuesMatch ( values , [ 'light' , 'high-contrast' ] ) ) {
141- delete values [ 'high-contrast' ] ;
142- }
143- if ( categoryValuesMatch ( values , [ '' , 'compact' ] ) || categoryValuesMatch ( values , [ 'light' , 'compact' ] ) ) {
144- delete values [ 'compact' ] ;
121+ export function createCssVarCompletions ( {
122+ baseTokenDictionary,
123+ compactThemeTokenDictionary,
124+ darkThemeTokenDictionary,
125+ highContrastThemeTokenDictionary,
126+ reducedMotionThemeTokenDictionary
127+ } ) {
128+ const categorizedTokens = { } ;
129+
130+ collectCategorizedTokens ( categorizedTokens , 'light' , baseTokenDictionary ) ;
131+ collectCategorizedTokens ( categorizedTokens , 'dark' , baseTokenDictionary , darkThemeTokenDictionary ) ;
132+ collectCategorizedTokens ( categorizedTokens , 'high-contrast' , baseTokenDictionary , highContrastThemeTokenDictionary ) ;
133+ collectCategorizedTokens ( categorizedTokens , 'compact' , baseTokenDictionary , compactThemeTokenDictionary ) ;
134+ collectCategorizedTokens ( categorizedTokens , 'reduced-motion' , baseTokenDictionary , reducedMotionThemeTokenDictionary ) ;
135+
136+ // Consolidate categorized token values that are the same.
137+ for ( const token of Object . values ( categorizedTokens ) ) {
138+ const values = token . values ;
139+ if ( categoryValuesMatch ( values , [ 'light' , 'dark' ] ) ) {
140+ values [ '' ] = values [ 'light' ] ;
141+ delete values [ 'light' ] ;
142+ delete values [ 'dark' ] ;
143+ }
144+ if ( categoryValuesMatch ( values , [ '' , 'high-contrast' ] ) || categoryValuesMatch ( values , [ 'light' , 'high-contrast' ] ) ) {
145+ delete values [ 'high-contrast' ] ;
146+ }
147+ if ( categoryValuesMatch ( values , [ '' , 'compact' ] ) || categoryValuesMatch ( values , [ 'light' , 'compact' ] ) ) {
148+ delete values [ 'compact' ] ;
149+ }
150+ if (
151+ categoryValuesMatch ( values , [ '' , 'reduced-motion' ] ) ||
152+ categoryValuesMatch ( values , [ 'light' , 'reduced-motion' ] )
153+ ) {
154+ delete values [ 'reduced-motion' ] ;
155+ }
145156 }
146- if ( categoryValuesMatch ( values , [ '' , 'reduced-motion' ] ) || categoryValuesMatch ( values , [ 'light' , 'reduced-motion' ] ) ) {
147- delete values [ 'reduced-motion' ] ;
157+
158+ // Collect all tokens with their paths transformed to css variable identifiers.
159+ const cssVarCompletions = { } ;
160+ for ( const [ path , token ] of Object . entries ( categorizedTokens ) ) {
161+ cssVarCompletions [ `--nve-${ path . replaceAll ( '.' , '-' ) } ` ] = token ;
148162 }
149- }
150163
151- // Collect all tokens with their paths transformed to css variable identifiers.
152- const cssVarCompletions = { } ;
153- for ( const [ path , token ] of Object . entries ( categorizedTokens ) ) {
154- cssVarCompletions [ `--nve-${ path . replaceAll ( '.' , '-' ) } ` ] = token ;
164+ return cssVarCompletions ;
155165}
156166
157- if ( ! fs . existsSync ( `${ buildPath } ` ) ) {
158- fs . mkdirSync ( `${ buildPath } ` ) ;
167+ export function buildCssVarCompletions ( ) {
168+ const baseTokenDictionary = loadTokenDictionary ( `${ sourcePath } /index.json` ) ;
169+ const compactThemeTokenDictionary = loadTokenDictionary ( `${ sourcePath } /compact.json` ) ;
170+ const darkThemeTokenDictionary = loadTokenDictionary ( `${ sourcePath } /dark.json` ) ;
171+ const highContrastThemeTokenDictionary = loadTokenDictionary ( `${ sourcePath } /high-contrast.json` ) ;
172+ const reducedMotionThemeTokenDictionary = loadTokenDictionary ( `${ sourcePath } /reduced-motion.json` ) ;
173+
174+ const cssVarCompletions = createCssVarCompletions ( {
175+ baseTokenDictionary,
176+ compactThemeTokenDictionary,
177+ darkThemeTokenDictionary,
178+ highContrastThemeTokenDictionary,
179+ reducedMotionThemeTokenDictionary
180+ } ) ;
181+
182+ if ( ! fs . existsSync ( `${ buildPath } ` ) ) {
183+ fs . mkdirSync ( `${ buildPath } ` ) ;
184+ }
185+
186+ writeJSONFile ( './dist/data.css-vars.json' , cssVarCompletions ) ;
159187}
160188
161- writeJSONFile ( './dist/data.css-vars.json' , cssVarCompletions ) ;
189+ if ( process . argv [ 1 ] === fileURLToPath ( import . meta. url ) ) {
190+ buildCssVarCompletions ( ) ;
191+ }
0 commit comments