-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
306 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import rcs from '../../lib'; | ||
import sortSelectors from '../../lib/optimize/sortSelectors'; | ||
|
||
jest.mock('../../lib/optimize/sortSelectors'); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const mockedSortSelectors = (sortSelectors as any) as jest.Mock<typeof sortSelectors>; | ||
|
||
beforeEach(() => { | ||
rcs.cssVariablesLibrary.setAlphabet('#abcdefghijklmnopqrstuvwxyz'); | ||
rcs.cssVariablesLibrary.reset(); | ||
rcs.keyframesLibrary.setAlphabet('#abcdefghijklmnopqrstuvwxyz'); | ||
rcs.keyframesLibrary.reset(); | ||
rcs.selectorsLibrary.setAlphabet('#abcdefghijklmnopqrstuvwxyz'); | ||
rcs.selectorsLibrary.reset(); | ||
|
||
mockedSortSelectors.mockImplementation((array) => ( | ||
array.map(([selector]) => selector).sort() | ||
)); | ||
}); | ||
|
||
test('should optimize', () => { | ||
rcs.mapping.load({ | ||
selectors: { | ||
'#test': 'a', | ||
'.ca': 'a', | ||
'.ba': 'b', | ||
'.aa': 'c', | ||
}, | ||
}); | ||
rcs.statistics.load({ | ||
ids: { | ||
unused: [], | ||
usageCount: { | ||
test: 2, | ||
}, | ||
}, | ||
classes: { | ||
unused: [], | ||
usageCount: { | ||
'my-selector': 2, | ||
}, | ||
}, | ||
keyframes: { | ||
unused: [], | ||
usageCount: {}, | ||
}, | ||
cssVariables: { | ||
unused: [], | ||
usageCount: {}, | ||
}, | ||
}); | ||
|
||
rcs.optimize(); | ||
|
||
expect(rcs.mapping.generate()).toEqual({ | ||
selectors: { | ||
'#test': 'a', | ||
'.aa': 'a', | ||
'.ba': 'b', | ||
'.ca': 'c', | ||
}, | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import separateMappingSelectors from '../../lib/optimize/separateMappingSelectors'; | ||
|
||
test('should do nothing', () => { | ||
expect(separateMappingSelectors()).toEqual({ | ||
cssVariables: [], | ||
keyframes: [], | ||
classes: [], | ||
ids: [], | ||
}); | ||
}); | ||
|
||
test('should separate selectors correctly', () => { | ||
expect(separateMappingSelectors({ | ||
'.test': 'a', | ||
'#my-id': 'a', | ||
'#my-other-id': 'b', | ||
})).toEqual({ | ||
cssVariables: [], | ||
keyframes: [], | ||
classes: [['test', 'a']], | ||
ids: [['my-id', 'a'], ['my-other-id', 'b']], | ||
}); | ||
}); | ||
|
||
test('should separate selectors, keyframes and cssVariables correctly', () => { | ||
expect(separateMappingSelectors({ | ||
'.test': 'a', | ||
'#my-id': 'a', | ||
'#my-other-id': 'b', | ||
'-my-var': 'a', | ||
'@my-keyframe': 'a', | ||
})).toEqual({ | ||
cssVariables: [['my-var', 'a']], | ||
keyframes: [['my-keyframe', 'a']], | ||
classes: [['test', 'a']], | ||
ids: [['my-id', 'a'], ['my-other-id', 'b']], | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import sortSelectors from '../../lib/optimize/sortSelectors'; | ||
|
||
test('should optimize', () => { | ||
const result = sortSelectors( | ||
[], | ||
{ | ||
unused: [], | ||
usageCount: { | ||
'short-selector': 14, | ||
'a-very-long-selector': 2, | ||
}, | ||
}, | ||
); | ||
|
||
expect(result).toEqual(['short-selector', 'a-very-long-selector']); | ||
}); | ||
|
||
test('should optimize', () => { | ||
const result = sortSelectors( | ||
[], | ||
{ | ||
unused: [], | ||
usageCount: { | ||
'short-selector': 2, | ||
'a-very-long-selector': 14, | ||
}, | ||
}, | ||
); | ||
|
||
expect(result).toEqual(['a-very-long-selector', 'short-selector']); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import optimize from './optimize'; | ||
|
||
export default optimize; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import generateMapping from '../mapping/generate'; | ||
import generateStatistics from '../statistics/generate'; | ||
import { getStatistics } from '../statistics'; | ||
import loadMapping from '../mapping/load'; | ||
import selectorsLibrary from '../selectorsLibrary'; | ||
import keyframesLibrary from '../keyframesLibrary'; | ||
import cssVariablesLibrary from '../cssVariablesLibrary'; | ||
import separateMappingSelectors from './separateMappingSelectors'; | ||
import sortSelectors from './sortSelectors'; | ||
|
||
const optimize = (): void => { | ||
const mapping = generateMapping(); | ||
const statistics = getStatistics(); | ||
|
||
if (!statistics) { | ||
return; | ||
} | ||
|
||
// sorting | ||
// renaming into new mapping | ||
const separateMapping = separateMappingSelectors(mapping.selectors); | ||
const optimizedMapping: { [key in keyof ReturnType<typeof generateStatistics>]?: string[] } = {}; | ||
|
||
// optimize each library | ||
Object.entries(separateMapping).forEach(([key, selectors]) => { | ||
const statisticsData = statistics[key as 'ids']; | ||
const newSortedSelectors = sortSelectors(selectors, statisticsData); | ||
|
||
optimizedMapping[key as 'ids'] = newSortedSelectors; | ||
}); | ||
|
||
// could be a cold start but | ||
// reset everything just in case | ||
selectorsLibrary.reset(); | ||
keyframesLibrary.reset(); | ||
cssVariablesLibrary.reset(); | ||
|
||
// fill libraries with optimized mapping | ||
Object.entries(optimizedMapping).forEach(([key, selectors]) => { | ||
if (!selectors) { | ||
return; | ||
} | ||
|
||
switch (key) { | ||
case 'ids': | ||
selectors.forEach((selector) => selectorsLibrary.set(`#${selector}`)); | ||
|
||
break; | ||
|
||
case 'keyframes': | ||
selectors.forEach((selector) => keyframesLibrary.set(`@${selector}`)); | ||
|
||
break; | ||
|
||
case 'cssVariables': | ||
selectors.forEach((selector) => cssVariablesLibrary.set(`--${selector}`)); | ||
|
||
break; | ||
|
||
case 'classes': | ||
default: | ||
selectors.forEach((selector) => selectorsLibrary.set(`.${selector}`)); | ||
} | ||
}); | ||
|
||
// load with the same attribute selectors | ||
// these cannot be optimized yet | ||
loadMapping({ attributeSelectors: mapping.attributeSelectors }); | ||
}; | ||
|
||
export default optimize; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import generate from '../mapping/generate'; | ||
import generateStatistics from '../statistics/generate'; | ||
|
||
type Result = { [key in keyof ReturnType<typeof generateStatistics>]: string[][] }; | ||
|
||
const separateMappingSelectors = (mappingSelectors: ReturnType<typeof generate>['selectors'] = {}): Result => { | ||
const selectors = Object.entries(mappingSelectors); | ||
const result: Result = { | ||
ids: [], | ||
classes: [], | ||
keyframes: [], | ||
cssVariables: [], | ||
}; | ||
|
||
selectors.forEach(([selector, renamedSelector]) => { | ||
const plainSelector = selector.slice(1); | ||
const toPush = [plainSelector, renamedSelector]; | ||
|
||
switch (selector.charAt(0)) { | ||
case '-': | ||
result.cssVariables.push(toPush); | ||
|
||
break; | ||
|
||
case '@': | ||
result.keyframes.push(toPush); | ||
|
||
break; | ||
|
||
case '#': | ||
result.ids.push(toPush); | ||
|
||
break; | ||
|
||
case '.': | ||
default: | ||
result.classes.push(toPush); | ||
} | ||
}); | ||
|
||
return result; | ||
}; | ||
|
||
export default separateMappingSelectors; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { Statistic } from '../statistics/generate'; | ||
|
||
/** | ||
* the main logic for the optimization | ||
* | ||
* returns a list of sorted selectors to rename | ||
* | ||
* weight selectors and sort them based on their weight | ||
* the weight (W) is length of the selector (L) | ||
* muliplied by the appearences of the selector (C) | ||
* | ||
* Formular: L * C = W | ||
* | ||
* Example with two selectors 'a-very-long-selector' and 'short-selector': | ||
* | ||
* Apperiances: | ||
* 'short-selector': 10 times | ||
* 'a-very-long-selector': 2 times | ||
* | ||
* Length: | ||
* 'short-selector': 14 | ||
* 'a-very-long-selector': 20 | ||
* | ||
* In this case 'short-selector' is used much more often | ||
* so multiplying with the length will output the total | ||
* amount of saved chars | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars | ||
const sortSelectors = (selectors: string[][], statistic: Statistic): string[] => { | ||
const { unused, usageCount } = statistic; | ||
|
||
const weightedSelectors = Object.entries(usageCount) | ||
// generate weights | ||
.reduce<[string, number][]>((prev, [selector, count]) => [ | ||
...prev, | ||
[selector, selector.length * count], | ||
], []) | ||
// sort based on weights | ||
.sort((a, b) => ( | ||
b[1] - a[1] | ||
)) | ||
.map(([selector]) => selector); | ||
|
||
// put unsuded to the end | ||
return [...weightedSelectors, ...unused]; | ||
}; | ||
|
||
export default sortSelectors; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters