@@ -735,6 +735,127 @@ function buildBaseThemeCss(tokens, cssValues, tokenMap, lightTheme, darkTheme) {
735735 return parts . join ( '\n' ) ;
736736}
737737
738+ function buildSliceCss ( tokens , cssValues , tokenMap , lightTheme , darkTheme ) {
739+ if ( tokens . length === 0 ) return '' ;
740+ return buildBaseThemeCss ( tokens , cssValues , tokenMap , lightTheme , darkTheme ) ;
741+ }
742+
743+ const SLICE_SCAN_SKIP_DIRS = new Set ( [ '__tests__' , '__snapshots__' , 'demo' ] ) ;
744+
745+ function listSliceScanFiles ( dirPath ) {
746+ if ( ! fs . existsSync ( dirPath ) ) return [ ] ;
747+ const result = [ ] ;
748+ for ( const entry of fs . readdirSync ( dirPath , { withFileTypes : true } ) ) {
749+ if ( SLICE_SCAN_SKIP_DIRS . has ( entry . name ) ) continue ;
750+ const full = path . join ( dirPath , entry . name ) ;
751+ if ( entry . isDirectory ( ) ) {
752+ result . push ( ...listSliceScanFiles ( full ) ) ;
753+ } else if ( CONSUMER_FILE_EXTENSIONS . has ( path . extname ( entry . name ) ) ) {
754+ result . push ( full ) ;
755+ }
756+ }
757+ return result ;
758+ }
759+
760+ function listReactComponentDirs ( ) {
761+ if ( ! fs . existsSync ( REACT_SRC_DIR ) ) return [ ] ;
762+ return listDirectories ( REACT_SRC_DIR ) . filter (
763+ ( name ) => ! name . startsWith ( '_' ) && name !== 'style' && name !== 'locale'
764+ ) ;
765+ }
766+
767+ function extractStyleEntryDeps ( dirName , namespacesWithStyles ) {
768+ const styleEntryPath = path . join ( REACT_SRC_DIR , dirName , 'style' , 'index.tsx' ) ;
769+ if ( ! fs . existsSync ( styleEntryPath ) ) return new Set ( ) ;
770+
771+ const sourceText = readText ( styleEntryPath ) ;
772+ const importRegex = / f r o m \s + [ ' " ] ( [ ^ ' " ] + ) [ ' " ] | i m p o r t \s + [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / g;
773+ const deps = new Set ( ) ;
774+ let match ;
775+
776+ while ( ( match = importRegex . exec ( sourceText ) ) !== null ) {
777+ const importPath = match [ 1 ] || match [ 2 ] ;
778+ const styleDepMatch = importPath . match ( / ^ \. \. \/ \. \. \/ ( [ ^ / ] + ) \/ s t y l e / ) ;
779+ if ( styleDepMatch && namespacesWithStyles . has ( styleDepMatch [ 1 ] ) ) {
780+ deps . add ( styleDepMatch [ 1 ] ) ;
781+ }
782+ }
783+
784+ return deps ;
785+ }
786+
787+ function computeSliceManifest ( allTokens ) {
788+ const componentTokens = allTokens . filter ( ( token ) => token . category === 'component' ) ;
789+ const tokensByNamespace = new Map ( ) ;
790+ for ( const token of componentTokens ) {
791+ if ( ! token . component ) continue ;
792+ if ( ! tokensByNamespace . has ( token . component ) ) {
793+ tokensByNamespace . set ( token . component , [ ] ) ;
794+ }
795+ tokensByNamespace . get ( token . component ) . push ( token ) ;
796+ }
797+
798+ const varToNamespace = new Map ( ) ;
799+ for ( const token of componentTokens ) {
800+ if ( token . component ) {
801+ varToNamespace . set ( tokenKeyToCssVar ( token . key ) , token . component ) ;
802+ }
803+ }
804+
805+ const reactDirs = listReactComponentDirs ( ) ;
806+ const namespacesWithStyles = new Set ( reactDirs ) ;
807+
808+ const directDeps = new Map ( ) ;
809+ for ( const dirName of reactDirs ) {
810+ const dirPath = path . join ( REACT_SRC_DIR , dirName ) ;
811+ const deps = new Set ( ) ;
812+
813+ for ( const filePath of listSliceScanFiles ( dirPath ) ) {
814+ const sourceText = readText ( filePath ) ;
815+ for ( const reference of extractTyVarReferences ( sourceText ) ) {
816+ const namespace = varToNamespace . get ( reference . name ) ;
817+ if ( namespace ) deps . add ( namespace ) ;
818+ }
819+ }
820+
821+ for ( const styleDep of extractStyleEntryDeps ( dirName , namespacesWithStyles ) ) {
822+ deps . add ( styleDep ) ;
823+ }
824+
825+ directDeps . set ( dirName , deps ) ;
826+ }
827+
828+ const closure = new Map ( ) ;
829+ for ( const dirName of directDeps . keys ( ) ) {
830+ const visited = new Set ( ) ;
831+ const queue = [ ...( directDeps . get ( dirName ) || [ ] ) ] ;
832+ while ( queue . length > 0 ) {
833+ const namespace = queue . shift ( ) ;
834+ if ( visited . has ( namespace ) ) continue ;
835+ visited . add ( namespace ) ;
836+ const nsDeps = directDeps . get ( namespace ) ;
837+ if ( nsDeps ) {
838+ for ( const next of nsDeps ) {
839+ if ( ! visited . has ( next ) ) queue . push ( next ) ;
840+ }
841+ }
842+ }
843+ closure . set ( dirName , visited ) ;
844+ }
845+
846+ const manifest = { } ;
847+ const tokenSliceNames = new Set ( tokensByNamespace . keys ( ) ) ;
848+ for ( const [ dirName , namespaceSet ] of closure . entries ( ) ) {
849+ const sliceNames = Array . from ( namespaceSet )
850+ . filter ( ( namespace ) => tokenSliceNames . has ( namespace ) )
851+ . sort ( ) ;
852+ if ( sliceNames . length === 0 ) continue ;
853+ manifest [ dirName ] = sliceNames ;
854+ }
855+
856+ return { tokensByNamespace, manifest } ;
857+ }
858+
738859function buildRegistryDts ( ) {
739860 return `export type TokenCategory = 'primitive' | 'semantic' | 'component';
740861export type TokenType =
@@ -970,13 +1091,32 @@ function buildRuntimeTokens(options = {}) {
9701091 const darkCss = buildThemeCss ( allTokens , cssValues , tokenMap , darkThemeOverrides , "[data-tiny-theme='dark']" ) ;
9711092 const baseCss = buildBaseThemeCss ( allTokens , cssValues , tokenMap , lightTheme , darkTheme ) ;
9721093
1094+ const primitiveTier = allTokens . filter ( ( token ) => token . category === 'primitive' ) ;
1095+ const semanticTier = allTokens . filter ( ( token ) => token . category === 'semantic' ) ;
1096+ const foundationCss = buildSliceCss ( primitiveTier , cssValues , tokenMap , lightTheme , darkTheme ) ;
1097+ const semanticCss = buildSliceCss ( semanticTier , cssValues , tokenMap , lightTheme , darkTheme ) ;
1098+
1099+ const { tokensByNamespace, manifest } = computeSliceManifest ( allTokens ) ;
1100+
9731101 mkdirp ( DIST_CSS_DIR ) ;
9741102 mkdirp ( SCHEMA_DIST_DIR ) ;
1103+ const componentsDir = path . join ( DIST_CSS_DIR , 'components' ) ;
1104+ mkdirp ( componentsDir ) ;
9751105 writeJson ( path . join ( DIST_DIR , 'registry.json' ) , registry ) ;
9761106 writeJson ( path . join ( DIST_DIR , 'presets.json' ) , presets ) ;
9771107 fs . writeFileSync ( path . join ( DIST_CSS_DIR , 'light.css' ) , lightCss ) ;
9781108 fs . writeFileSync ( path . join ( DIST_CSS_DIR , 'dark.css' ) , darkCss ) ;
9791109 fs . writeFileSync ( path . join ( DIST_CSS_DIR , 'base.css' ) , baseCss ) ;
1110+ fs . writeFileSync ( path . join ( DIST_CSS_DIR , 'foundation.css' ) , foundationCss ) ;
1111+ fs . writeFileSync ( path . join ( DIST_CSS_DIR , 'semantic.css' ) , semanticCss ) ;
1112+
1113+ for ( const [ namespace , namespaceTokens ] of tokensByNamespace . entries ( ) ) {
1114+ const sliceCss = buildSliceCss ( namespaceTokens , cssValues , tokenMap , lightTheme , darkTheme ) ;
1115+ fs . writeFileSync ( path . join ( componentsDir , `${ namespace } .css` ) , sliceCss ) ;
1116+ }
1117+
1118+ writeJson ( path . join ( DIST_CSS_DIR , 'component-deps.json' ) , manifest ) ;
1119+
9801120 fs . writeFileSync ( REGISTRY_DTS_PATH , buildRegistryDts ( ) ) ;
9811121 fs . writeFileSync ( PRESETS_DTS_PATH , buildPresetsDts ( presets ) ) ;
9821122 fs . copyFileSync ( THEME_SCHEMA_PATH , path . join ( SCHEMA_DIST_DIR , 'theme.schema.json' ) ) ;
@@ -989,6 +1129,10 @@ function buildRuntimeTokens(options = {}) {
9891129 console . log ( ' dist/css/light.css' ) ;
9901130 console . log ( ' dist/css/dark.css' ) ;
9911131 console . log ( ' dist/css/base.css' ) ;
1132+ console . log ( ' dist/css/foundation.css' ) ;
1133+ console . log ( ' dist/css/semantic.css' ) ;
1134+ console . log ( ` dist/css/components/*.css (${ tokensByNamespace . size } slices)` ) ;
1135+ console . log ( ` dist/css/component-deps.json (${ Object . keys ( manifest ) . length } dirs)` ) ;
9921136 console . log ( '\nRuntime tokens done.' ) ;
9931137
9941138 return { registry, presets } ;
0 commit comments