Skip to content

Commit 5d15dcf

Browse files
authored
Merge pull request #287 from core-ds/feature/postcss-perfom
Добавлен кастомный postcss плагин postcss-global-variables
2 parents 54300a7 + 53896eb commit 5d15dcf

10 files changed

Lines changed: 305 additions & 21 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'arui-scripts': minor
3+
---
4+
5+
Добавлен кастомный плагин postcss-global-variables, для оптимизации времени обработки глобальных переменных.
6+
@csstools/postcss-global-data *удален*
7+
8+
Проекты, которые использовали в оверрайдах кастомные настройки для плагина @csstools/postcss-global-data, должны перейти на использование postcss-global-variables следующим образом
9+
```
10+
postcss: (config) => {
11+
const overrideConfig = config.map((plugin) => {
12+
if (plugin.name === 'postCssGlobalVariables') {
13+
return {
14+
...plugin,
15+
options: plugin.options.concat([
16+
// ваши файлы
17+
])
18+
}
19+
}
20+
return plugin;
21+
});
22+
23+
return overrideConfig;
24+
}
25+
```
26+
Плагин работает только с глобальными переменными, если вам надо вставить что-то другое, отличное от глобальных переменных, вам нужно будет добавить @csstools/postcss-global-data в свой проект самостоятельно

packages/arui-scripts/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
"@babel/preset-react": "^7.23.3",
3737
"@babel/preset-typescript": "^7.23.3",
3838
"@babel/runtime": "^7.23.8",
39-
"@csstools/postcss-global-data": "^2.0.1",
4039
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
4140
"@swc/core": "^1.7.35",
4241
"@swc/jest": "^0.2.36",

packages/arui-scripts/src/configs/postcss.config.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
11
import path from 'path';
22

