66} = require ( "@asamuzakjp/css-color" ) ;
77const { next : syntaxes } = require ( "@csstools/css-syntax-patches-for-csstree" ) ;
88const csstree = require ( "css-tree" ) ;
9+ const { LRUCache } = require ( "lru-cache" ) ;
910const { asciiLowercase } = require ( "./utils/strings" ) ;
1011
1112// CSS global keywords
@@ -86,6 +87,11 @@ const varContainedRegEx = /(?<=[*/\s(])var\(/;
8687// Patched css-tree
8788const cssTree = csstree . fork ( syntaxes ) ;
8889
90+ // Instance of the LRU Cache. Stores up to 4096 items.
91+ const lruCache = new LRUCache ( {
92+ max : 4096
93+ } ) ;
94+
8995/**
9096 * Prepares a stringified value.
9197 *
@@ -192,16 +198,23 @@ const isValidPropertyValue = (prop, val) => {
192198 }
193199 return false ;
194200 }
195- let ast ;
201+ const cacheKey = `isValidPropertyValue_${ prop } _${ val } ` ;
202+ const cachedValue = lruCache . get ( cacheKey ) ;
203+ if ( typeof cachedValue === "boolean" ) {
204+ return cachedValue ;
205+ }
206+ let result ;
196207 try {
197- ast = parseCSS ( val , {
208+ const ast = parseCSS ( val , {
198209 context : "value"
199210 } ) ;
211+ const { error, matched } = cssTree . lexer . matchProperty ( prop , ast ) ;
212+ result = error === null && matched !== null ;
200213 } catch {
201- return false ;
214+ result = false ;
202215 }
203- const { error , matched } = cssTree . lexer . matchProperty ( prop , ast ) ;
204- return error === null && matched !== null ;
216+ lruCache . set ( cacheKey , result ) ;
217+ return result ;
205218} ;
206219
207220/**
@@ -218,6 +231,11 @@ const resolveCalc = (val, opt = { format: "specifiedValue" }) => {
218231 if ( val === "" || hasVarFunc ( val ) || ! hasCalcFunc ( val ) ) {
219232 return val ;
220233 }
234+ const cacheKey = `resolveCalc_${ val } ` ;
235+ const cachedValue = lruCache . get ( cacheKey ) ;
236+ if ( typeof cachedValue === "string" ) {
237+ return cachedValue ;
238+ }
221239 const obj = parseCSS ( val , { context : "value" } , true ) ;
222240 if ( ! obj ?. children ) {
223241 return ;
@@ -243,7 +261,9 @@ const resolveCalc = (val, opt = { format: "specifiedValue" }) => {
243261 values . push ( itemName ?? itemValue ) ;
244262 }
245263 }
246- return values . join ( " " ) ;
264+ const resolvedValue = values . join ( " " ) ;
265+ lruCache . set ( cacheKey , resolvedValue ) ;
266+ return resolvedValue ;
247267} ;
248268
249269/**
@@ -269,123 +289,144 @@ const parsePropertyValue = (prop, val, opt = {}) => {
269289 }
270290 val = calculatedValue ;
271291 }
292+ const cacheKey = `parsePropertyValue_${ prop } _${ val } _${ caseSensitive } ` ;
293+ const cachedValue = lruCache . get ( cacheKey ) ;
294+ if ( cachedValue === false ) {
295+ return ;
296+ } else if ( inArray ) {
297+ if ( Array . isArray ( cachedValue ) ) {
298+ return cachedValue ;
299+ }
300+ } else if ( typeof cachedValue === "string" ) {
301+ return cachedValue ;
302+ }
303+ let parsedValue ;
272304 const lowerCasedValue = asciiLowercase ( val ) ;
273305 if ( GLOBAL_KEYS . has ( lowerCasedValue ) ) {
274306 if ( inArray ) {
275- return [
307+ parsedValue = [
276308 {
277309 type : AST_TYPES . GLOBAL_KEYWORD ,
278310 name : lowerCasedValue
279311 }
280312 ] ;
313+ } else {
314+ parsedValue = lowerCasedValue ;
281315 }
282- return lowerCasedValue ;
283316 } else if ( SYS_COLORS . has ( lowerCasedValue ) ) {
284317 if ( / ^ (?: (?: - w e b k i t - ) ? (?: [ a - z ] [ a - z \d ] * - ) * c o l o r | b o r d e r ) $ / i. test ( prop ) ) {
285318 if ( inArray ) {
286- return [
319+ parsedValue = [
287320 {
288321 type : AST_TYPES . IDENTIFIER ,
289322 name : lowerCasedValue
290323 }
291324 ] ;
325+ } else {
326+ parsedValue = lowerCasedValue ;
292327 }
293- return lowerCasedValue ;
294- }
295- return ;
296- }
297- try {
298- const ast = parseCSS ( val , {
299- context : "value"
300- } ) ;
301- const { error, matched } = cssTree . lexer . matchProperty ( prop , ast ) ;
302- if ( error || ! matched ) {
303- return ;
328+ } else {
329+ parsedValue = false ;
304330 }
305- if ( inArray ) {
306- const obj = cssTree . toPlainObject ( ast ) ;
307- const items = obj . children ;
308- const parsedValues = [ ] ;
309- for ( const item of items ) {
310- const { children, name, type, value, unit } = item ;
311- switch ( type ) {
312- case AST_TYPES . DIMENSION : {
313- parsedValues . push ( {
314- type,
315- value,
316- unit : asciiLowercase ( unit )
317- } ) ;
318- break ;
319- }
320- case AST_TYPES . FUNCTION : {
321- const css = cssTree
322- . generate ( item )
323- . replace ( / \) (? ! \) | \s | , ) / g, ") " )
324- . trim ( ) ;
325- const raw = items . length === 1 ? val : css ;
326- // Remove "${name}(" from the start and ")" from the end
327- const itemValue = raw . slice ( name . length + 1 , - 1 ) . trim ( ) ;
328- if ( name === "calc" ) {
329- if ( children . length === 1 ) {
330- const [ child ] = children ;
331- if ( child . type === AST_TYPES . NUMBER ) {
332- parsedValues . push ( {
333- type : AST_TYPES . CALC ,
334- isNumber : true ,
335- value : `${ parseFloat ( child . value ) } ` ,
336- name,
337- raw
338- } ) ;
331+ } else {
332+ try {
333+ const ast = parseCSS ( val , {
334+ context : "value"
335+ } ) ;
336+ const { error, matched } = cssTree . lexer . matchProperty ( prop , ast ) ;
337+ if ( error || ! matched ) {
338+ parsedValue = false ;
339+ } else if ( inArray ) {
340+ const obj = cssTree . toPlainObject ( ast ) ;
341+ const items = obj . children ;
342+ const values = [ ] ;
343+ for ( const item of items ) {
344+ const { children, name, type, value, unit } = item ;
345+ switch ( type ) {
346+ case AST_TYPES . DIMENSION : {
347+ values . push ( {
348+ type,
349+ value,
350+ unit : asciiLowercase ( unit )
351+ } ) ;
352+ break ;
353+ }
354+ case AST_TYPES . FUNCTION : {
355+ const css = cssTree
356+ . generate ( item )
357+ . replace ( / \) (? ! \) | \s | , ) / g, ") " )
358+ . trim ( ) ;
359+ const raw = items . length === 1 ? val : css ;
360+ // Remove "${name}(" from the start and ")" from the end
361+ const itemValue = raw . slice ( name . length + 1 , - 1 ) . trim ( ) ;
362+ if ( name === "calc" ) {
363+ if ( children . length === 1 ) {
364+ const [ child ] = children ;
365+ if ( child . type === AST_TYPES . NUMBER ) {
366+ values . push ( {
367+ type : AST_TYPES . CALC ,
368+ isNumber : true ,
369+ value : `${ parseFloat ( child . value ) } ` ,
370+ name,
371+ raw
372+ } ) ;
373+ } else {
374+ values . push ( {
375+ type : AST_TYPES . CALC ,
376+ isNumber : false ,
377+ value : `${ asciiLowercase ( itemValue ) } ` ,
378+ name,
379+ raw
380+ } ) ;
381+ }
339382 } else {
340- parsedValues . push ( {
383+ values . push ( {
341384 type : AST_TYPES . CALC ,
342385 isNumber : false ,
343- value : ` ${ asciiLowercase ( itemValue ) } ` ,
386+ value : asciiLowercase ( itemValue ) ,
344387 name,
345388 raw
346389 } ) ;
347390 }
348391 } else {
349- parsedValues . push ( {
350- type : AST_TYPES . CALC ,
351- isNumber : false ,
352- value : asciiLowercase ( itemValue ) ,
392+ values . push ( {
393+ type,
353394 name,
395+ value : asciiLowercase ( itemValue ) ,
354396 raw
355397 } ) ;
356398 }
357- } else {
358- parsedValues . push ( {
359- type,
360- name,
361- value : asciiLowercase ( itemValue ) ,
362- raw
363- } ) ;
399+ break ;
364400 }
365- break ;
366- }
367- case AST_TYPES . IDENTIFIER : {
368- if ( caseSensitive ) {
369- parsedValues . push ( item ) ;
370- } else {
371- parsedValues . push ( {
372- type,
373- name : asciiLowercase ( name )
374- } ) ;
401+ case AST_TYPES . IDENTIFIER : {
402+ if ( caseSensitive ) {
403+ values . push ( item ) ;
404+ } else {
405+ values . push ( {
406+ type,
407+ name : asciiLowercase ( name )
408+ } ) ;
409+ }
410+ break ;
411+ }
412+ default : {
413+ values . push ( item ) ;
375414 }
376- break ;
377- }
378- default : {
379- parsedValues . push ( item ) ;
380415 }
381416 }
417+ parsedValue = values ;
418+ } else {
419+ parsedValue = val ;
382420 }
383- return parsedValues ;
421+ } catch {
422+ parsedValue = false ;
384423 }
385- } catch {
424+ }
425+ lruCache . set ( cacheKey , parsedValue ) ;
426+ if ( parsedValue === false ) {
386427 return ;
387428 }
388- return val ;
429+ return parsedValue ;
389430} ;
390431
391432/**
0 commit comments