emily-css 1.2.0-alpha.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/bin/emilyui.js +22 -15
- package/package.json +1 -1
- package/src/constants.js +65 -0
- package/src/doctor.js +1 -22
- package/src/generators.js +20 -19
- package/src/index.js +8 -7
- package/src/init.js +2 -36
- package/src/manifest.js +4 -19
- package/src/migrate.js +150 -24
- package/src/purge.js +1 -22
- package/src/watch.js +32 -40
package/CHANGELOG.md
CHANGED
|
@@ -23,6 +23,17 @@ All notable changes to `emily-css` are documented here.
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
+
## v1.2.1 — May 2026
|
|
27
|
+
|
|
28
|
+
**updated the full system to be more efficient**
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- updated the full system to be more efficient
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- updated release logic
|
|
35
|
+
|
|
36
|
+
---
|
|
26
37
|
## v1.1.1 — May 2026
|
|
27
38
|
|
|
28
39
|
**updated changes and added**
|
package/bin/emilyui.js
CHANGED
|
@@ -4,6 +4,21 @@ const path = require("path");
|
|
|
4
4
|
|
|
5
5
|
const command = process.argv[2];
|
|
6
6
|
const packageJson = require(path.join(__dirname, "..", "package.json"));
|
|
7
|
+
const usageText = `
|
|
8
|
+
emily-css — Config-driven CSS framework generator
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
emily-css init Set up a new project
|
|
12
|
+
emily-css build Generate production CSS to the configured output path
|
|
13
|
+
emily-css watch Dev mode: rebuild on changes
|
|
14
|
+
emily-css doctor Scan project files for unknown EmilyCSS classes
|
|
15
|
+
emily-css migrate Generate a Tailwind-to-EmilyCSS migration report
|
|
16
|
+
--import-colours Detect Tailwind colour palettes and suggest importedPalettes config
|
|
17
|
+
emily-css showcase Browse components in your browser
|
|
18
|
+
emily-css help Full command reference
|
|
19
|
+
|
|
20
|
+
Run emily-css help for more detail.
|
|
21
|
+
`;
|
|
7
22
|
|
|
8
23
|
if (command === "init") {
|
|
9
24
|
require("../src/init.js");
|
|
@@ -50,19 +65,11 @@ if (command === "init") {
|
|
|
50
65
|
Docs: https://emilyui.dev
|
|
51
66
|
`);
|
|
52
67
|
} else {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
emily-css doctor Scan project files for unknown EmilyCSS classes
|
|
61
|
-
emily-css migrate Generate a Tailwind-to-EmilyCSS migration report
|
|
62
|
-
--import-colours Detect Tailwind colour palettes and suggest importedPalettes config
|
|
63
|
-
emily-css showcase Browse components in your browser
|
|
64
|
-
emily-css help Full command reference
|
|
65
|
-
|
|
66
|
-
Run emily-css help for more detail.
|
|
67
|
-
`);
|
|
68
|
+
if (!command) {
|
|
69
|
+
console.log(usageText);
|
|
70
|
+
} else {
|
|
71
|
+
console.error(`Unknown command: ${command}`);
|
|
72
|
+
console.log(usageText);
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
}
|
|
68
75
|
}
|
package/package.json
CHANGED
package/src/constants.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const DEFAULT_EXTENSIONS = [
|
|
2
|
+
'.html',
|
|
3
|
+
'.htm',
|
|
4
|
+
'.twig',
|
|
5
|
+
'.njk',
|
|
6
|
+
'.liquid',
|
|
7
|
+
'.hbs',
|
|
8
|
+
'.js',
|
|
9
|
+
'.jsx',
|
|
10
|
+
'.ts',
|
|
11
|
+
'.tsx',
|
|
12
|
+
'.vue',
|
|
13
|
+
'.php',
|
|
14
|
+
'.astro',
|
|
15
|
+
'.svelte',
|
|
16
|
+
'.blade.php',
|
|
17
|
+
'.jinja',
|
|
18
|
+
'.jinja2',
|
|
19
|
+
'.j2',
|
|
20
|
+
'.md',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const DEFAULT_PURGE_IGNORE = [
|
|
24
|
+
'node_modules',
|
|
25
|
+
'.git',
|
|
26
|
+
'.nuxt',
|
|
27
|
+
'.next',
|
|
28
|
+
'.output',
|
|
29
|
+
'dist',
|
|
30
|
+
'build',
|
|
31
|
+
'coverage',
|
|
32
|
+
'.cache',
|
|
33
|
+
'.vite',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const DEFAULT_RESPONSIVE_VARIANTS = ['sm', 'md', 'lg', 'xl', '2xl'];
|
|
37
|
+
|
|
38
|
+
const BASE_VARIANTS = [
|
|
39
|
+
'hover',
|
|
40
|
+
'focus',
|
|
41
|
+
'focus-within',
|
|
42
|
+
'focus-visible',
|
|
43
|
+
'active',
|
|
44
|
+
'disabled',
|
|
45
|
+
'motion-reduce',
|
|
46
|
+
'motion-safe',
|
|
47
|
+
'aria-expanded',
|
|
48
|
+
'aria-selected',
|
|
49
|
+
'aria-current',
|
|
50
|
+
'aria-disabled',
|
|
51
|
+
'data-open',
|
|
52
|
+
'data-closed',
|
|
53
|
+
'dark',
|
|
54
|
+
'forced-colors',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const PURGE_EXTENSIONS = [...DEFAULT_EXTENSIONS];
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
DEFAULT_EXTENSIONS,
|
|
61
|
+
DEFAULT_PURGE_IGNORE,
|
|
62
|
+
DEFAULT_RESPONSIVE_VARIANTS,
|
|
63
|
+
BASE_VARIANTS,
|
|
64
|
+
PURGE_EXTENSIONS,
|
|
65
|
+
};
|
package/src/doctor.js
CHANGED
|
@@ -3,28 +3,7 @@ const path = require("path");
|
|
|
3
3
|
const fg = require("fast-glob");
|
|
4
4
|
const { extractClassNames } = require("./purge.js");
|
|
5
5
|
const { ensureFullFramework, generateManifest } = require("./index.js");
|
|
6
|
-
|
|
7
|
-
const DEFAULT_EXTENSIONS = [
|
|
8
|
-
".html",
|
|
9
|
-
".htm",
|
|
10
|
-
".twig",
|
|
11
|
-
".njk",
|
|
12
|
-
".liquid",
|
|
13
|
-
".hbs",
|
|
14
|
-
".js",
|
|
15
|
-
".jsx",
|
|
16
|
-
".ts",
|
|
17
|
-
".tsx",
|
|
18
|
-
".vue",
|
|
19
|
-
".php",
|
|
20
|
-
".astro",
|
|
21
|
-
".svelte",
|
|
22
|
-
".blade.php",
|
|
23
|
-
".jinja",
|
|
24
|
-
".jinja2",
|
|
25
|
-
".j2",
|
|
26
|
-
".md",
|
|
27
|
-
];
|
|
6
|
+
const { DEFAULT_EXTENSIONS } = require("./constants.js");
|
|
28
7
|
|
|
29
8
|
function getConfigPath() {
|
|
30
9
|
return path.join(process.cwd(), "emily.config.json");
|
package/src/generators.js
CHANGED
|
@@ -281,6 +281,7 @@ function transitionUtilities() {
|
|
|
281
281
|
// Transforms
|
|
282
282
|
function transformUtilities(spacing) {
|
|
283
283
|
let css = `/* Transforms */\n`;
|
|
284
|
+
const composedTransform = 'translate(var(--translate-x, 0), var(--translate-y, 0)) rotate(var(--rotate, 0)) skewX(var(--skew-x, 0)) skewY(var(--skew-y, 0)) scaleX(var(--scale-x, 1)) scaleY(var(--scale-y, 1))';
|
|
284
285
|
|
|
285
286
|
css += `.transform { transform: translateZ(0); }\n`;
|
|
286
287
|
css += `.transform-gpu { transform: translate3d(0, 0, 0); }\n`;
|
|
@@ -289,30 +290,30 @@ function transformUtilities(spacing) {
|
|
|
289
290
|
// Translate
|
|
290
291
|
Object.entries(spacing).forEach(([key, value]) => {
|
|
291
292
|
const escaped = escapeClassName(key);
|
|
292
|
-
css += `.translate-x-${escaped} {
|
|
293
|
-
css += `.translate-y-${escaped} {
|
|
294
|
-
css += `.-translate-x-${escaped} {
|
|
295
|
-
css += `.-translate-y-${escaped} {
|
|
293
|
+
css += `.translate-x-${escaped} { --translate-x: ${value}; transform: ${composedTransform}; }\n`;
|
|
294
|
+
css += `.translate-y-${escaped} { --translate-y: ${value}; transform: ${composedTransform}; }\n`;
|
|
295
|
+
css += `.-translate-x-${escaped} { --translate-x: -${value}; transform: ${composedTransform}; }\n`;
|
|
296
|
+
css += `.-translate-y-${escaped} { --translate-y: -${value}; transform: ${composedTransform}; }\n`;
|
|
296
297
|
});
|
|
297
298
|
|
|
298
299
|
// Rotate
|
|
299
300
|
const rotations = [0, 1, 2, 3, 6, 12, 45, 90, 180];
|
|
300
301
|
rotations.forEach(deg => {
|
|
301
|
-
css += `.rotate-${deg} {
|
|
302
|
-
if (deg > 0) css += `.-rotate-${deg} {
|
|
302
|
+
css += `.rotate-${deg} { --rotate: ${deg}deg; transform: ${composedTransform}; }\n`;
|
|
303
|
+
if (deg > 0) css += `.-rotate-${deg} { --rotate: -${deg}deg; transform: ${composedTransform}; }\n`;
|
|
303
304
|
});
|
|
304
305
|
|
|
305
306
|
// Scale
|
|
306
307
|
const scales = [0, 50, 75, 90, 95, 100, 110, 125, 150];
|
|
307
308
|
scales.forEach(scale => {
|
|
308
|
-
css += `.scale-${scale} {
|
|
309
|
+
css += `.scale-${scale} { --scale-x: ${scale / 100}; --scale-y: ${scale / 100}; transform: ${composedTransform}; }\n`;
|
|
309
310
|
});
|
|
310
311
|
|
|
311
312
|
// Skew
|
|
312
313
|
const skews = [0, 1, 2, 3];
|
|
313
314
|
skews.forEach(sk => {
|
|
314
|
-
css += `.skew-x-${sk} {
|
|
315
|
-
css += `.skew-y-${sk} {
|
|
315
|
+
css += `.skew-x-${sk} { --skew-x: ${sk}deg; transform: ${composedTransform}; }\n`;
|
|
316
|
+
css += `.skew-y-${sk} { --skew-y: ${sk}deg; transform: ${composedTransform}; }\n`;
|
|
316
317
|
});
|
|
317
318
|
|
|
318
319
|
// Transform origin
|
|
@@ -349,18 +350,21 @@ function shadowUtilities() {
|
|
|
349
350
|
function ringUtilities(colours) {
|
|
350
351
|
let css = `/* Rings & Outlines */\n`;
|
|
351
352
|
|
|
352
|
-
css += `.ring-0 { box-shadow: 0 0 0 0px var(--ring-color, transparent
|
|
353
|
-
css += `.ring-1 { box-shadow: 0 0 0 1px var(--ring-color,
|
|
354
|
-
css += `.ring-2 { box-shadow: 0 0 0 2px var(--ring-color,
|
|
353
|
+
css += `.ring-0 { --ring-offset-width: 0px; --ring-offset-color: #fff; --ring-color: currentColor; box-shadow: 0 0 0 var(--ring-offset-width, 0px) var(--ring-offset-color, #fff), 0 0 0 var(--ring-offset-width, 0px) transparent; }\n`;
|
|
354
|
+
css += `.ring-1 { --ring-offset-width: 0px; --ring-offset-color: #fff; --ring-color: currentColor; box-shadow: 0 0 0 var(--ring-offset-width, 0px) var(--ring-offset-color, #fff), 0 0 0 calc(1px + var(--ring-offset-width, 0px)) var(--ring-color, currentColor); }\n`;
|
|
355
|
+
css += `.ring-2 { --ring-offset-width: 0px; --ring-offset-color: #fff; --ring-color: currentColor; box-shadow: 0 0 0 var(--ring-offset-width, 0px) var(--ring-offset-color, #fff), 0 0 0 calc(2px + var(--ring-offset-width, 0px)) var(--ring-color, currentColor); }\n`;
|
|
355
356
|
|
|
356
357
|
css += `.ring-offset-0 { --ring-offset-width: 0px; }\n`;
|
|
357
358
|
css += `.ring-offset-2 { --ring-offset-width: 2px; }\n`;
|
|
358
359
|
css += `.ring-offset-4 { --ring-offset-width: 4px; }\n`;
|
|
360
|
+
css += `.ring-offset-white { --ring-offset-color: #fff; }\n`;
|
|
361
|
+
css += `.ring-offset-black { --ring-offset-color: #000; }\n`;
|
|
359
362
|
|
|
360
363
|
// Ring colours
|
|
361
364
|
Object.entries(colours).forEach(([colourName, shades]) => {
|
|
362
365
|
Object.entries(shades).forEach(([shade]) => {
|
|
363
366
|
css += `.ring-${colourName}-${shade} { --ring-color: var(--color-${colourName}-${shade}); }\n`;
|
|
367
|
+
css += `.ring-offset-${colourName}-${shade} { --ring-offset-color: var(--color-${colourName}-${shade}); }\n`;
|
|
364
368
|
});
|
|
365
369
|
});
|
|
366
370
|
|
|
@@ -642,13 +646,10 @@ function accessibilityUtilities() {
|
|
|
642
646
|
// Container Queries (Forward-looking)
|
|
643
647
|
function containerUtilities() {
|
|
644
648
|
return `/* Container Queries */
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
@container (min-width: 36rem) { .cq-md\\: { /* utilities */ } }
|
|
650
|
-
@container (min-width: 48rem) { .cq-lg\\: { /* utilities */ } }
|
|
651
|
-
}
|
|
649
|
+
.container-type-inline { container-type: inline-size; }
|
|
650
|
+
.container-type-size { container-type: size; }
|
|
651
|
+
.container-type-normal { container-type: normal; }
|
|
652
|
+
.container-name-none { container-name: none; }
|
|
652
653
|
|
|
653
654
|
`;
|
|
654
655
|
}
|
package/src/index.js
CHANGED
|
@@ -235,6 +235,7 @@ function generateCSSVariables(colours, spacing, config) {
|
|
|
235
235
|
css += ` --color-${name}: ${hex};\n`;
|
|
236
236
|
});
|
|
237
237
|
}
|
|
238
|
+
css += ` --focus-ring-glow: color-mix(in srgb, var(--color-brand-80) 12%, transparent);\n`;
|
|
238
239
|
|
|
239
240
|
// Spacing variables
|
|
240
241
|
Object.entries(spacing).forEach(([key, value]) => {
|
|
@@ -1241,7 +1242,7 @@ function generatePatternComponents() {
|
|
|
1241
1242
|
outline: 2px solid var(--color-neutral-80);
|
|
1242
1243
|
outline-offset: 3px;
|
|
1243
1244
|
border-color: var(--color-neutral-80);
|
|
1244
|
-
box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
|
|
1245
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1245
1246
|
}
|
|
1246
1247
|
|
|
1247
1248
|
.checkbox-group,
|
|
@@ -1272,7 +1273,7 @@ function generatePatternComponents() {
|
|
|
1272
1273
|
input[type="checkbox"]:focus {
|
|
1273
1274
|
outline: 2px solid var(--color-neutral-80);
|
|
1274
1275
|
outline-offset: 3px;
|
|
1275
|
-
box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
|
|
1276
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1276
1277
|
}
|
|
1277
1278
|
|
|
1278
1279
|
input[type="radio"] {
|
|
@@ -1317,7 +1318,7 @@ function generatePatternComponents() {
|
|
|
1317
1318
|
outline: 2px solid var(--color-neutral-80);
|
|
1318
1319
|
outline-offset: 3px;
|
|
1319
1320
|
border-radius: 50%;
|
|
1320
|
-
box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
|
|
1321
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1321
1322
|
}
|
|
1322
1323
|
|
|
1323
1324
|
input[aria-invalid="true"] {
|
|
@@ -1382,7 +1383,7 @@ function generatePatternComponents() {
|
|
|
1382
1383
|
.btn-primary:focus-visible {
|
|
1383
1384
|
outline: 2px solid var(--color-neutral-80);
|
|
1384
1385
|
outline-offset: 3px;
|
|
1385
|
-
box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
|
|
1386
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1386
1387
|
}
|
|
1387
1388
|
|
|
1388
1389
|
.btn-secondary {
|
|
@@ -1400,7 +1401,7 @@ function generatePatternComponents() {
|
|
|
1400
1401
|
.btn-secondary:focus-visible {
|
|
1401
1402
|
outline: 2px solid var(--color-neutral-80);
|
|
1402
1403
|
outline-offset: 3px;
|
|
1403
|
-
box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
|
|
1404
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1404
1405
|
}
|
|
1405
1406
|
|
|
1406
1407
|
.btn-ghost {
|
|
@@ -1416,7 +1417,7 @@ function generatePatternComponents() {
|
|
|
1416
1417
|
.btn-ghost:focus-visible {
|
|
1417
1418
|
outline: 2px solid var(--color-neutral-80);
|
|
1418
1419
|
outline-offset: 3px;
|
|
1419
|
-
box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
|
|
1420
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1420
1421
|
}
|
|
1421
1422
|
|
|
1422
1423
|
.btn-danger {
|
|
@@ -1432,7 +1433,7 @@ function generatePatternComponents() {
|
|
|
1432
1433
|
.btn-danger:focus-visible {
|
|
1433
1434
|
outline: 2px solid var(--color-neutral-80);
|
|
1434
1435
|
outline-offset: 3px;
|
|
1435
|
-
box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
|
|
1436
|
+
box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
|
|
1436
1437
|
}
|
|
1437
1438
|
|
|
1438
1439
|
.btn-sm {
|
package/src/init.js
CHANGED
|
@@ -5,24 +5,12 @@ const { Select, Input, Confirm } = require("enquirer");
|
|
|
5
5
|
const chalk = require("chalk");
|
|
6
6
|
const ora = require("ora");
|
|
7
7
|
const boxen = require("boxen");
|
|
8
|
+
const { DEFAULT_PURGE_IGNORE, PURGE_EXTENSIONS } = require("./constants.js");
|
|
8
9
|
|
|
9
10
|
// ============================================================================
|
|
10
11
|
// CONSTANTS
|
|
11
12
|
// ============================================================================
|
|
12
13
|
|
|
13
|
-
const DEFAULT_PURGE_IGNORE = [
|
|
14
|
-
"node_modules",
|
|
15
|
-
".git",
|
|
16
|
-
".nuxt",
|
|
17
|
-
".next",
|
|
18
|
-
".output",
|
|
19
|
-
"dist",
|
|
20
|
-
"build",
|
|
21
|
-
"coverage",
|
|
22
|
-
".cache",
|
|
23
|
-
".vite",
|
|
24
|
-
];
|
|
25
|
-
|
|
26
14
|
const COLOUR_PRESETS = {
|
|
27
15
|
primary: [
|
|
28
16
|
{ value: "custom", label: "Enter your own hex" },
|
|
@@ -69,28 +57,6 @@ const FONT_OPTIONS = [
|
|
|
69
57
|
{ name: "system", message: "System sans-serif (no download required)" },
|
|
70
58
|
];
|
|
71
59
|
|
|
72
|
-
const PURGE_EXTENSIONS = [
|
|
73
|
-
".html",
|
|
74
|
-
".htm",
|
|
75
|
-
".twig",
|
|
76
|
-
".njk",
|
|
77
|
-
".liquid",
|
|
78
|
-
".hbs",
|
|
79
|
-
".js",
|
|
80
|
-
".jsx",
|
|
81
|
-
".ts",
|
|
82
|
-
".tsx",
|
|
83
|
-
".vue",
|
|
84
|
-
".php",
|
|
85
|
-
".astro",
|
|
86
|
-
".svelte",
|
|
87
|
-
".blade.php",
|
|
88
|
-
".jinja",
|
|
89
|
-
".jinja2",
|
|
90
|
-
".j2",
|
|
91
|
-
".md",
|
|
92
|
-
];
|
|
93
|
-
|
|
94
60
|
// ============================================================================
|
|
95
61
|
// HELPERS
|
|
96
62
|
// ============================================================================
|
|
@@ -662,7 +628,7 @@ async function init() {
|
|
|
662
628
|
|
|
663
629
|
const baseUnitRaw = await new Input({
|
|
664
630
|
name: "baseUnit",
|
|
665
|
-
message: "Base spacing unit in px (
|
|
631
|
+
message: "Base spacing unit in px (label/documentation only)",
|
|
666
632
|
initial: "18",
|
|
667
633
|
validate: function (value) {
|
|
668
634
|
const parsed = Number.parseInt(value, 10);
|
package/src/manifest.js
CHANGED
|
@@ -1,23 +1,8 @@
|
|
|
1
1
|
const MANIFEST_VERSION = '1.1.0';
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
'focus-within',
|
|
7
|
-
'focus-visible',
|
|
8
|
-
'active',
|
|
9
|
-
'disabled',
|
|
10
|
-
'motion-reduce',
|
|
11
|
-
'motion-safe',
|
|
12
|
-
'aria-expanded',
|
|
13
|
-
'aria-selected',
|
|
14
|
-
'aria-current',
|
|
15
|
-
'aria-disabled',
|
|
16
|
-
'data-open',
|
|
17
|
-
'data-closed',
|
|
18
|
-
'dark',
|
|
19
|
-
'forced-colors',
|
|
20
|
-
];
|
|
2
|
+
const {
|
|
3
|
+
DEFAULT_RESPONSIVE_VARIANTS,
|
|
4
|
+
BASE_VARIANTS,
|
|
5
|
+
} = require('./constants.js');
|
|
21
6
|
|
|
22
7
|
function parseDeclarations(block) {
|
|
23
8
|
const declarations = {};
|
package/src/migrate.js
CHANGED
|
@@ -5,29 +5,8 @@ const fg = require('fast-glob');
|
|
|
5
5
|
const { extractClassNames, getAllFiles } = require('./purge.js');
|
|
6
6
|
const { generateManifest } = require('./manifest.js');
|
|
7
7
|
const { normaliseClassForManifest, suggestClassName } = require('./doctor.js');
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
'.html',
|
|
11
|
-
'.htm',
|
|
12
|
-
'.twig',
|
|
13
|
-
'.njk',
|
|
14
|
-
'.liquid',
|
|
15
|
-
'.hbs',
|
|
16
|
-
'.js',
|
|
17
|
-
'.jsx',
|
|
18
|
-
'.ts',
|
|
19
|
-
'.tsx',
|
|
20
|
-
'.vue',
|
|
21
|
-
'.php',
|
|
22
|
-
'.astro',
|
|
23
|
-
'.svelte',
|
|
24
|
-
'.blade.php',
|
|
25
|
-
'.jinja',
|
|
26
|
-
'.jinja2',
|
|
27
|
-
'.j2',
|
|
28
|
-
'.md',
|
|
29
|
-
'.mdx',
|
|
30
|
-
];
|
|
8
|
+
const { DEFAULT_EXTENSIONS } = require('./constants.js');
|
|
9
|
+
const MIGRATION_DEFAULT_EXTENSIONS = [...DEFAULT_EXTENSIONS, '.mdx'];
|
|
31
10
|
|
|
32
11
|
const TAILWIND_MAPPINGS = {
|
|
33
12
|
'text-gray-900': {
|
|
@@ -163,6 +142,150 @@ const TAILWIND_SHADE_TO_EMILY_SHADE = {
|
|
|
163
142
|
'950': '100',
|
|
164
143
|
};
|
|
165
144
|
|
|
145
|
+
const SINGLE_WORD_UTILITY_ALLOWLIST = new Set([
|
|
146
|
+
'flex',
|
|
147
|
+
'grid',
|
|
148
|
+
'hidden',
|
|
149
|
+
'block',
|
|
150
|
+
'inline',
|
|
151
|
+
'table',
|
|
152
|
+
'contents',
|
|
153
|
+
'flow',
|
|
154
|
+
'container',
|
|
155
|
+
'relative',
|
|
156
|
+
'absolute',
|
|
157
|
+
'fixed',
|
|
158
|
+
'sticky',
|
|
159
|
+
'static',
|
|
160
|
+
'visible',
|
|
161
|
+
'invisible',
|
|
162
|
+
'uppercase',
|
|
163
|
+
'lowercase',
|
|
164
|
+
'capitalize',
|
|
165
|
+
'truncate',
|
|
166
|
+
'antialiased',
|
|
167
|
+
'italic',
|
|
168
|
+
'not-italic',
|
|
169
|
+
'underline',
|
|
170
|
+
'overline',
|
|
171
|
+
'line-through',
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
const UTILITY_PREFIX_ALLOWLIST = new Set([
|
|
175
|
+
'bg',
|
|
176
|
+
'text',
|
|
177
|
+
'border',
|
|
178
|
+
'outline',
|
|
179
|
+
'accent',
|
|
180
|
+
'fill',
|
|
181
|
+
'stroke',
|
|
182
|
+
'ring',
|
|
183
|
+
'rounded',
|
|
184
|
+
'shadow',
|
|
185
|
+
'font',
|
|
186
|
+
'leading',
|
|
187
|
+
'tracking',
|
|
188
|
+
'p',
|
|
189
|
+
'px',
|
|
190
|
+
'py',
|
|
191
|
+
'pt',
|
|
192
|
+
'pr',
|
|
193
|
+
'pb',
|
|
194
|
+
'pl',
|
|
195
|
+
'm',
|
|
196
|
+
'mx',
|
|
197
|
+
'my',
|
|
198
|
+
'mt',
|
|
199
|
+
'mr',
|
|
200
|
+
'mb',
|
|
201
|
+
'ml',
|
|
202
|
+
'w',
|
|
203
|
+
'h',
|
|
204
|
+
'min-w',
|
|
205
|
+
'max-w',
|
|
206
|
+
'min-h',
|
|
207
|
+
'max-h',
|
|
208
|
+
'gap',
|
|
209
|
+
'space',
|
|
210
|
+
'inset',
|
|
211
|
+
'top',
|
|
212
|
+
'right',
|
|
213
|
+
'bottom',
|
|
214
|
+
'left',
|
|
215
|
+
'z',
|
|
216
|
+
'order',
|
|
217
|
+
'col',
|
|
218
|
+
'row',
|
|
219
|
+
'grid-cols',
|
|
220
|
+
'grid-rows',
|
|
221
|
+
'justify',
|
|
222
|
+
'items',
|
|
223
|
+
'content',
|
|
224
|
+
'self',
|
|
225
|
+
'place',
|
|
226
|
+
'object',
|
|
227
|
+
'overflow',
|
|
228
|
+
'divide',
|
|
229
|
+
'cursor',
|
|
230
|
+
'select',
|
|
231
|
+
'duration',
|
|
232
|
+
'delay',
|
|
233
|
+
'ease',
|
|
234
|
+
'scale',
|
|
235
|
+
'rotate',
|
|
236
|
+
'translate',
|
|
237
|
+
'skew',
|
|
238
|
+
'origin',
|
|
239
|
+
'opacity',
|
|
240
|
+
'basis',
|
|
241
|
+
'grow',
|
|
242
|
+
'shrink',
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
function hasUtilityLikeSyntax(className) {
|
|
246
|
+
if (!className || typeof className !== 'string') {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const variantSeparatorIndex = className.lastIndexOf(':');
|
|
251
|
+
if (variantSeparatorIndex !== -1) {
|
|
252
|
+
const baseClass = className.slice(variantSeparatorIndex + 1);
|
|
253
|
+
if (!baseClass) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
return hasUtilityLikeSyntax(baseClass);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (className.startsWith('-')) {
|
|
260
|
+
const baseClass = className.slice(1);
|
|
261
|
+
return baseClass.length > 0 && hasUtilityLikeSyntax(baseClass);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (SINGLE_WORD_UTILITY_ALLOWLIST.has(className)) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (
|
|
269
|
+
hasArbitraryValueSyntax(className) ||
|
|
270
|
+
className.includes('/') ||
|
|
271
|
+
className.includes('.') ||
|
|
272
|
+
className.includes('_') ||
|
|
273
|
+
/\d/.test(className)
|
|
274
|
+
) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const parts = className.split('-').filter(Boolean);
|
|
279
|
+
|
|
280
|
+
if (parts.length >= 2) {
|
|
281
|
+
const first = parts[0];
|
|
282
|
+
const firstTwo = `${parts[0]}-${parts[1]}`;
|
|
283
|
+
return UTILITY_PREFIX_ALLOWLIST.has(first) || UTILITY_PREFIX_ALLOWLIST.has(firstTwo);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
166
289
|
function getConfigPath(options = {}) {
|
|
167
290
|
return options.configPath || path.join(process.cwd(), 'emily.config.json');
|
|
168
291
|
}
|
|
@@ -443,12 +566,15 @@ function isLikelyUtilityClass(className) {
|
|
|
443
566
|
if (/\s/.test(className)) return false;
|
|
444
567
|
if (className.length > 120) return false;
|
|
445
568
|
if (className.startsWith('--')) return false;
|
|
569
|
+
if (className.startsWith(':')) return false;
|
|
446
570
|
if (className.startsWith('.') || className.startsWith('#') || className.startsWith('@')) return false;
|
|
447
571
|
if (className.endsWith(':')) return false;
|
|
448
572
|
if (className.includes('://')) return false;
|
|
449
573
|
if (/[;()={},`]/.test(className)) return false;
|
|
450
574
|
if (!/[a-zA-Z]/.test(className)) return false;
|
|
451
575
|
if (!/^[a-zA-Z0-9:#_./\-[\]]+$/.test(className)) return false;
|
|
576
|
+
if (/^[a-z]+$/.test(className) && !SINGLE_WORD_UTILITY_ALLOWLIST.has(className)) return false;
|
|
577
|
+
if (!hasUtilityLikeSyntax(className) && !SINGLE_WORD_UTILITY_ALLOWLIST.has(className)) return false;
|
|
452
578
|
|
|
453
579
|
return true;
|
|
454
580
|
}
|
|
@@ -614,7 +740,7 @@ function migrateClasses(input, options = {}) {
|
|
|
614
740
|
}
|
|
615
741
|
|
|
616
742
|
function getFilesToScan(config, options = {}) {
|
|
617
|
-
const extensions = (config && config.purge && config.purge.extensions) ||
|
|
743
|
+
const extensions = (config && config.purge && config.purge.extensions) || MIGRATION_DEFAULT_EXTENSIONS;
|
|
618
744
|
const ignore = (config && config.purge && config.purge.ignore) || [];
|
|
619
745
|
|
|
620
746
|
if (options.sourceGlobs && options.sourceGlobs.length > 0) {
|
package/src/purge.js
CHANGED
|
@@ -2,28 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
|
|
6
|
-
const DEFAULT_EXTENSIONS = [
|
|
7
|
-
".html",
|
|
8
|
-
".htm",
|
|
9
|
-
".twig",
|
|
10
|
-
".njk",
|
|
11
|
-
".liquid",
|
|
12
|
-
".hbs",
|
|
13
|
-
".js",
|
|
14
|
-
".jsx",
|
|
15
|
-
".ts",
|
|
16
|
-
".tsx",
|
|
17
|
-
".vue",
|
|
18
|
-
".php",
|
|
19
|
-
".astro",
|
|
20
|
-
".svelte",
|
|
21
|
-
".blade.php",
|
|
22
|
-
".jinja",
|
|
23
|
-
".jinja2",
|
|
24
|
-
".j2",
|
|
25
|
-
".md",
|
|
26
|
-
];
|
|
5
|
+
const { DEFAULT_EXTENSIONS } = require("./constants.js");
|
|
27
6
|
|
|
28
7
|
function getAllFiles(dir, extensions = DEFAULT_EXTENSIONS) {
|
|
29
8
|
let files = [];
|
package/src/watch.js
CHANGED
|
@@ -3,6 +3,10 @@ const path = require("path");
|
|
|
3
3
|
const chokidar = require("chokidar");
|
|
4
4
|
const chalk = require("chalk");
|
|
5
5
|
const fg = require("fast-glob");
|
|
6
|
+
const {
|
|
7
|
+
DEFAULT_PURGE_IGNORE,
|
|
8
|
+
DEFAULT_EXTENSIONS,
|
|
9
|
+
} = require("./constants.js");
|
|
6
10
|
|
|
7
11
|
const {
|
|
8
12
|
buildFullFramework,
|
|
@@ -16,6 +20,7 @@ let isRunning = false;
|
|
|
16
20
|
let pendingRun = false;
|
|
17
21
|
let previousClasses = new Set();
|
|
18
22
|
let hasRunOnce = false;
|
|
23
|
+
let activeIgnoreList = DEFAULT_PURGE_IGNORE;
|
|
19
24
|
|
|
20
25
|
function readConfig() {
|
|
21
26
|
const configPath = path.join(process.cwd(), "emily.config.json");
|
|
@@ -34,22 +39,26 @@ function normalisePath(filePath) {
|
|
|
34
39
|
return filePath.replace(/\\/g, "/");
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
function
|
|
38
|
-
|
|
42
|
+
function normaliseIgnoreEntry(entry) {
|
|
43
|
+
return normalisePath(String(entry || ""))
|
|
44
|
+
.replace(/^\.\/+/, "")
|
|
45
|
+
.replace(/^\/+|\/+$/g, "");
|
|
46
|
+
}
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
function shouldIgnore(filePath, ignoreList = DEFAULT_PURGE_IGNORE) {
|
|
49
|
+
if (!filePath) return false;
|
|
50
|
+
|
|
51
|
+
const normalised = normalisePath(filePath);
|
|
52
|
+
const normalisedIgnoreList = (Array.isArray(ignoreList) ? ignoreList : DEFAULT_PURGE_IGNORE)
|
|
53
|
+
.map(normaliseIgnoreEntry)
|
|
54
|
+
.filter(Boolean);
|
|
55
|
+
|
|
56
|
+
return normalisedIgnoreList.some(
|
|
57
|
+
(entry) =>
|
|
58
|
+
normalised === entry ||
|
|
59
|
+
normalised.startsWith(entry + "/") ||
|
|
60
|
+
normalised.includes("/" + entry + "/") ||
|
|
61
|
+
normalised.endsWith("/" + entry),
|
|
53
62
|
);
|
|
54
63
|
}
|
|
55
64
|
|
|
@@ -80,27 +89,7 @@ function getScanFiles(config) {
|
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
const sourceDir = config.purge?.sourceDir || ".";
|
|
83
|
-
const extensions = config.purge?.extensions ||
|
|
84
|
-
".html",
|
|
85
|
-
".htm",
|
|
86
|
-
".twig",
|
|
87
|
-
".njk",
|
|
88
|
-
".liquid",
|
|
89
|
-
".hbs",
|
|
90
|
-
".js",
|
|
91
|
-
".jsx",
|
|
92
|
-
".ts",
|
|
93
|
-
".tsx",
|
|
94
|
-
".vue",
|
|
95
|
-
".php",
|
|
96
|
-
".astro",
|
|
97
|
-
".svelte",
|
|
98
|
-
".blade.php",
|
|
99
|
-
".jinja",
|
|
100
|
-
".jinja2",
|
|
101
|
-
".j2",
|
|
102
|
-
".md",
|
|
103
|
-
];
|
|
92
|
+
const extensions = config.purge?.extensions || DEFAULT_EXTENSIONS;
|
|
104
93
|
|
|
105
94
|
return fg.sync(
|
|
106
95
|
extensions.map((ext) => `${sourceDir.replace(/\/$/, "")}/**/*${ext}`),
|
|
@@ -115,9 +104,10 @@ function getScanFiles(config) {
|
|
|
115
104
|
function collectUsedClasses(config) {
|
|
116
105
|
const files = getScanFiles(config);
|
|
117
106
|
const usedClasses = new Set();
|
|
107
|
+
const ignoreList = config.purge?.ignore || DEFAULT_PURGE_IGNORE;
|
|
118
108
|
|
|
119
109
|
for (const file of files) {
|
|
120
|
-
if (shouldIgnore(file)) continue;
|
|
110
|
+
if (shouldIgnore(file, ignoreList)) continue;
|
|
121
111
|
|
|
122
112
|
try {
|
|
123
113
|
const content = fs.readFileSync(file, "utf8");
|
|
@@ -210,6 +200,7 @@ function runProductionUpdate(filePath) {
|
|
|
210
200
|
|
|
211
201
|
try {
|
|
212
202
|
const config = readConfig();
|
|
203
|
+
activeIgnoreList = config.purge?.ignore || DEFAULT_PURGE_IGNORE;
|
|
213
204
|
const normalisedFilePath = filePath ? normalisePath(filePath) : "";
|
|
214
205
|
const isConfigChange = normalisedFilePath.endsWith("emily.config.json");
|
|
215
206
|
|
|
@@ -244,12 +235,13 @@ function getWatchPaths(config) {
|
|
|
244
235
|
}
|
|
245
236
|
|
|
246
237
|
function queueUpdate(filePath) {
|
|
247
|
-
if (filePath && shouldIgnore(filePath)) return;
|
|
238
|
+
if (filePath && shouldIgnore(filePath, activeIgnoreList)) return;
|
|
248
239
|
runProductionUpdate(filePath);
|
|
249
240
|
}
|
|
250
241
|
|
|
251
242
|
function runWatch() {
|
|
252
243
|
const config = readConfig();
|
|
244
|
+
activeIgnoreList = config.purge?.ignore || DEFAULT_PURGE_IGNORE;
|
|
253
245
|
const watchPaths = getWatchPaths(config);
|
|
254
246
|
|
|
255
247
|
console.log("");
|
|
@@ -270,7 +262,7 @@ function runWatch() {
|
|
|
270
262
|
runProductionUpdate();
|
|
271
263
|
|
|
272
264
|
const watcher = chokidar.watch(watchPaths, {
|
|
273
|
-
ignored: shouldIgnore,
|
|
265
|
+
ignored: (filePath) => shouldIgnore(filePath, activeIgnoreList),
|
|
274
266
|
ignoreInitial: true,
|
|
275
267
|
awaitWriteFinish: {
|
|
276
268
|
stabilityThreshold: 500,
|
|
@@ -288,4 +280,4 @@ function runWatch() {
|
|
|
288
280
|
});
|
|
289
281
|
}
|
|
290
282
|
|
|
291
|
-
runWatch();
|
|
283
|
+
runWatch();
|