3+
import type { PluginCreator } from 'postcss';
4+
5+
import { postCssGlobalVariables } from '../plugins/postcss-global-variables/postcss-global-variables';
6+
37
import config from './app-configs';
48
import supportingBrowsers from './supporting-browsers';
9+
10+
type PostCssPluginName = string | PluginCreator<any>;
11+
type PostcssPlugin = string | [string, unknown] | {name: string; plugin: PluginCreator<any>; options?: unknown};
12+
513
/**
614
* Функция для создания конфигурационного файла postcss
7-
* @param {String[]} plugins список плагинов
15+
* @param {PostCssPluginName[]} plugins список плагинов
816
* @param {Object} options коллекция конфигураций плагинов, где ключ - название плагина, а значение - аргумент для инициализации
917
* @returns {*}
1018
*/
1119
export function createPostcssConfig(
12-
plugins: string[],
20+
plugins: PostCssPluginName[],
1321
options: Record<string, unknown>,
14-
): string[] | unknown[] {
22+
): PostcssPlugin[] {
1523
return plugins.map((pluginName) => {
16-
if (pluginName in options) {
17-
return [pluginName, options[pluginName]];
24+
if (typeof pluginName === 'string') {
25+
return pluginName in options
26+
? [pluginName, options[pluginName]]
27+
: pluginName;
1828
}
1929

20-
return pluginName;
30+
return {
31+
name: pluginName.name,
32+
plugin: pluginName,
33+
options: options[pluginName.name]
34+
}
2135
});
2236
}
2337

@@ -28,7 +42,7 @@ export const postcssPlugins = [
2842
'postcss-mixins',
2943
'postcss-for',
3044
'postcss-each',
31-
'@csstools/postcss-global-data',
45+
postCssGlobalVariables,
3246
'postcss-custom-media',
3347
'postcss-color-mod-function',
3448
!config.keepCssVars && 'postcss-custom-properties',
@@ -40,13 +54,13 @@ export const postcssPlugins = [
4054
'autoprefixer',
4155
'postcss-inherit',
4256
'postcss-discard-comments',
43-
].filter(Boolean) as string[];
57+
].filter(Boolean) as PostCssPluginName[];
4458

4559
export const postcssPluginsOptions = {
4660
'postcss-import': {
4761
path: ['./src'],
4862
},
49-
'@csstools/postcss-global-data': {
63+
'postCssGlobalVariables': {
5064
files: [path.join(__dirname, 'mq.css'), config.componentsTheme].filter(Boolean) as string[],
5165
},
5266
'postcss-url': {

packages/arui-scripts/src/configs/postcss.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { createPostcssConfig, postcssPlugins, postcssPluginsOptions } from './po
44
const postcssConfig = applyOverrides(
55
'postcss',
66
createPostcssConfig(postcssPlugins, postcssPluginsOptions),
7-
);
7+
// тк дается возможность переопределять options для плагинов импортируемых напрямую
8+
// инициализировать их нужно после оверайдов
9+
).map((plugin) => typeof plugin === 'string' || Array.isArray(plugin)
10+
? plugin
11+
: plugin.plugin(plugin.options));
812

913
export default postcssConfig;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { AtRule, Plugin, PluginCreator, Rule } from 'postcss';
2+
3+
import { insertParsedCss, parseImport, parseMediaQuery, parseVariables } from './utils/utils';
4+
5+
type PluginOptions = {
6+
files?: string[];
7+
};
8+
9+
const postCssGlobalVariables: PluginCreator<PluginOptions> = (opts?: PluginOptions) => {
10+
const options = {
11+
files: [],
12+
...opts,
13+
};
14+
15+
const parsedVariables: Record<string, string> = {};
16+
const parsedCustomMedia: Record<string, AtRule> = {};
17+
18+
let rulesSelectors = new Set<Rule>();
19+
20+
return {
21+
postcssPlugin: '@alfalab/postcss-global-variables',
22+
prepare(): Plugin {
23+
return {
24+
postcssPlugin: '@alfalab/postcss-global-variables',
25+
Once(root, postcssHelpers): void {
26+
if (!Object.keys(parsedVariables).length) {
27+
options.files.forEach((filePath) => {
28+
const importedCss = parseImport(root, postcssHelpers, filePath);
29+
30+
parseVariables(importedCss, parsedVariables);
31+
parseMediaQuery(importedCss, parsedCustomMedia);
32+
});
33+
}
34+
35+
const rootRule = insertParsedCss(root, parsedVariables, parsedCustomMedia);
36+
37+
root.append(rootRule);
38+
rulesSelectors.add(rootRule)
39+
},
40+
OnceExit(): void {
41+
rulesSelectors.forEach((rule) => {
42+
rule.remove();
43+
});
44+
rulesSelectors = new Set<Rule>();
45+
},
46+
};
47+
},
48+
};
49+
};
50+
51+
postCssGlobalVariables.postcss = true;
52+
53+
export { postCssGlobalVariables };
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Rule } from 'postcss';
2+
3+
import { addGlobalVariable } from '../utils';
4+
5+
describe('addGlobalVariable', () => {
6+
it('Должен добавлять переменные, найденные в cssValue, в rootSelector', () => {
7+
const mockRootSelector = new Rule({ selector: ':root' });
8+
const parsedVariables = {
9+
'--color-primary': '#ff0000',
10+
};
11+
12+
addGlobalVariable('var(--color-primary)', mockRootSelector, parsedVariables);
13+
14+
expect(mockRootSelector.nodes).toMatchObject([
15+
{ prop: '--color-primary', value: '#ff0000' }
16+
]);
17+
});
18+
19+
it('Должен рекурсивно добавлять вложенные переменные', () => {
20+
const mockRootSelector = new Rule({ selector: ':root' });
21+
const mockRootSelectorWithSpace = new Rule({ selector: ':root' });
22+
const mockRootSelectorWithNewLine = new Rule({ selector: ':root' });
23+
24+
const parsedVariables = {
25+
'--color-primary': 'var(--color-secondary)',
26+
'--color-secondary': '#00ff00',
27+
};
28+
29+
const parsedVariablesWithSpace = {
30+
'--color-primary': 'var( --color-secondary )',
31+
'--color-secondary': '#00ff00',
32+
};
33+
34+
const parsedVariablesWithNewLine = {
35+
'--color-primary': 'var(\n --color-secondary\n )',
36+
'--color-secondary': '#00ff00',
37+
};
38+
39+
addGlobalVariable('var(--color-primary)', mockRootSelector, parsedVariables);
40+
addGlobalVariable('var(--color-primary)', mockRootSelectorWithSpace, parsedVariablesWithSpace);
41+
addGlobalVariable('var(--color-primary)', mockRootSelectorWithNewLine, parsedVariablesWithNewLine);
42+
43+
expect(mockRootSelector.nodes).toMatchObject([
44+
{ prop: '--color-primary', value: 'var(--color-secondary)' },
45+
{ prop: '--color-secondary', value: '#00ff00' },
46+
]);
47+
48+
expect(mockRootSelectorWithSpace.nodes).toMatchObject([
49+
{ prop: '--color-primary', value: 'var( --color-secondary )' },
50+
{ prop: '--color-secondary', value: '#00ff00' },
51+
]);
52+
53+
expect(mockRootSelectorWithNewLine.nodes).toMatchObject([
54+
{ prop: '--color-primary', value: 'var(\n --color-secondary\n )' },
55+
{ prop: '--color-secondary', value: '#00ff00' },
56+
]);
57+
});
58+
59+
it('Не должен добавлять переменные, если их нет в parsedVariables', () => {
60+
const mockRootSelector = new Rule({ selector: ':root' });
61+
const parsedVariables = {
62+
'color-primary': '#ff0000',
63+
};
64+
65+
addGlobalVariable('var(--color-secondary)', mockRootSelector, parsedVariables);
66+
67+
expect(mockRootSelector.nodes).toEqual([]);
68+
});
69+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { AtRule } from 'postcss';
2+
3+
import { getMediaQueryName, } from '../utils';
4+
5+
describe('getMediaQueryName', () => {
6+
it('Должен возвращать имя медиа-запроса', () => {
7+
const rule: AtRule = { params: 'screen and (min-width: 768px)' } as AtRule;
8+
9+
expect(getMediaQueryName(rule)).toBe('screen');
10+
});
11+
12+
it('Должен возвращать пустую строку, если params пустой', () => {
13+
const rule: AtRule = { params: '' } as AtRule;
14+
15+
expect(getMediaQueryName(rule)).toBe('');
16+
});
17+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { Declaration,Root } from 'postcss';
2+
3+
import { parseVariables } from '../utils';
4+
5+
describe('parseVariables', () => {
6+
it('Должен корректно заполнять объект переменными на основе импортируемого файла', () => {
7+
const parsedVariables: Record<string, string> = {};
8+
9+
const mockImportedFile = {
10+
walkDecls: (callback: (decl: Declaration, index: number) => false | void) => {
11+
const mockDeclarations: Declaration[] = [
12+
{ prop: '--color-primary', value: '#3498db' } as Declaration,
13+
{ prop: '--font-size', value: 'var(--gap-24)' } as Declaration,
14+
];
15+
16+
mockDeclarations.forEach(callback);
17+
18+
return false;
19+
}
20+
};
21+
22+
parseVariables(mockImportedFile as Root, parsedVariables);
23+
24+
expect(parsedVariables).toEqual({
25+
'--color-primary': '#3498db',
26+
'--font-size': 'var(--gap-24)',
27+
});
28+
});
29+
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
4+
import { AtRule, Declaration, Helpers, Root, Rule } from 'postcss';
5+
6+
export const getMediaQueryName = (rule: AtRule) => rule.params.split(' ')[0];
7+
8+
export function parseImport(root: Root, postcssHelpers: Helpers, filePath: string) {
9+
let resolvedPath = '';
10+
11+
try {
12+
resolvedPath = path.resolve(filePath);
13+
} catch (err) {
14+
throw new Error(`Failed to read ${filePath} with error ${(err instanceof Error) ? err.message : err}`);
15+
}
16+
17+
postcssHelpers.result.messages.push({
18+
type: 'dependency',
19+
plugin: 'postcss-global-environments',
20+
file: resolvedPath,
21+
parent: root.source?.input?.file,
22+
});
23+
24+
const fileContents = fs.readFileSync(resolvedPath, 'utf8');
25+
26+
return postcssHelpers.postcss.parse(fileContents, { from: resolvedPath });
27+
}
28+
29+
export const parseVariables = (importedFile: Root, parsedVariables: Record<string, string>) => {
30+
importedFile.walkDecls((decl) => {
31+
// eslint-disable-next-line no-param-reassign
32+
parsedVariables[decl.prop] = decl.value;
33+
});
34+
};
35+
36+
export const parseMediaQuery = (importedFile: Root, parsedCustomMedia: Record<string, AtRule>) => {
37+
importedFile.walkAtRules('custom-media', (mediaRule) => {
38+
const mediaName = getMediaQueryName(mediaRule);
39+
40+
// eslint-disable-next-line no-param-reassign
41+
parsedCustomMedia[mediaName] = mediaRule;
42+
});
43+
};
44+
45+
export function addGlobalVariable(cssValue: string, rootSelector: Rule, parsedVariables: Record<string, string>) {
46+
const variableMatches = cssValue.match(/var\(\s*--([^)]+)\s*\)/g);
47+
48+
if (variableMatches) {
49+
variableMatches.forEach((match) => {
50+
// var(--gap-24) => --gap-24
51+
const variableName = match.slice(4, -1).trim();
52+
53+
if (parsedVariables[variableName]) {
54+
rootSelector.append(new Declaration({ prop: variableName, value: parsedVariables[variableName] }));
55+
56+
// Рекурсивно проходимся по значениям css, там тоже могут использоваться переменные
57+
addGlobalVariable(parsedVariables[variableName], rootSelector, parsedVariables);
58+
}
59+
});
60+
}
61+
}
62+
63+
export const insertParsedCss = (root: Root, parsedVariables: Record<string, string>, parsedCustomMedia: Record<string, AtRule>): Rule => {
64+
const rootRule = new Rule({ selector: ':root' });
65+
66+
root.walkDecls((decl) => {
67+
addGlobalVariable(decl.value, rootRule, parsedVariables);
68+
});
69+
70+
root.walkAtRules('media', (rule) => {
71+
const mediaFullName = getMediaQueryName(rule);
72+
73+
if (mediaFullName.startsWith('(--')) {
74+
const mediaName = mediaFullName.slice(1, -1);
75+
76+
if (parsedCustomMedia[mediaName]) {
77+
root.append(parsedCustomMedia[mediaName]);
78+
}
79+
}
80+
});
81+
82+
return rootRule;
83+
};

yarn.lock

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2741,15 +2741,6 @@ __metadata:
27412741
languageName: node
27422742
linkType: hard
27432743

2744-
"@csstools/postcss-global-data@npm:^2.0.1":
2745-
version: 2.0.1
2746-
resolution: "@csstools/postcss-global-data@npm:2.0.1"
2747-
peerDependencies:
2748-
postcss: ^8.4
2749-
checksum: 21e7057b7f527481c7374810c3b49a2d47414b39f4408c5d182bb6aab62d190da5b6173aa1accacb091994d1158cee2a651fbf2977957bbcf1fc654669886c1a
2750-
languageName: node
2751-
linkType: hard
2752-
27532744
"@csstools/postcss-gradients-interpolation-method@npm:^3.0.6":
27542745
version: 3.0.6
27552746
resolution: "@csstools/postcss-gradients-interpolation-method@npm:3.0.6"
@@ -6485,7 +6476,6 @@ __metadata:
64856476
"@babel/preset-react": ^7.23.3
64866477
"@babel/preset-typescript": ^7.23.3
64876478
"@babel/runtime": ^7.23.8
6488-
"@csstools/postcss-global-data": ^2.0.1
64896479
"@pmmmwh/react-refresh-webpack-plugin": 0.5.11
64906480
"@swc/core": ^1.7.35
64916481
"@swc/jest": ^0.2.36

0 commit comments

Comments
 (0)