1- import css from "css" ;
1+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
22import { assert } from "tsafe/assert" ;
3+ import type { Equals } from "tsafe" ;
34import { objectKeys } from "tsafe/objectKeys" ;
45import { exclude } from "tsafe/exclude" ;
6+ import { parseCss } from "./parseCss" ;
7+ import memoize from "memoizee" ;
58
69export type BreakpointsValues = {
710 unit : string /* em, px ... */ ;
@@ -11,50 +14,127 @@ export type BreakpointsValues = {
1114 xl : number ;
1215} ;
1316
14- export function parseBreakpointsValues ( rawCssCode : string ) : BreakpointsValues {
15- const parsedCss = css . parse ( rawCssCode ) ;
17+ export type MediaQueryByBreakpoint = Record < "sm" | "md" | "lg" | "xl" , string > ;
1618
17- let prevUnit : string | undefined = undefined ;
19+ assert < Equals < keyof MediaQueryByBreakpoint , Exclude < keyof BreakpointsValues , "unit" > > > ( ) ;
1820
19- const values : number [ ] = [ ] ;
21+ export const parseBreakpointsValues = memoize (
22+ (
23+ rawCssCode : string
24+ ) : {
25+ breakpointsValues : BreakpointsValues ;
26+ mediaQueryByBreakpoint : MediaQueryByBreakpoint ;
27+ } => {
28+ const parsedCss = parseCss ( rawCssCode ) ;
2029
21- parsedCss . stylesheet ?. rules
22- . filter ( rule => rule . type === "media" )
23- . forEach ( rule => {
24- const match = ( rule as { media : string } ) . media . match (
25- / ^ \( m i n - w i d t h : \s * ( [ 0 - 9 ] + ) ( [ ^ ) ] + ) \) $ /
26- ) ;
30+ let prevUnit : string | undefined = undefined ;
2731
28- if ( match === null ) {
29- return ;
30- }
32+ const values : number [ ] = [ ] ;
3133
32- const [ , valueStr , unit ] = match ;
34+ const mediaQueryByValue = new Map < number , string > ( ) ;
3335
34- if ( prevUnit === undefined ) {
35- prevUnit = unit ;
36- } else {
37- assert ( prevUnit === unit ) ;
38- }
36+ parsedCss . stylesheet ?. rules
37+ . filter ( rule => rule . type === "media" )
38+ . forEach ( rule => {
39+ const mediaQuery : string = ( rule as any ) . media ;
3940
40- values . push ( parseFloat ( valueStr ) ) ;
41- } ) ;
41+ const match = mediaQuery . match ( / ^ \( m i n - w i d t h : \s * ( [ 0 - 9 ] + ) ( [ ^ ) ] + ) \) $ / ) ;
4242
43- assert ( values . length === 4 ) ;
44- assert ( prevUnit !== undefined ) ;
43+ if ( match === null ) {
44+ return ;
45+ }
4546
46- const [ sm , md , lg , xl ] = values . sort ( ) ;
47+ const [ , valueStr , unit ] = match ;
4748
48- return {
49- "unit" : prevUnit ,
50- sm,
51- md,
52- lg,
53- xl
54- } ;
55- }
49+ if ( prevUnit === undefined ) {
50+ prevUnit = unit ;
51+ } else {
52+ assert ( prevUnit === unit ) ;
53+ }
54+
55+ const value = parseFloat ( valueStr ) ;
56+
57+ values . push ( value ) ;
58+
59+ mediaQueryByValue . set ( value , mediaQuery ) ;
60+ } ) ;
61+
62+ assert ( values . length === 4 ) ;
63+ assert ( prevUnit !== undefined ) ;
64+
65+ const [ sm , md , lg , xl ] = values . sort ( ) ;
66+
67+ const breakpointsValues : BreakpointsValues = {
68+ "unit" : prevUnit ,
69+ sm,
70+ md,
71+ lg,
72+ xl
73+ } ;
74+
75+ const mediaQueryByBreakpoint = Object . fromEntries (
76+ objectKeys ( breakpointsValues )
77+ . map ( breakPoint =>
78+ breakPoint === "unit"
79+ ? undefined
80+ : ( [ breakPoint , breakpointsValues [ breakPoint ] ] as const )
81+ )
82+ . filter ( exclude ( undefined ) )
83+ . map ( ( [ breakPoint , value ] ) => [
84+ breakPoint ,
85+ ( ( ) => {
86+ const mediaQuery = mediaQueryByValue . get ( value ) ;
87+ assert ( mediaQuery !== undefined ) ;
88+ return mediaQuery ;
89+ } ) ( )
90+ ] )
91+ ) as MediaQueryByBreakpoint ;
92+
93+ return { breakpointsValues, mediaQueryByBreakpoint } ;
94+ }
95+ ) ;
96+
97+ export type RulesByBreakpoint = Record <
98+ "root" | keyof MediaQueryByBreakpoint ,
99+ {
100+ type : "rule" ;
101+ selectors : string [ ] ;
102+ declarations : Record < string , unknown > [ ] ;
103+ } [ ]
104+ > ;
105+
106+ export const getRulesByBreakpoint = memoize ( ( rawCssCode : string ) : RulesByBreakpoint => {
107+ const parsedCss = parseCss ( rawCssCode ) ;
108+
109+ const { mediaQueryByBreakpoint } = parseBreakpointsValues ( rawCssCode ) ;
110+
111+ const rulesByBreakpoint : RulesByBreakpoint = { } as any ;
112+
113+ rulesByBreakpoint . root = ( parsedCss . stylesheet ! . rules as any [ ] ) . filter (
114+ ( { type } ) => type === "rule"
115+ ) ;
116+
117+ ( parsedCss . stylesheet ! . rules as any [ ] )
118+ . filter ( ( { type } ) => type === "media" )
119+ . filter ( ( { media } ) => Object . values ( mediaQueryByBreakpoint ) . includes ( media ) )
120+ . forEach (
121+ ( { media, rules } : any ) =>
122+ ( rulesByBreakpoint [
123+ objectKeys ( mediaQueryByBreakpoint )
124+ . map ( breakpoint => ( {
125+ breakpoint,
126+ "mediaQuery" : mediaQueryByBreakpoint [ breakpoint ]
127+ } ) )
128+ . find ( ( { mediaQuery } ) => mediaQuery === media ) ! . breakpoint
129+ ] = rules . filter ( ( { type } : any ) => type === "rule" ) )
130+ ) ;
131+
132+ return rulesByBreakpoint ;
133+ } ) ;
134+
135+ export function generateBreakpointsTsCode ( rawCssCode : string ) : string {
136+ const { breakpointsValues } = parseBreakpointsValues ( rawCssCode ) ;
56137
57- export function generateBreakpointsTsCode ( breakpointsValues : BreakpointsValues ) : string {
58138 const sortedKeys = objectKeys ( breakpointsValues )
59139 . filter ( exclude ( "unit" ) )
60140 . sort ( ( a , b ) => breakpointsValues [ a ] - breakpointsValues [ b ] ) ;
0 commit comments