Skip to content

Commit

Permalink
Feat: only optimize by occurrence
Browse files Browse the repository at this point in the history
  • Loading branch information
JPeer264 committed Aug 9, 2020
1 parent b373ed6 commit ee722b6
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 38 deletions.
144 changes: 132 additions & 12 deletions __tests__/optimize/optimize.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
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');
Expand All @@ -13,13 +7,34 @@ beforeEach(() => {
rcs.keyframesLibrary.reset();
rcs.selectorsLibrary.setAlphabet('#abcdefghijklmnopqrstuvwxyz');
rcs.selectorsLibrary.reset();
});

test('should ignore everything without selectors', () => {
const input = { };

rcs.mapping.load(input);
rcs.optimize();

expect(rcs.mapping.generate()).toEqual(input);
});

test('should ignore when no statistics are available', () => {
const input = {
selectors: {
'#test': 'a',
'.ca': 'a',
'.ba': 'b',
'.aa': 'c',
},
};

mockedSortSelectors.mockImplementation((array) => (
array.map(([selector]) => selector).sort()
));
rcs.mapping.load(input);
rcs.optimize();

expect(rcs.mapping.generate()).toEqual(input);
});

test('should optimize', () => {
test('should optimize without usageCount', () => {
rcs.mapping.load({
selectors: {
'#test': 'a',
Expand Down Expand Up @@ -56,9 +71,114 @@ test('should optimize', () => {
expect(rcs.mapping.generate()).toEqual({
selectors: {
'#test': 'a',
'.aa': 'a',
'.aa': 'c',
'.ba': 'b',
'.ca': 'c',
'.ca': 'a',
},
});
});

test('should optimize with usageCount', () => {
rcs.mapping.load({
selectors: {
'#test': 'a',
'.ca': 'a',
'.ba': 'b',
'.aa': 'c',
},
});
rcs.statistics.load({
ids: {
unused: [],
usageCount: {
test: 2,
},
},
classes: {
unused: [],
usageCount: {
ca: 3,
ba: 10,
},
},
keyframes: {
unused: [],
usageCount: {},
},
cssVariables: {
unused: [],
usageCount: {},
},
});

rcs.optimize();

expect(rcs.mapping.generate()).toEqual({
selectors: {
'#test': 'a',
'.aa': 'c',
'.ba': 'a',
'.ca': 'b',
},
});
});

test('should optimize everything', () => {
rcs.mapping.load({
selectors: {
'#a-first': 'a',
'#b-second': 'b',
'.a-first': 'a',
'.b-second': 'b',
'@a-first': 'a',
'@b-second': 'b',
'-a-first': 'a',
'-b-second': 'b',
},
});
rcs.statistics.load({
ids: {
unused: [],
usageCount: {
'a-first': 1,
'b-second': 3,
},
},
classes: {
unused: [],
usageCount: {
'a-first': 1,
'b-second': 3,
},
},
keyframes: {
unused: [],
usageCount: {
'a-first': 1,
'b-second': 3,
},
},
cssVariables: {
unused: [],
usageCount: {
'a-first': 1,
'b-second': 3,
},
},
});

rcs.optimize();

expect(rcs.mapping.generate()).toEqual({
selectors: {
'#a-first': 'b',
'#b-second': 'a',
'.a-first': 'b',
'.b-second': 'a',
'@a-first': 'b',
'@b-second': 'a',
'-a-first': 'b',
'-b-second': 'a',
},
});
});
37 changes: 34 additions & 3 deletions __tests__/optimize/sortSelectors.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import sortSelectors from '../../lib/optimize/sortSelectors';

test('should ignore usage count for sorting', () => {
const result = sortSelectors(
['my-selector', 'should-be-here'],
{
unused: [],
usageCount: {
'not-available-selector': 14,
'my-selector': 2,
},
},
);

expect(result).toEqual(['my-selector', 'should-be-here']);
});

test('should optimize', () => {
const result = sortSelectors(
[],
['short-selector', 'a-very-long-selector'],
{
unused: [],
usageCount: {
Expand All @@ -15,9 +30,9 @@ test('should optimize', () => {
expect(result).toEqual(['short-selector', 'a-very-long-selector']);
});

test('should optimize', () => {
test('sort by occurrence count', () => {
const result = sortSelectors(
[],
['short-selector', 'a-very-long-selector'],
{
unused: [],
usageCount: {
Expand All @@ -29,3 +44,19 @@ test('should optimize', () => {

expect(result).toEqual(['a-very-long-selector', 'short-selector']);
});

test('should sort three by occurrence coun', () => {
const result = sortSelectors(
['first-id', 'second-id', 'third-id'],
{
unused: [],
usageCount: {
'first-id': 25,
'second-id': 32,
'third-id': 29,
},
},
);

expect(result).toEqual(['second-id', 'third-id', 'first-id']);
});
2 changes: 1 addition & 1 deletion lib/optimize/optimize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const optimize = (): void => {
// optimize each library
Object.entries(separateMapping).forEach(([key, selectors]) => {
const statisticsData = statistics[key as 'ids'];
const newSortedSelectors = sortSelectors(selectors, statisticsData);
const newSortedSelectors = sortSelectors(selectors.map(([s]) => s), statisticsData);

optimizedMapping[key as 'ids'] = newSortedSelectors;
});
Expand Down
26 changes: 4 additions & 22 deletions lib/optimize/sortSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,20 @@ 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
* Sort them by occorrunce count
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
const sortSelectors = (selectors: string[][], statistic: Statistic): string[] => {
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],
], [])
const weightedSelectors = selectors
.map((selector) => [selector, usageCount[selector] || 0] as const)
// sort based on weights
.sort((a, b) => (
b[1] - a[1]
Expand Down

0 comments on commit ee722b6

Please sign in to comment.