emily-css 1.2.0 → 1.2.2
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 +127 -69
- package/README.md +60 -111
- package/bin/emilyui.js +6 -1
- package/package.json +3 -3
- package/src/doctor.js +119 -4
- package/src/generators/accessibility.js +40 -0
- package/src/generators/animation.js +31 -0
- package/src/generators/background.js +36 -0
- package/src/generators/code.js +31 -0
- package/src/generators/display.js +46 -0
- package/src/generators/effects.js +218 -0
- package/src/generators/forms.js +25 -0
- package/src/generators/index.js +45 -0
- package/src/generators/layout.js +138 -0
- package/src/generators/overflow.js +36 -0
- package/src/generators/positioning.js +58 -0
- package/src/generators/rings.js +41 -0
- package/src/generators/sizing.js +111 -0
- package/src/generators/svg.js +38 -0
- package/src/generators/transforms.js +61 -0
- package/src/generators.js +2 -884
- package/src/index.js +78 -5
- package/src/intellisense.js +27 -0
- package/src/manifest.js +90 -24
- package/src/purge.js +40 -20
- package/templates/showcase.html +1 -1
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { generateManifest } = require('./manifest');
|
|
7
|
+
const { generateIntellisense } = require('./intellisense');
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
// ============================================================================
|
|
@@ -1507,6 +1508,32 @@ function getManifestOutputPath(config) {
|
|
|
1507
1508
|
: path.join(process.cwd(), outputPath);
|
|
1508
1509
|
}
|
|
1509
1510
|
|
|
1511
|
+
function getIntellisenseSettings(config) {
|
|
1512
|
+
const intellisenseConfig = config.intellisense;
|
|
1513
|
+
|
|
1514
|
+
if (intellisenseConfig === true) {
|
|
1515
|
+
return { enabled: true, output: 'dist/emily.intellisense.json' };
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
if (intellisenseConfig && typeof intellisenseConfig === 'object') {
|
|
1519
|
+
return {
|
|
1520
|
+
enabled: intellisenseConfig.enabled === true,
|
|
1521
|
+
output: intellisenseConfig.output || 'dist/emily.intellisense.json',
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
return { enabled: false, output: 'dist/emily.intellisense.json' };
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function getIntellisenseOutputPath(config) {
|
|
1529
|
+
const intellisenseSettings = getIntellisenseSettings(config);
|
|
1530
|
+
const outputPath = intellisenseSettings.output || 'dist/emily.intellisense.json';
|
|
1531
|
+
|
|
1532
|
+
return path.isAbsolute(outputPath)
|
|
1533
|
+
? outputPath
|
|
1534
|
+
: path.join(process.cwd(), outputPath);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1510
1537
|
function ensureDirectoryForFile(filePath) {
|
|
1511
1538
|
const dir = path.dirname(filePath);
|
|
1512
1539
|
|
|
@@ -1729,15 +1756,30 @@ ${bodyFont}`;
|
|
|
1729
1756
|
fs.writeFileSync(fullCssPath, css);
|
|
1730
1757
|
|
|
1731
1758
|
const manifestSettings = getManifestSettings(config);
|
|
1759
|
+
const intellisenseSettings = getIntellisenseSettings(config);
|
|
1760
|
+
const shouldGenerateManifestData =
|
|
1761
|
+
manifestSettings.enabled || intellisenseSettings.enabled;
|
|
1762
|
+
const manifestData = shouldGenerateManifestData
|
|
1763
|
+
? generateManifest(css, config)
|
|
1764
|
+
: null;
|
|
1765
|
+
|
|
1732
1766
|
if (manifestSettings.enabled) {
|
|
1733
1767
|
const manifestPath = getManifestOutputPath(config);
|
|
1734
|
-
const manifest = generateManifest(css, config);
|
|
1735
1768
|
|
|
1736
1769
|
ensureDirectoryForFile(manifestPath);
|
|
1737
|
-
fs.writeFileSync(manifestPath, JSON.stringify(
|
|
1770
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifestData, null, 2));
|
|
1738
1771
|
console.log(`✓ Generated manifest: ${manifestPath}`);
|
|
1739
1772
|
}
|
|
1740
1773
|
|
|
1774
|
+
if (intellisenseSettings.enabled) {
|
|
1775
|
+
const intellisensePath = getIntellisenseOutputPath(config);
|
|
1776
|
+
const intellisense = generateIntellisense(manifestData);
|
|
1777
|
+
|
|
1778
|
+
ensureDirectoryForFile(intellisensePath);
|
|
1779
|
+
fs.writeFileSync(intellisensePath, JSON.stringify(intellisense, null, 2));
|
|
1780
|
+
console.log(`✓ Generated IntelliSense: ${intellisensePath}`);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1741
1783
|
console.log(`✓ Generated CSS: ${fullCssPath}`);
|
|
1742
1784
|
console.log(`✓ File size: ${(css.length / 1024).toFixed(2)} KB (unminified)`);
|
|
1743
1785
|
console.log('Full framework build complete');
|
|
@@ -1753,11 +1795,16 @@ function minify(css) {
|
|
|
1753
1795
|
.trim();
|
|
1754
1796
|
}
|
|
1755
1797
|
|
|
1756
|
-
function buildProductionCss() {
|
|
1798
|
+
function buildProductionCss(options = {}) {
|
|
1757
1799
|
const config = getConfig();
|
|
1758
1800
|
const sourceDir = getSourceDir(config);
|
|
1759
1801
|
const fullCssPath = getFullCssPath(config);
|
|
1760
1802
|
const productionCssPath = getProductionCssPath(config);
|
|
1803
|
+
const profile = {
|
|
1804
|
+
purge: 0,
|
|
1805
|
+
minify: 0,
|
|
1806
|
+
write: 0,
|
|
1807
|
+
};
|
|
1761
1808
|
|
|
1762
1809
|
if (!fs.existsSync(fullCssPath)) {
|
|
1763
1810
|
buildFullFramework();
|
|
@@ -1765,11 +1812,18 @@ function buildProductionCss() {
|
|
|
1765
1812
|
|
|
1766
1813
|
const { purgeCSS } = require('./purge.js');
|
|
1767
1814
|
const css = fs.readFileSync(fullCssPath, 'utf8');
|
|
1815
|
+
const purgeStart = Date.now();
|
|
1768
1816
|
const purged = purgeCSS(css, sourceDir, config);
|
|
1817
|
+
profile.purge = Date.now() - purgeStart;
|
|
1818
|
+
|
|
1819
|
+
const minifyStart = Date.now();
|
|
1769
1820
|
const minified = minify(purged);
|
|
1821
|
+
profile.minify = Date.now() - minifyStart;
|
|
1770
1822
|
|
|
1823
|
+
const writeStart = Date.now();
|
|
1771
1824
|
ensureDirectoryForFile(productionCssPath);
|
|
1772
1825
|
fs.writeFileSync(productionCssPath, minified);
|
|
1826
|
+
profile.write = Date.now() - writeStart;
|
|
1773
1827
|
|
|
1774
1828
|
return {
|
|
1775
1829
|
css,
|
|
@@ -1779,6 +1833,7 @@ function buildProductionCss() {
|
|
|
1779
1833
|
outputSize: Buffer.byteLength(minified, 'utf8'),
|
|
1780
1834
|
outputPath: productionCssPath,
|
|
1781
1835
|
fullCssPath,
|
|
1836
|
+
profile: options.profile ? profile : undefined,
|
|
1782
1837
|
};
|
|
1783
1838
|
}
|
|
1784
1839
|
|
|
@@ -1800,11 +1855,14 @@ function ensureFullFramework() {
|
|
|
1800
1855
|
}
|
|
1801
1856
|
|
|
1802
1857
|
function build(options = {}) {
|
|
1858
|
+
const totalStart = Date.now();
|
|
1859
|
+
const frameworkStart = Date.now();
|
|
1803
1860
|
ensureFullFramework();
|
|
1861
|
+
const frameworkMs = Date.now() - frameworkStart;
|
|
1804
1862
|
|
|
1805
1863
|
const config = getConfig();
|
|
1806
1864
|
const fullCssPath = getFullCssPath(config);
|
|
1807
|
-
const result = buildProductionCss();
|
|
1865
|
+
const result = buildProductionCss({ profile: options.profile });
|
|
1808
1866
|
|
|
1809
1867
|
console.log('\u2713 Generated production CSS: ' + path.relative(process.cwd(), result.outputPath));
|
|
1810
1868
|
console.log('\u2713 File size: ' + (result.outputSize / 1024).toFixed(2) + ' KB');
|
|
@@ -1818,12 +1876,27 @@ function build(options = {}) {
|
|
|
1818
1876
|
}
|
|
1819
1877
|
}
|
|
1820
1878
|
|
|
1879
|
+
if (options.profile) {
|
|
1880
|
+
const timings = result.profile || { purge: 0, minify: 0, write: 0 };
|
|
1881
|
+
const totalMs = Date.now() - totalStart;
|
|
1882
|
+
|
|
1883
|
+
console.log('\nEmilyCSS build profile\n');
|
|
1884
|
+
console.log('Framework: ' + frameworkMs + 'ms');
|
|
1885
|
+
console.log('Purge: ' + timings.purge + 'ms');
|
|
1886
|
+
console.log('Minify: ' + timings.minify + 'ms');
|
|
1887
|
+
console.log('Write: ' + timings.write + 'ms');
|
|
1888
|
+
console.log('Total: ' + totalMs + 'ms');
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1821
1891
|
console.log('Build complete');
|
|
1822
1892
|
}
|
|
1823
1893
|
|
|
1824
1894
|
if (require.main === module) {
|
|
1825
1895
|
const args = process.argv.slice(2);
|
|
1826
|
-
build({
|
|
1896
|
+
build({
|
|
1897
|
+
keepFull: args.includes('--keep-full'),
|
|
1898
|
+
profile: args.includes('--profile'),
|
|
1899
|
+
});
|
|
1827
1900
|
}
|
|
1828
1901
|
|
|
1829
1902
|
module.exports = {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function toIntellisenseUtility(utility) {
|
|
2
|
+
return {
|
|
3
|
+
class: utility.class || null,
|
|
4
|
+
category: utility.category || null,
|
|
5
|
+
property: utility.property || null,
|
|
6
|
+
value: utility.value || null,
|
|
7
|
+
token: utility.token || null,
|
|
8
|
+
variants: Array.isArray(utility.variants) ? utility.variants : [],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function generateIntellisense(manifest) {
|
|
13
|
+
const utilities = Array.isArray(manifest && manifest.utilities)
|
|
14
|
+
? manifest.utilities
|
|
15
|
+
: [];
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
version: "1",
|
|
19
|
+
generatedAt:
|
|
20
|
+
(manifest && manifest.generatedAt) || new Date().toISOString(),
|
|
21
|
+
utilities: utilities.map(toIntellisenseUtility),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
generateIntellisense,
|
|
27
|
+
};
|
package/src/manifest.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const MANIFEST_VERSION = '1.1.0';
|
|
2
|
+
const packageJson = require('../package.json');
|
|
2
3
|
const {
|
|
3
4
|
DEFAULT_RESPONSIVE_VARIANTS,
|
|
4
5
|
BASE_VARIANTS,
|
|
@@ -38,18 +39,76 @@ function getTokenFromDeclarations(declarations) {
|
|
|
38
39
|
return null;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function
|
|
42
|
-
if (!selector || !selector.startsWith('.')) return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
function readLeadingClassSelector(selector) {
|
|
43
|
+
if (!selector || !selector.startsWith('.')) return null;
|
|
44
|
+
|
|
45
|
+
let classSelector = '.';
|
|
46
|
+
|
|
47
|
+
for (let i = 1; i < selector.length; i++) {
|
|
48
|
+
const char = selector[i];
|
|
49
|
+
|
|
50
|
+
if (char === '\\' && i + 1 < selector.length) {
|
|
51
|
+
classSelector += selector[i] + selector[i + 1];
|
|
52
|
+
i++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
char === ' ' ||
|
|
58
|
+
char === '\t' ||
|
|
59
|
+
char === '\n' ||
|
|
60
|
+
char === '\r' ||
|
|
61
|
+
char === ',' ||
|
|
62
|
+
char === '>' ||
|
|
63
|
+
char === '+' ||
|
|
64
|
+
char === '~' ||
|
|
65
|
+
char === ':' ||
|
|
66
|
+
char === '['
|
|
67
|
+
) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
classSelector += char;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return classSelector.length > 1 ? classSelector : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function extractManifestClassSelectors(selector) {
|
|
78
|
+
if (typeof selector !== 'string' || selector.trim().length === 0) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const classSelectors = [];
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
const selectorParts = selector
|
|
85
|
+
.split(',')
|
|
86
|
+
.map((part) => part.trim())
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
|
|
89
|
+
selectorParts.forEach((part) => {
|
|
90
|
+
const classSelector = readLeadingClassSelector(part);
|
|
91
|
+
if (!classSelector) return;
|
|
92
|
+
if (classSelector.includes('\\:')) return;
|
|
93
|
+
|
|
94
|
+
const remainder = part.slice(classSelector.length).trimStart();
|
|
95
|
+
|
|
96
|
+
// Expanded pseudo-state / pseudo-element and attribute selectors are not
|
|
97
|
+
// base utilities in the manifest (for example .hover\:x:hover, .x[...]).
|
|
98
|
+
if (
|
|
99
|
+
remainder.startsWith(':') ||
|
|
100
|
+
remainder.startsWith('::') ||
|
|
101
|
+
remainder.startsWith('[')
|
|
102
|
+
) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (seen.has(classSelector)) return;
|
|
107
|
+
seen.add(classSelector);
|
|
108
|
+
classSelectors.push(classSelector);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return classSelectors;
|
|
53
112
|
}
|
|
54
113
|
|
|
55
114
|
function inferCategory(className, property) {
|
|
@@ -249,7 +308,10 @@ function getVariants(config) {
|
|
|
249
308
|
|
|
250
309
|
function generateManifest(css, config = {}) {
|
|
251
310
|
const manifest = {
|
|
252
|
-
|
|
311
|
+
schemaVersion: '1',
|
|
312
|
+
package: packageJson.name || 'emily-css',
|
|
313
|
+
version: packageJson.version || '0.0.0',
|
|
314
|
+
manifestVersion: MANIFEST_VERSION,
|
|
253
315
|
generatedAt: new Date().toISOString(),
|
|
254
316
|
utilities: [],
|
|
255
317
|
};
|
|
@@ -266,21 +328,25 @@ function generateManifest(css, config = {}) {
|
|
|
266
328
|
while ((ruleMatch = ruleRegex.exec(cleanedCss)) !== null) {
|
|
267
329
|
const selector = ruleMatch[1].trim();
|
|
268
330
|
const declarationBlock = ruleMatch[2].trim();
|
|
269
|
-
|
|
270
|
-
if (
|
|
331
|
+
const classSelectors = extractManifestClassSelectors(selector);
|
|
332
|
+
if (classSelectors.length === 0) continue;
|
|
271
333
|
|
|
272
334
|
const { declarations, firstProperty, firstValue } = parseDeclarations(declarationBlock);
|
|
273
335
|
if (!firstProperty) continue;
|
|
274
336
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
337
|
+
classSelectors.forEach((classSelector) => {
|
|
338
|
+
const className = normalizeClassName(classSelector);
|
|
339
|
+
|
|
340
|
+
manifest.utilities.push({
|
|
341
|
+
class: className,
|
|
342
|
+
category: inferCategory(className, firstProperty),
|
|
343
|
+
property: firstProperty,
|
|
344
|
+
value: firstValue,
|
|
345
|
+
token: getTokenFromDeclarations(declarations),
|
|
346
|
+
declarations,
|
|
347
|
+
variants,
|
|
348
|
+
source: 'generated-css',
|
|
349
|
+
});
|
|
284
350
|
});
|
|
285
351
|
}
|
|
286
352
|
|
package/src/purge.js
CHANGED
|
@@ -36,23 +36,48 @@ function getAllFiles(dir, extensions = DEFAULT_EXTENSIONS) {
|
|
|
36
36
|
|
|
37
37
|
function extractClassNames(content) {
|
|
38
38
|
const classNames = new Set();
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
function isLikelyClassToken(token) {
|
|
41
|
+
if (!token || typeof token !== "string") return false;
|
|
42
|
+
if (token.length > 120) return false;
|
|
43
|
+
if (token.includes("://")) return false;
|
|
44
|
+
if (token.startsWith(".") || token.startsWith("#") || token.startsWith("@")) return false;
|
|
45
|
+
if (token.endsWith(":")) return false;
|
|
46
|
+
if (/[(){};,`$]/.test(token)) return false;
|
|
47
|
+
if (!/^[a-zA-Z0-9:#_./\-[\]]+$/.test(token)) return false;
|
|
48
|
+
if (!/[a-zA-Z]/.test(token)) return false;
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function addClassToken(token) {
|
|
54
|
+
if (!token) return;
|
|
55
|
+
|
|
56
|
+
const cleaned = token.trim().replace(/^['"`]+|['"`]+$/g, "");
|
|
57
|
+
if (!cleaned) return;
|
|
58
|
+
if (!isLikelyClassToken(cleaned)) return;
|
|
59
|
+
|
|
60
|
+
classNames.add(cleaned);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const classRegex = /(?:class|className)\s*=\s*(["'])([\s\S]*?)\1/g;
|
|
40
64
|
let match;
|
|
41
65
|
|
|
42
66
|
while ((match = classRegex.exec(content)) !== null) {
|
|
43
|
-
const classes = match[
|
|
44
|
-
classes.forEach(
|
|
45
|
-
if (cls.trim()) classNames.add(cls.trim());
|
|
46
|
-
});
|
|
67
|
+
const classes = match[2].split(/\s+/);
|
|
68
|
+
classes.forEach(addClassToken);
|
|
47
69
|
}
|
|
48
70
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
const vueBindingRegex = /(?:\:class|v-bind:class)\s*=\s*(["'])([\s\S]*?)\1/g;
|
|
72
|
+
const vueObjectKeyRegex = /['"`]([^'"`]+)['"`]\s*:/g;
|
|
73
|
+
|
|
74
|
+
while ((match = vueBindingRegex.exec(content)) !== null) {
|
|
75
|
+
const bindingContent = match[2];
|
|
76
|
+
let keyMatch;
|
|
77
|
+
|
|
78
|
+
while ((keyMatch = vueObjectKeyRegex.exec(bindingContent)) !== null) {
|
|
79
|
+
addClassToken(keyMatch[1]);
|
|
80
|
+
}
|
|
56
81
|
}
|
|
57
82
|
|
|
58
83
|
const templateStringRegex = /`([^`]+)`/g;
|
|
@@ -62,14 +87,9 @@ function extractClassNames(content) {
|
|
|
62
87
|
|
|
63
88
|
possibleClasses.forEach((cls) => {
|
|
64
89
|
const cleaned = cls.trim();
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
/^[a-zA-Z0-9:_./-]+$/.test(cleaned) &&
|
|
69
|
-
/[-:]/.test(cleaned)
|
|
70
|
-
) {
|
|
71
|
-
classNames.add(cleaned);
|
|
72
|
-
}
|
|
90
|
+
if (!cleaned) return;
|
|
91
|
+
if (!/[-:]/.test(cleaned)) return;
|
|
92
|
+
addClassToken(cleaned);
|
|
73
93
|
});
|
|
74
94
|
}
|
|
75
95
|
|
package/templates/showcase.html
CHANGED
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
</div>
|
|
150
150
|
</section>
|
|
151
151
|
|
|
152
|
-
<section id="buttons" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6
|
|
152
|
+
<section id="buttons" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
|
|
153
153
|
<h2 class="text-2xl font-bold mb-4">Buttons</h2>
|
|
154
154
|
|
|
155
155
|
<div class="flex flex-wrap gap-3">
|