esm-styles 0.4.3 → 0.4.4
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/dist/lib/build.js +37 -13
- package/package.json +1 -1
package/dist/lib/build.js
CHANGED
|
@@ -40,15 +40,31 @@ function escapeRegex(str) {
|
|
|
40
40
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* Write a file atomically
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
43
|
+
* Write a file atomically, skipping the write when the on-disk content already
|
|
44
|
+
* matches. Reads the current file first; if it is identical there is no write,
|
|
45
|
+
* no temp file, and no inode swap, so file watchers (Vite, Cursor) stay quiet
|
|
46
|
+
* and no needless HMR reload is triggered.
|
|
47
|
+
*
|
|
48
|
+
* When the content differs (or the file is missing) it writes to a temp file in
|
|
49
|
+
* the same directory and renames it over the target. rename() is atomic on the
|
|
50
|
+
* same filesystem and swaps the inode, so watchers pick up a single clean
|
|
51
|
+
* replacement instead of observing a truncate-then-write mid-flight.
|
|
52
|
+
*
|
|
53
|
+
* Returns true if the file was written, false if it was unchanged and skipped.
|
|
47
54
|
*/
|
|
48
55
|
async function writeAtomic(filePath, data, encoding = 'utf8') {
|
|
56
|
+
try {
|
|
57
|
+
const existing = await fs.readFile(filePath, encoding);
|
|
58
|
+
if (existing === data)
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// File does not exist (or is unreadable) -> fall through and write it.
|
|
63
|
+
}
|
|
49
64
|
const tmpPath = `${filePath}.tmp`;
|
|
50
65
|
await fs.writeFile(tmpPath, data, encoding);
|
|
51
66
|
await fs.rename(tmpPath, filePath);
|
|
67
|
+
return true;
|
|
52
68
|
}
|
|
53
69
|
/**
|
|
54
70
|
* Import a module with alias resolution using esbuild.
|
|
@@ -104,6 +120,9 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
104
120
|
// Ensure output directory exists
|
|
105
121
|
await fs.mkdir(outputPath, { recursive: true });
|
|
106
122
|
const cssFiles = [];
|
|
123
|
+
// Track whether any output file actually changed on disk. Used to decide
|
|
124
|
+
// whether the timestamp (an HMR trigger) needs to be bumped at the end.
|
|
125
|
+
let changed = false;
|
|
107
126
|
// 2. Process globalVariables
|
|
108
127
|
if (config.globalVariables) {
|
|
109
128
|
const inputFile = path.join(sourcePath, `${config.globalVariables}${suffix}`);
|
|
@@ -114,7 +133,7 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
114
133
|
const rootSelector = config.globalRootSelector || ':root';
|
|
115
134
|
const comment = generateCssComment(config.globalVariables);
|
|
116
135
|
const wrappedCss = `${comment}${rootSelector} {\n${cssVars}\n}`;
|
|
117
|
-
await writeAtomic(outputFile, wrappedCss);
|
|
136
|
+
changed = (await writeAtomic(outputFile, wrappedCss)) || changed;
|
|
118
137
|
cssFiles.push({ type: 'global', file: 'global.css' });
|
|
119
138
|
}
|
|
120
139
|
// 3. Process media variable sets
|
|
@@ -152,7 +171,7 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
152
171
|
}
|
|
153
172
|
const comment = generateCssComment(setName);
|
|
154
173
|
const block = `${comment}${fullSelector} {\n${cssVars}\n}`;
|
|
155
|
-
await writeAtomic(outputFile, block);
|
|
174
|
+
changed = (await writeAtomic(outputFile, block)) || changed;
|
|
156
175
|
cssFiles.push({
|
|
157
176
|
type: 'media',
|
|
158
177
|
file: fileName,
|
|
@@ -237,7 +256,7 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
237
256
|
const supportingModuleObj = buildSupportingModule([], true);
|
|
238
257
|
const supportingModulePath = path.join(sourcePath, `$${mediaType}.mjs`);
|
|
239
258
|
const moduleContent = `export default ${JSON.stringify(supportingModuleObj, null, 2)}\n`;
|
|
240
|
-
await writeAtomic(supportingModulePath, moduleContent);
|
|
259
|
+
changed = (await writeAtomic(supportingModulePath, moduleContent)) || changed;
|
|
241
260
|
}
|
|
242
261
|
}
|
|
243
262
|
// 4. Process each floor (replaces legacy layers)
|
|
@@ -281,7 +300,7 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
281
300
|
});
|
|
282
301
|
wrappedCss = result.code;
|
|
283
302
|
}
|
|
284
|
-
await writeAtomic(outputFile, wrappedCss);
|
|
303
|
+
changed = (await writeAtomic(outputFile, wrappedCss)) || changed;
|
|
285
304
|
// Calculate relative path from default output directory for imports
|
|
286
305
|
const relativePath = floorOutputPath
|
|
287
306
|
? path.relative(outputPath, outputFile)
|
|
@@ -330,11 +349,16 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
330
349
|
.filter(Boolean)
|
|
331
350
|
.join('\n') +
|
|
332
351
|
'\n';
|
|
333
|
-
await writeAtomic(mainCssPath, mainCss);
|
|
334
|
-
// 6. Create timestamp file
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
352
|
+
changed = (await writeAtomic(mainCssPath, mainCss)) || changed;
|
|
353
|
+
// 6. Create timestamp file.
|
|
354
|
+
// The timestamp embeds Date.now() and is the HMR trigger, so only bump it
|
|
355
|
+
// when at least one output actually changed. A no-op rebuild leaves every
|
|
356
|
+
// file (including the timestamp) untouched and triggers no reload.
|
|
357
|
+
if (changed) {
|
|
358
|
+
const { outputPath: timestampOutputPath, extension: timestampExtension } = config.timestamp || { outputPath: '', extension: 'mjs' };
|
|
359
|
+
const timestampPath = path.join(config.basePath || '.', timestampOutputPath, 'timestamp.' + timestampExtension);
|
|
360
|
+
await writeAtomic(timestampPath, `export default ${Date.now()}`);
|
|
361
|
+
}
|
|
338
362
|
}
|
|
339
363
|
// Helper for file URL import
|
|
340
364
|
import { pathToFileURL as pathToFileUrl } from 'node:url';
|