emily-css 1.0.17 → 1.0.19
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 +13 -0
- package/README.md +21 -29
- package/bin/emilyui.js +4 -8
- package/package.json +7 -2
- package/src/index.js +86 -77
- package/src/init.js +224 -116
- package/src/purge.js +123 -56
- package/src/showcase.js +84 -39
- package/src/watch.js +145 -57
- package/src/purge-cmd.js +0 -55
package/src/watch.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const chokidar = require(
|
|
4
|
-
const chalk = require(
|
|
5
|
-
const
|
|
6
|
-
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const chokidar = require("chokidar");
|
|
4
|
+
const chalk = require("chalk");
|
|
5
|
+
const fg = require("fast-glob");
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
buildFullFramework,
|
|
9
|
+
buildProductionCss,
|
|
10
|
+
ensureFullFramework,
|
|
11
|
+
} = require("./index.js");
|
|
12
|
+
|
|
13
|
+
const { extractClassNames } = require("./purge.js");
|
|
7
14
|
|
|
8
15
|
let isRunning = false;
|
|
9
16
|
let pendingRun = false;
|
|
@@ -11,31 +18,35 @@ let previousClasses = new Set();
|
|
|
11
18
|
let hasRunOnce = false;
|
|
12
19
|
|
|
13
20
|
function readConfig() {
|
|
14
|
-
const configPath = path.join(process.cwd(),
|
|
21
|
+
const configPath = path.join(process.cwd(), "emily.config.json");
|
|
15
22
|
|
|
16
23
|
if (!fs.existsSync(configPath)) {
|
|
17
24
|
console.error('\n emily-css: No config found. Run "emily-css init" first.\n');
|
|
18
25
|
process.exit(1);
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
return JSON.parse(fs.readFileSync(configPath,
|
|
28
|
+
return JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalisePath(filePath) {
|
|
32
|
+
return filePath.replace(/\\/g, "/");
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
function shouldIgnore(filePath) {
|
|
25
|
-
const normalised = filePath
|
|
36
|
+
const normalised = normalisePath(filePath);
|
|
26
37
|
|
|
27
38
|
return [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
].some(part => normalised.includes(
|
|
39
|
+
"node_modules/",
|
|
40
|
+
".git/",
|
|
41
|
+
".nuxt/",
|
|
42
|
+
".next/",
|
|
43
|
+
".output/",
|
|
44
|
+
"dist/",
|
|
45
|
+
"build/",
|
|
46
|
+
"coverage/",
|
|
47
|
+
".cache/",
|
|
48
|
+
".vite/",
|
|
49
|
+
].some((part) => normalised.includes("/" + part) || normalised.startsWith(part));
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
function runQuietly(fn) {
|
|
@@ -53,16 +64,58 @@ function runQuietly(fn) {
|
|
|
53
64
|
}
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
function
|
|
57
|
-
const
|
|
67
|
+
function getScanFiles(config) {
|
|
68
|
+
const sourceGlobs = config.purge?.sourceGlobs;
|
|
69
|
+
|
|
70
|
+
if (sourceGlobs && sourceGlobs.length) {
|
|
71
|
+
return fg.sync(sourceGlobs, {
|
|
72
|
+
ignore: config.purge?.ignore || [],
|
|
73
|
+
onlyFiles: true,
|
|
74
|
+
unique: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sourceDir = config.purge?.sourceDir || ".";
|
|
79
|
+
const extensions = config.purge?.extensions || [
|
|
80
|
+
".html",
|
|
81
|
+
".htm",
|
|
82
|
+
".twig",
|
|
83
|
+
".njk",
|
|
84
|
+
".liquid",
|
|
85
|
+
".hbs",
|
|
86
|
+
".jsx",
|
|
87
|
+
".tsx",
|
|
88
|
+
".vue",
|
|
89
|
+
".php",
|
|
90
|
+
".astro",
|
|
91
|
+
".svelte",
|
|
92
|
+
".blade.php",
|
|
93
|
+
".jinja",
|
|
94
|
+
".jinja2",
|
|
95
|
+
".j2",
|
|
96
|
+
".md",
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
return fg.sync(
|
|
100
|
+
extensions.map((ext) => `${sourceDir.replace(/\/$/, "")}/**/*${ext}`),
|
|
101
|
+
{
|
|
102
|
+
ignore: config.purge?.ignore || [],
|
|
103
|
+
onlyFiles: true,
|
|
104
|
+
unique: true,
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function collectUsedClasses(config) {
|
|
110
|
+
const files = getScanFiles(config);
|
|
58
111
|
const usedClasses = new Set();
|
|
59
112
|
|
|
60
113
|
for (const file of files) {
|
|
61
114
|
if (shouldIgnore(file)) continue;
|
|
62
115
|
|
|
63
116
|
try {
|
|
64
|
-
const content = fs.readFileSync(file,
|
|
65
|
-
extractClassNames(content).forEach(cls => usedClasses.add(cls));
|
|
117
|
+
const content = fs.readFileSync(file, "utf8");
|
|
118
|
+
extractClassNames(content).forEach((cls) => usedClasses.add(cls));
|
|
66
119
|
} catch {}
|
|
67
120
|
}
|
|
68
121
|
|
|
@@ -70,8 +123,8 @@ function collectUsedClasses(sourceDir, config) {
|
|
|
70
123
|
}
|
|
71
124
|
|
|
72
125
|
function getClassDiff(currentClasses) {
|
|
73
|
-
const added = [...currentClasses].filter(cls => !previousClasses.has(cls));
|
|
74
|
-
const removed = [...previousClasses].filter(cls => !currentClasses.has(cls));
|
|
126
|
+
const added = [...currentClasses].filter((cls) => !previousClasses.has(cls));
|
|
127
|
+
const removed = [...previousClasses].filter((cls) => !currentClasses.has(cls));
|
|
75
128
|
|
|
76
129
|
previousClasses = new Set(currentClasses);
|
|
77
130
|
|
|
@@ -79,32 +132,63 @@ function getClassDiff(currentClasses) {
|
|
|
79
132
|
}
|
|
80
133
|
|
|
81
134
|
function formatClassList(classes) {
|
|
82
|
-
if (classes.length === 0) return
|
|
135
|
+
if (classes.length === 0) return "";
|
|
83
136
|
|
|
84
|
-
const shown = classes.slice(0, 8).join(
|
|
85
|
-
const extra = classes.length > 8 ?
|
|
137
|
+
const shown = classes.slice(0, 8).join(", ");
|
|
138
|
+
const extra = classes.length > 8 ? " +" + (classes.length - 8) + " more" : "";
|
|
86
139
|
|
|
87
140
|
return shown + extra;
|
|
88
141
|
}
|
|
89
142
|
|
|
90
143
|
function printSummary({ currentClasses, result, added, removed }) {
|
|
91
|
-
const reduction = (
|
|
144
|
+
const reduction = (
|
|
145
|
+
((result.originalSize - result.outputSize) / result.originalSize) *
|
|
146
|
+
100
|
|
147
|
+
).toFixed(1);
|
|
148
|
+
|
|
92
149
|
const sizeKb = (result.outputSize / 1024).toFixed(1);
|
|
150
|
+
const outputPath = result.outputPath
|
|
151
|
+
? path.relative(process.cwd(), result.outputPath)
|
|
152
|
+
: "emily.min.css";
|
|
153
|
+
|
|
93
154
|
const time = new Date().toLocaleTimeString();
|
|
94
155
|
|
|
95
156
|
console.log(
|
|
96
|
-
chalk.green(
|
|
97
|
-
|
|
157
|
+
chalk.green("✓ " + time + " updated") +
|
|
158
|
+
chalk.gray(
|
|
159
|
+
" | " +
|
|
160
|
+
currentClasses.size +
|
|
161
|
+
" classes | " +
|
|
162
|
+
sizeKb +
|
|
163
|
+
" KB | " +
|
|
164
|
+
reduction +
|
|
165
|
+
"% reduced | " +
|
|
166
|
+
outputPath,
|
|
167
|
+
),
|
|
98
168
|
);
|
|
99
169
|
|
|
100
170
|
if (!hasRunOnce) return;
|
|
101
171
|
|
|
102
172
|
if (removed.length > 0) {
|
|
103
|
-
console.log(
|
|
173
|
+
console.log(
|
|
174
|
+
chalk.red(
|
|
175
|
+
"− removed " +
|
|
176
|
+
removed.length +
|
|
177
|
+
" class" +
|
|
178
|
+
(removed.length === 1 ? "" : "es"),
|
|
179
|
+
) + chalk.gray(" (" + formatClassList(removed) + ")"),
|
|
180
|
+
);
|
|
104
181
|
}
|
|
105
182
|
|
|
106
183
|
if (added.length > 0) {
|
|
107
|
-
console.log(
|
|
184
|
+
console.log(
|
|
185
|
+
chalk.green(
|
|
186
|
+
"+ added " +
|
|
187
|
+
added.length +
|
|
188
|
+
" class" +
|
|
189
|
+
(added.length === 1 ? "" : "es"),
|
|
190
|
+
) + chalk.gray(" (" + formatClassList(added) + ")"),
|
|
191
|
+
);
|
|
108
192
|
}
|
|
109
193
|
}
|
|
110
194
|
|
|
@@ -118,25 +202,24 @@ function runProductionUpdate(filePath) {
|
|
|
118
202
|
|
|
119
203
|
try {
|
|
120
204
|
const config = readConfig();
|
|
121
|
-
const
|
|
122
|
-
const isConfigChange =
|
|
123
|
-
const cssPath = path.join(process.cwd(), 'dist/emily.css');
|
|
205
|
+
const normalisedFilePath = filePath ? normalisePath(filePath) : "";
|
|
206
|
+
const isConfigChange = normalisedFilePath.endsWith("emily.config.json");
|
|
124
207
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
} else {
|
|
128
|
-
|
|
129
|
-
}
|
|
208
|
+
if (isConfigChange) {
|
|
209
|
+
runQuietly(() => buildFullFramework());
|
|
210
|
+
} else {
|
|
211
|
+
runQuietly(() => ensureFullFramework());
|
|
212
|
+
}
|
|
130
213
|
|
|
131
214
|
const result = runQuietly(() => buildProductionCss());
|
|
132
|
-
const currentClasses = collectUsedClasses(
|
|
215
|
+
const currentClasses = collectUsedClasses(config);
|
|
133
216
|
const { added, removed } = getClassDiff(currentClasses);
|
|
134
217
|
|
|
135
218
|
printSummary({ currentClasses, result, added, removed });
|
|
136
219
|
|
|
137
220
|
hasRunOnce = true;
|
|
138
221
|
} catch (error) {
|
|
139
|
-
console.error(
|
|
222
|
+
console.error("\n❌ EmilyUI watch failed");
|
|
140
223
|
console.error(error.message);
|
|
141
224
|
} finally {
|
|
142
225
|
isRunning = false;
|
|
@@ -150,8 +233,8 @@ function runProductionUpdate(filePath) {
|
|
|
150
233
|
|
|
151
234
|
function getWatchPaths(config) {
|
|
152
235
|
return [
|
|
153
|
-
config.purge?.sourceDir ||
|
|
154
|
-
|
|
236
|
+
config.purge?.sourceDir || ".",
|
|
237
|
+
"emily.config.json",
|
|
155
238
|
];
|
|
156
239
|
}
|
|
157
240
|
|
|
@@ -164,9 +247,14 @@ function runWatch() {
|
|
|
164
247
|
const config = readConfig();
|
|
165
248
|
const watchPaths = getWatchPaths(config);
|
|
166
249
|
|
|
167
|
-
console.log(
|
|
168
|
-
|
|
169
|
-
|
|
250
|
+
console.log("\n👀 EmilyUI is watching...");
|
|
251
|
+
console.log(chalk.gray(" Project: " + (config.purge?.projectType || "Unknown")));
|
|
252
|
+
console.log(chalk.gray(" Output: " + (config.output?.css || "dist/emily.min.css")));
|
|
253
|
+
console.log(chalk.gray(" Watching:"));
|
|
254
|
+
|
|
255
|
+
watchPaths.forEach((item) => {
|
|
256
|
+
console.log(chalk.gray(" - " + item));
|
|
257
|
+
});
|
|
170
258
|
|
|
171
259
|
runQuietly(() => ensureFullFramework());
|
|
172
260
|
runProductionUpdate();
|
|
@@ -176,18 +264,18 @@ function runWatch() {
|
|
|
176
264
|
ignoreInitial: true,
|
|
177
265
|
awaitWriteFinish: {
|
|
178
266
|
stabilityThreshold: 500,
|
|
179
|
-
pollInterval: 100
|
|
180
|
-
}
|
|
267
|
+
pollInterval: 100,
|
|
268
|
+
},
|
|
181
269
|
});
|
|
182
270
|
|
|
183
|
-
watcher.on(
|
|
184
|
-
watcher.on(
|
|
185
|
-
watcher.on(
|
|
271
|
+
watcher.on("change", queueUpdate);
|
|
272
|
+
watcher.on("add", queueUpdate);
|
|
273
|
+
watcher.on("unlink", queueUpdate);
|
|
186
274
|
|
|
187
|
-
watcher.on(
|
|
188
|
-
console.error(
|
|
275
|
+
watcher.on("error", (error) => {
|
|
276
|
+
console.error("\n❌ EmilyUI watcher error");
|
|
189
277
|
console.error(error.message);
|
|
190
278
|
});
|
|
191
279
|
}
|
|
192
280
|
|
|
193
|
-
runWatch();
|
|
281
|
+
runWatch();
|
package/src/purge-cmd.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
function runPurge() {
|
|
5
|
-
const configPath = path.join(process.cwd(), 'emily.config.json');
|
|
6
|
-
|
|
7
|
-
if (!fs.existsSync(configPath)) {
|
|
8
|
-
console.error('\n emily-css: No config found. Run "emily-css init" first.\n');
|
|
9
|
-
process.exit(1);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
13
|
-
const sourceDir = config.purge && config.purge.sourceDir;
|
|
14
|
-
|
|
15
|
-
if (!sourceDir) {
|
|
16
|
-
console.error('\n emily-css: No purge sourceDir in emily.config.json.');
|
|
17
|
-
console.error(' Add: "purge": { "sourceDir": "./src" }\n');
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const cssPath = path.join(process.cwd(), 'dist/emily.css');
|
|
22
|
-
|
|
23
|
-
if (!fs.existsSync(cssPath)) {
|
|
24
|
-
console.error('\n emily-css: No CSS found. Run "emily-css build" first.\n');
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const { purgeCSS } = require('./purge.js');
|
|
29
|
-
|
|
30
|
-
console.log('\nPurging unused utilities from ' + sourceDir + '...');
|
|
31
|
-
|
|
32
|
-
const css = fs.readFileSync(cssPath, 'utf8');
|
|
33
|
-
const purged = purgeCSS(css, sourceDir, config);
|
|
34
|
-
const minified = purged
|
|
35
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
36
|
-
.replace(/\s+/g, ' ')
|
|
37
|
-
.replace(/\s?\{/g, '{')
|
|
38
|
-
.replace(/\s?\}/g, '}')
|
|
39
|
-
.replace(/;\s/g, ';')
|
|
40
|
-
.trim();
|
|
41
|
-
|
|
42
|
-
fs.writeFileSync(path.join(process.cwd(), 'dist/emily.purged.css'), purged);
|
|
43
|
-
fs.writeFileSync(path.join(process.cwd(), 'dist/emily.purged.min.css'), minified);
|
|
44
|
-
|
|
45
|
-
const original = Buffer.byteLength(css, 'utf8');
|
|
46
|
-
const purgedSize = Buffer.byteLength(purged, 'utf8');
|
|
47
|
-
const reduction = Math.round((1 - purgedSize / original) * 100);
|
|
48
|
-
|
|
49
|
-
console.log('\n\u2713 Purged CSS written:');
|
|
50
|
-
console.log(' dist/emily.purged.css');
|
|
51
|
-
console.log(' dist/emily.purged.min.css');
|
|
52
|
-
console.log('\n ' + Math.round(original / 1024) + 'KB -> ' + Math.round(purgedSize / 1024) + 'KB (' + reduction + '% reduction)\n');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
runPurge();
|