Skip to content

Commit

Permalink
feat: Add keyframes renaming (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
ardalanamini authored Jul 22, 2022
1 parent b733eb5 commit 043cdd6
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 37 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@ module.exports = {

### Options

| Name | Type | Default | Description |
|:-------------:|:-------:|:-------:|:-----------------------------------------------------------:|
| classes | boolean | `true` | Should the plugin rename css classes |
| ids | boolean | `true` | Should the plugin rename css ids |
| variables | boolean | `true` | Should the plugin rename css variables |
| outputMapFile | string | - | Where to write the output map to be used outside the plugin |
| Name | Type | Default | Description |
|:-------------:|:-------:|:-------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
| classes | boolean | `true` | Should the plugin rename css classes |
| ids | boolean | `true` | Should the plugin rename css ids |
| keyframes | boolean | `true` | Should the plugin rename css keyframes & animation names (in case of css `animation`, name should be either first parameter or last parameter if it starts with duration (a digit). if the name is a css variable then this won't work) |
| variables | boolean | `true` | Should the plugin rename css variables |
| outputMapFile | string | - | Where to write the output map to be used outside the plugin |

> `*` means the options is required.
Expand Down
35 changes: 32 additions & 3 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ it("should not rename css ids", async () => {
expect(result.css).toBe("#red { color: red; } #blue { color: blue; }");
});

it("should rename css keyframes", async () => {
const result = await postcss([minicss()])
.process(
"@keyframes color-change { from { color: blue; } to { color: red; } } .foo { animation-name: none; }",
{ from: undefined },
);

expect(result.css).toBe("@keyframes _ { from { color: blue; } to { color: red; } } ._ { animation-name: none; }");
});

it("should not rename css keyframes", async () => {
const result = await postcss([minicss({ keyframes: false })])
.process(
"@keyframes color-change { from { color: blue; } to { color: red; } } .foo { animation-name: none; }",
{ from: undefined },
);

expect(result.css)
.toBe("@keyframes color-change { from { color: blue; } to { color: red; } } ._ { animation-name: none; }");
});

it("should rename css variables", async () => {
const result = await postcss([minicss()])
.process(":root { --red: red; } .red { color: var(--red); }", { from: undefined });
Expand All @@ -59,13 +80,18 @@ it("should output the name map results at the provided file path", async () => {

const result = await postcss([minicss({ outputMapFile })])
.process(
":root { --red: red; } .red { color: var(--red); } .blue { color: blue; }"
+ " #red { color: red; } #blue { color: blue; }",
":root { --red: red; }"
+ " @keyframes color-change { from { color: blue; } to { color: red; } }"
+ " .red { color: var(--red); animation: none; } .blue { color: blue; animation: 1s color-change; }"
+ " #red { color: red; animation: color-change 1s; } #blue { color: blue; animation-name: color-change; }",
{ from: undefined },
);

expect(result.css)
.toBe(":root { --_: red; } ._ { color: var(--_); } .a { color: blue; } #_ { color: red; } #a { color: blue; }");
.toBe(":root { --_: red; }"
+ " @keyframes _ { from { color: blue; } to { color: red; } }"
+ " ._ { color: var(--_); animation: none; } .a { color: blue; animation: 1s _; }"
+ " #_ { color: red; animation: _ 1s; } #a { color: blue; animation-name: _; }");

expect(existsSync(outputMapFile)).toBe(true);

Expand All @@ -80,6 +106,9 @@ it("should output the name map results at the provided file path", async () => {
red : "_",
blue: "a",
},
keyframes: {
"color-change": "_",
},
variables: {
red: "_",
},
Expand Down
12 changes: 6 additions & 6 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
╔═══════════════════════════╤═══════════════════════════╤═══════════════════════════╤═══════════════════════════╗
║ CSS File │ Original Size │ Renamed Size │ Improvement ║
╟───────────────────────────┼───────────────────────────┼───────────────────────────┼───────────────────────────╢
║ antd-v4.21.7 │ 709.411 KB │ 486.881 KB │ 31.368% ║
║ antd-v4.21.7 │ 709.411 KB │ 482.816 KB │ 31.941% ║
╟───────────────────────────┼───────────────────────────┼───────────────────────────┼───────────────────────────╢
║ antd-v4.21.7.min │ 545.281 KB │ 323.31 KB │ 40.708% ║
║ antd-v4.21.7.min │ 545.281 KB │ 319.464 KB │ 41.413% ║
╟───────────────────────────┼───────────────────────────┼───────────────────────────┼───────────────────────────╢
║ bootstrap-v5.2.0-beta1 │ 231.991 KB │ 173.35 KB │ 25.278%
║ bootstrap-v5.2.0-beta1 │ 231.991 KB │ 173.111 KB │ 25.38%
╟───────────────────────────┼───────────────────────────┼───────────────────────────┼───────────────────────────╢
║ bootstrap-v5.2.0-beta1.mi │ 188.949 KB │ 130.308 KB │ 31.036% ║
║ bootstrap-v5.2.0-beta1.mi │ 188.949 KB │ 130.069 KB │ 31.162% ║
║ n │ │ │ ║
╟───────────────────────────┼───────────────────────────┼───────────────────────────┼───────────────────────────╢
║ materialize-v1.0.0 │ 175.044 KB │ 147.625 KB │ 15.664% ║
║ materialize-v1.0.0 │ 175.044 KB │ 146.938 KB │ 16.057% ║
╟───────────────────────────┼───────────────────────────┼───────────────────────────┼───────────────────────────╢
║ materialize-v1.0.0.min │ 138.517 KB │ 111.098 KB │ 19.795% ║
║ materialize-v1.0.0.min │ 138.517 KB │ 110.41 KB │ 20.291% ║
╚═══════════════════════════╧═══════════════════════════╧═══════════════════════════╧═══════════════════════════╝
```
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@
"test:ci": "npm run test:coverage -- --ci --verbose --no-cache"
},
"peerDependencies": {
"@minicss/core": "^1.0.0-alpha.1",
"@minicss/core": "^1.0.0-alpha.2",
"postcss": "^8.4.14"
},
"devDependencies": {
"@minicss/core": "^1.0.0-alpha.1",
"@minicss/core": "^1.0.0-alpha.2",
"@types/jest": "^28.1.6",
"@types/node": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.30.7",
Expand Down
11 changes: 7 additions & 4 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { MiniCSS } from "@minicss/core";
import { Helpers, Plugin } from "postcss";

export const CLASS_REGEX = /(?<className>-?(?:[_a-zA-Z]|\\[.:/\d])+(?:[-_a-zA-Z\d]|\\[.:/])*)/gm;
export const CLASS_SELECTOR_REGEX = new RegExp(`\\.${ CLASS_REGEX.source }`, "gm");
export const NAME_REGEX = /(?<name>-?(?:[_a-zA-Z]|\\[.:/\d])+(?:[-_a-zA-Z\d]|\\[.:/])*)/gm;

export const ID_REGEX = /(?<id>-?(?:[_a-zA-Z]|\\[.:/\d])+(?:[-_a-zA-Z\d]|\\[.:/])*)/gm;
export const ID_SELECTOR_REGEX = new RegExp(`#${ ID_REGEX.source }`, "gm");
export const CLASS_SELECTOR_REGEX = new RegExp(`\\.${ NAME_REGEX.source }`, "gm");

export const ID_SELECTOR_REGEX = new RegExp(`#${ NAME_REGEX.source }`, "gm");

export const VARIABLE_REGEX = /-{2}(?<variable>-?[_a-zA-Z]+[-_a-zA-Z\d]*)/gm;
export const PROPERTY_REGEX = new RegExp(`var\\(${ VARIABLE_REGEX.source }\\)`, "gm");

export const ANIMATION_NAMES = ["none", "initial", "inherit"];

export const PROCESSED = Symbol("PROCESSED");

export interface OptionsI {
classes?: boolean;
ids?: boolean;
keyframes?: boolean;
outputMapFile?: string | null;
variables?: boolean;
}
Expand Down
21 changes: 17 additions & 4 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ import { resolve } from "node:path";
import { MiniCSS } from "@minicss/core";
import { Plugin } from "postcss";
import { OptionsI, ProcessorsT } from "./constants.js";
import { processClasses, processClassesAndIds, processIds, processor, processVariables } from "./utils.js";
import {
processClasses,
processDeclarations,
processIds,
processKeyframes,
processKeyframesUsages,
processor,
processRules,
processVariables,
} from "./utils.js";

function minicss(options: OptionsI = {}): Plugin {
const { classes = true, ids = true, variables = true, outputMapFile = null } = options;
const { classes = true, ids = true, keyframes = true, variables = true, outputMapFile = null } = options;

return {
postcssPlugin: "minicss",
Expand All @@ -15,11 +24,15 @@ function minicss(options: OptionsI = {}): Plugin {

const miniCSS = (new MiniCSS);

if (classes && ids) processors.Rule = processor(miniCSS, processClassesAndIds);
if (classes && ids) processors.Rule = processor(miniCSS, processRules);
else if (classes) processors.Rule = processor(miniCSS, processClasses);
else if (ids) processors.Rule = processor(miniCSS, processIds);

if (variables) processors.Declaration = processor(miniCSS, processVariables);
if (keyframes) processors.AtRule = processor(miniCSS, processKeyframes);

if (variables && keyframes) processors.Declaration = processor(miniCSS, processDeclarations);
else if (variables) processors.Declaration = processor(miniCSS, processVariables);
else if (keyframes) processors.Declaration = processor(miniCSS, processKeyframesUsages);

if (outputMapFile != null) {
processors.OnceExit = async (): Promise<void> => writeFile(
Expand Down
49 changes: 45 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MiniCSS } from "@minicss/core";
import { Declaration, Node, Rule } from "postcss";
import { AtRule, Declaration, Node, Rule } from "postcss";
import {
ANIMATION_NAMES,
CLASS_SELECTOR_REGEX,
ID_SELECTOR_REGEX,
PostCSSProcessorT,
Expand All @@ -17,16 +18,56 @@ export function processIds(miniCSS: MiniCSS, rule: Rule): void {
rule.selector = rule.selector.replace(ID_SELECTOR_REGEX, (_, id) => `#${ miniCSS.id(id) }`);
}

export function processClassesAndIds(miniCSS: MiniCSS, rule: Rule): void {
export function processRules(miniCSS: MiniCSS, rule: Rule): void {
processClasses(miniCSS, rule);

processIds(miniCSS, rule);
}

export function processVariables(miniCSS: MiniCSS, decl: Declaration): void {
if (decl.variable) decl.prop = `--${ miniCSS.variable(decl.prop.substring(2)) }`;
const { variable, prop, value } = decl;

decl.value = decl.value.replace(PROPERTY_REGEX, (_, property) => `var(--${ miniCSS.variable(property) })`);
if (variable) decl.prop = `--${ miniCSS.variable(prop.substring(2)) }`;

decl.value = value.replace(PROPERTY_REGEX, (_, property) => `var(--${ miniCSS.variable(property) })`);
}

export function processKeyframes(miniCSS: MiniCSS, atRule: AtRule): void {
if (atRule.name.endsWith("keyframes")) atRule.params = miniCSS.keyframe(atRule.params);
}

export function processKeyframesUsages(miniCSS: MiniCSS, decl: Declaration): void {
const { prop, value } = decl;

if (prop.endsWith("animation-name")) {
if (ANIMATION_NAMES.includes(value) || value.startsWith("var(--")) return;

decl.value = miniCSS.keyframe(value);

return;
}

if (!prop.endsWith("animation")) return;

const words = value.split(" ");

let name = words[0];
let pattern = `^${ name }`;

if (/^\d/.test(name)) {
name = words[words.length - 1];
pattern = `${ name }$`;
}

if (ANIMATION_NAMES.includes(name) || name.startsWith("var(--")) return;

decl.value = value.replace(new RegExp(pattern), miniCSS.keyframe(name));
}

export function processDeclarations(miniCSS: MiniCSS, decl: Declaration): void {
processVariables(miniCSS, decl);

processKeyframesUsages(miniCSS, decl);
}

export function processor<T extends Node>(miniCSS: MiniCSS, fn: ProcessorT<T>): PostCSSProcessorT<T> {
Expand Down

0 comments on commit 043cdd6

Please sign in to comment.