Skip to content

Commit

Permalink
Merge pull request #287 from core-ds/feature/postcss-perfom
Browse files Browse the repository at this point in the history
Добавлен кастомный postcss плагин postcss-global-variables
  • Loading branch information
VladislavNsk authored Dec 5, 2024
2 parents 54300a7 + 53896eb commit 5d15dcf
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 21 deletions.
26 changes: 26 additions & 0 deletions .changeset/quick-starfishes-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'arui-scripts': minor
---

Добавлен кастомный плагин postcss-global-variables, для оптимизации времени обработки глобальных переменных.
@csstools/postcss-global-data *удален*

Проекты, которые использовали в оверрайдах кастомные настройки для плагина @csstools/postcss-global-data, должны перейти на использование postcss-global-variables следующим образом
```
postcss: (config) => {
const overrideConfig = config.map((plugin) => {
if (plugin.name === 'postCssGlobalVariables') {
return {
...plugin,
options: plugin.options.concat([
// ваши файлы
])
}
}
return plugin;
});
return overrideConfig;
}
```
Плагин работает только с глобальными переменными, если вам надо вставить что-то другое, отличное от глобальных переменных, вам нужно будет добавить @csstools/postcss-global-data в свой проект самостоятельно
1 change: 0 additions & 1 deletion packages/arui-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
"@babel/runtime": "^7.23.8",
"@csstools/postcss-global-data": "^2.0.1",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@swc/core": "^1.7.35",
"@swc/jest": "^0.2.36",
Expand Down
32 changes: 23 additions & 9 deletions packages/arui-scripts/src/configs/postcss.config.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import path from 'path';

import type { PluginCreator } from 'postcss';

import { postCssGlobalVariables } from '../plugins/postcss-global-variables/postcss-global-variables';

import config from './app-configs';
import supportingBrowsers from './supporting-browsers';

type PostCssPluginName = string | PluginCreator<any>;
type PostcssPlugin = string | [string, unknown] | {name: string; plugin: PluginCreator<any>; options?: unknown};

/**
* Функция для создания конфигурационного файла postcss
* @param {String[]} plugins список плагинов
* @param {PostCssPluginName[]} plugins список плагинов
* @param {Object} options коллекция конфигураций плагинов, где ключ - название плагина, а значение - аргумент для инициализации
* @returns {*}
*/
export function createPostcssConfig(
plugins: string[],
plugins: PostCssPluginName[],
options: Record<string, unknown>,
): string[] | unknown[] {
): PostcssPlugin[] {
return plugins.map((pluginName) => {
if (pluginName in options) {
return [pluginName, options[pluginName]];
if (typeof pluginName === 'string') {
return pluginName in options
? [pluginName, options[pluginName]]
: pluginName;
}

return pluginName;
return {
name: pluginName.name,
plugin: pluginName,
options: options[pluginName.name]
}
});
}

Expand All @@ -28,7 +42,7 @@ export const postcssPlugins = [
'postcss-mixins',
'postcss-for',
'postcss-each',
'@csstools/postcss-global-data',
postCssGlobalVariables,
'postcss-custom-media',
'postcss-color-mod-function',
!config.keepCssVars && 'postcss-custom-properties',
Expand All @@ -40,13 +54,13 @@ export const postcssPlugins = [
'autoprefixer',
'postcss-inherit',
'postcss-discard-comments',
].filter(Boolean) as string[];
].filter(Boolean) as PostCssPluginName[];

export const postcssPluginsOptions = {
'postcss-import': {
path: ['./src'],
},
'@csstools/postcss-global-data': {
'postCssGlobalVariables': {
files: [path.join(__dirname, 'mq.css'), config.componentsTheme].filter(Boolean) as string[],
},
'postcss-url': {
Expand Down
6 changes: 5 additions & 1 deletion packages/arui-scripts/src/configs/postcss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { createPostcssConfig, postcssPlugins, postcssPluginsOptions } from './po
const postcssConfig = applyOverrides(
'postcss',
createPostcssConfig(postcssPlugins, postcssPluginsOptions),
);
// тк дается возможность переопределять options для плагинов импортируемых напрямую
// инициализировать их нужно после оверайдов
).map((plugin) => typeof plugin === 'string' || Array.isArray(plugin)
? plugin
: plugin.plugin(plugin.options));

export default postcssConfig;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { AtRule, Plugin, PluginCreator, Rule } from 'postcss';

import { insertParsedCss, parseImport, parseMediaQuery, parseVariables } from './utils/utils';

type PluginOptions = {
files?: string[];
};

const postCssGlobalVariables: PluginCreator<PluginOptions> = (opts?: PluginOptions) => {
const options = {
files: [],
...opts,
};

const parsedVariables: Record<string, string> = {};
const parsedCustomMedia: Record<string, AtRule> = {};

let rulesSelectors = new Set<Rule>();

return {
postcssPlugin: '@alfalab/postcss-global-variables',
prepare(): Plugin {
return {
postcssPlugin: '@alfalab/postcss-global-variables',
Once(root, postcssHelpers): void {
if (!Object.keys(parsedVariables).length) {
options.files.forEach((filePath) => {
const importedCss = parseImport(root, postcssHelpers, filePath);

parseVariables(importedCss, parsedVariables);
parseMediaQuery(importedCss, parsedCustomMedia);
});
}

const rootRule = insertParsedCss(root, parsedVariables, parsedCustomMedia);

root.append(rootRule);
rulesSelectors.add(rootRule)
},
OnceExit(): void {
rulesSelectors.forEach((rule) => {
rule.remove();
});
rulesSelectors = new Set<Rule>();
},
};
},
};
};

postCssGlobalVariables.postcss = true;

export { postCssGlobalVariables };
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Rule } from 'postcss';

import { addGlobalVariable } from '../utils';

describe('addGlobalVariable', () => {
it('Должен добавлять переменные, найденные в cssValue, в rootSelector', () => {
const mockRootSelector = new Rule({ selector: ':root' });
const parsedVariables = {
'--color-primary': '#ff0000',
};

addGlobalVariable('var(--color-primary)', mockRootSelector, parsedVariables);

expect(mockRootSelector.nodes).toMatchObject([
{ prop: '--color-primary', value: '#ff0000' }
]);
});

it('Должен рекурсивно добавлять вложенные переменные', () => {
const mockRootSelector = new Rule({ selector: ':root' });
const mockRootSelectorWithSpace = new Rule({ selector: ':root' });
const mockRootSelectorWithNewLine = new Rule({ selector: ':root' });

const parsedVariables = {
'--color-primary': 'var(--color-secondary)',
'--color-secondary': '#00ff00',
};

const parsedVariablesWithSpace = {
'--color-primary': 'var( --color-secondary )',
'--color-secondary': '#00ff00',
};

const parsedVariablesWithNewLine = {
'--color-primary': 'var(\n --color-secondary\n )',
'--color-secondary': '#00ff00',
};

addGlobalVariable('var(--color-primary)', mockRootSelector, parsedVariables);
addGlobalVariable('var(--color-primary)', mockRootSelectorWithSpace, parsedVariablesWithSpace);
addGlobalVariable('var(--color-primary)', mockRootSelectorWithNewLine, parsedVariablesWithNewLine);

expect(mockRootSelector.nodes).toMatchObject([
{ prop: '--color-primary', value: 'var(--color-secondary)' },
{ prop: '--color-secondary', value: '#00ff00' },
]);

expect(mockRootSelectorWithSpace.nodes).toMatchObject([
{ prop: '--color-primary', value: 'var( --color-secondary )' },
{ prop: '--color-secondary', value: '#00ff00' },
]);

expect(mockRootSelectorWithNewLine.nodes).toMatchObject([
{ prop: '--color-primary', value: 'var(\n --color-secondary\n )' },
{ prop: '--color-secondary', value: '#00ff00' },
]);
});

it('Не должен добавлять переменные, если их нет в parsedVariables', () => {
const mockRootSelector = new Rule({ selector: ':root' });
const parsedVariables = {
'color-primary': '#ff0000',
};

addGlobalVariable('var(--color-secondary)', mockRootSelector, parsedVariables);

expect(mockRootSelector.nodes).toEqual([]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { AtRule } from 'postcss';

import { getMediaQueryName, } from '../utils';

describe('getMediaQueryName', () => {
it('Должен возвращать имя медиа-запроса', () => {
const rule: AtRule = { params: 'screen and (min-width: 768px)' } as AtRule;

expect(getMediaQueryName(rule)).toBe('screen');
});

it('Должен возвращать пустую строку, если params пустой', () => {
const rule: AtRule = { params: '' } as AtRule;

expect(getMediaQueryName(rule)).toBe('');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Declaration,Root } from 'postcss';

import { parseVariables } from '../utils';

describe('parseVariables', () => {
it('Должен корректно заполнять объект переменными на основе импортируемого файла', () => {
const parsedVariables: Record<string, string> = {};

const mockImportedFile = {
walkDecls: (callback: (decl: Declaration, index: number) => false | void) => {
const mockDeclarations: Declaration[] = [
{ prop: '--color-primary', value: '#3498db' } as Declaration,
{ prop: '--font-size', value: 'var(--gap-24)' } as Declaration,
];

mockDeclarations.forEach(callback);

return false;
}
};

parseVariables(mockImportedFile as Root, parsedVariables);

expect(parsedVariables).toEqual({
'--color-primary': '#3498db',
'--font-size': 'var(--gap-24)',
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import fs from 'fs';
import path from 'path';

import { AtRule, Declaration, Helpers, Root, Rule } from 'postcss';

export const getMediaQueryName = (rule: AtRule) => rule.params.split(' ')[0];

export function parseImport(root: Root, postcssHelpers: Helpers, filePath: string) {
let resolvedPath = '';

try {
resolvedPath = path.resolve(filePath);
} catch (err) {
throw new Error(`Failed to read ${filePath} with error ${(err instanceof Error) ? err.message : err}`);
}

postcssHelpers.result.messages.push({
type: 'dependency',
plugin: 'postcss-global-environments',
file: resolvedPath,
parent: root.source?.input?.file,
});

const fileContents = fs.readFileSync(resolvedPath, 'utf8');

return postcssHelpers.postcss.parse(fileContents, { from: resolvedPath });
}

export const parseVariables = (importedFile: Root, parsedVariables: Record<string, string>) => {
importedFile.walkDecls((decl) => {
// eslint-disable-next-line no-param-reassign
parsedVariables[decl.prop] = decl.value;
});
};

export const parseMediaQuery = (importedFile: Root, parsedCustomMedia: Record<string, AtRule>) => {
importedFile.walkAtRules('custom-media', (mediaRule) => {
const mediaName = getMediaQueryName(mediaRule);

// eslint-disable-next-line no-param-reassign
parsedCustomMedia[mediaName] = mediaRule;
});
};

export function addGlobalVariable(cssValue: string, rootSelector: Rule, parsedVariables: Record<string, string>) {
const variableMatches = cssValue.match(/var\(\s*--([^)]+)\s*\)/g);

if (variableMatches) {
variableMatches.forEach((match) => {
// var(--gap-24) => --gap-24
const variableName = match.slice(4, -1).trim();

if (parsedVariables[variableName]) {
rootSelector.append(new Declaration({ prop: variableName, value: parsedVariables[variableName] }));

// Рекурсивно проходимся по значениям css, там тоже могут использоваться переменные
addGlobalVariable(parsedVariables[variableName], rootSelector, parsedVariables);
}
});
}
}

export const insertParsedCss = (root: Root, parsedVariables: Record<string, string>, parsedCustomMedia: Record<string, AtRule>): Rule => {
const rootRule = new Rule({ selector: ':root' });

root.walkDecls((decl) => {
addGlobalVariable(decl.value, rootRule, parsedVariables);
});

root.walkAtRules('media', (rule) => {
const mediaFullName = getMediaQueryName(rule);

if (mediaFullName.startsWith('(--')) {
const mediaName = mediaFullName.slice(1, -1);

if (parsedCustomMedia[mediaName]) {
root.append(parsedCustomMedia[mediaName]);
}
}
});

return rootRule;
};
10 changes: 0 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2741,15 +2741,6 @@ __metadata:
languageName: node
linkType: hard

"@csstools/postcss-global-data@npm:^2.0.1":
version: 2.0.1
resolution: "@csstools/postcss-global-data@npm:2.0.1"
peerDependencies:
postcss: ^8.4
checksum: 21e7057b7f527481c7374810c3b49a2d47414b39f4408c5d182bb6aab62d190da5b6173aa1accacb091994d1158cee2a651fbf2977957bbcf1fc654669886c1a
languageName: node
linkType: hard

"@csstools/postcss-gradients-interpolation-method@npm:^3.0.6":
version: 3.0.6
resolution: "@csstools/postcss-gradients-interpolation-method@npm:3.0.6"
Expand Down Expand Up @@ -6485,7 +6476,6 @@ __metadata:
"@babel/preset-react": ^7.23.3
"@babel/preset-typescript": ^7.23.3
"@babel/runtime": ^7.23.8
"@csstools/postcss-global-data": ^2.0.1
"@pmmmwh/react-refresh-webpack-plugin": 0.5.11
"@swc/core": ^1.7.35
"@swc/jest": ^0.2.36
Expand Down

0 comments on commit 5d15dcf

Please sign in to comment.