esm-styles 0.4.2 → 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 +44 -15
- package/package.json +1 -1
package/dist/lib/build.js
CHANGED
|
@@ -39,6 +39,33 @@ function createAliasPlugin(aliases, sourcePath) {
|
|
|
39
39
|
function escapeRegex(str) {
|
|
40
40
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
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.
|
|
54
|
+
*/
|
|
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
|
+
}
|
|
64
|
+
const tmpPath = `${filePath}.tmp`;
|
|
65
|
+
await fs.writeFile(tmpPath, data, encoding);
|
|
66
|
+
await fs.rename(tmpPath, filePath);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
42
69
|
/**
|
|
43
70
|
* Import a module with alias resolution using esbuild.
|
|
44
71
|
* Falls back to direct import when no aliases are configured.
|
|
@@ -93,6 +120,9 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
93
120
|
// Ensure output directory exists
|
|
94
121
|
await fs.mkdir(outputPath, { recursive: true });
|
|
95
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;
|
|
96
126
|
// 2. Process globalVariables
|
|
97
127
|
if (config.globalVariables) {
|
|
98
128
|
const inputFile = path.join(sourcePath, `${config.globalVariables}${suffix}`);
|
|
@@ -103,7 +133,7 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
103
133
|
const rootSelector = config.globalRootSelector || ':root';
|
|
104
134
|
const comment = generateCssComment(config.globalVariables);
|
|
105
135
|
const wrappedCss = `${comment}${rootSelector} {\n${cssVars}\n}`;
|
|
106
|
-
await
|
|
136
|
+
changed = (await writeAtomic(outputFile, wrappedCss)) || changed;
|
|
107
137
|
cssFiles.push({ type: 'global', file: 'global.css' });
|
|
108
138
|
}
|
|
109
139
|
// 3. Process media variable sets
|
|
@@ -141,7 +171,7 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
141
171
|
}
|
|
142
172
|
const comment = generateCssComment(setName);
|
|
143
173
|
const block = `${comment}${fullSelector} {\n${cssVars}\n}`;
|
|
144
|
-
await
|
|
174
|
+
changed = (await writeAtomic(outputFile, block)) || changed;
|
|
145
175
|
cssFiles.push({
|
|
146
176
|
type: 'media',
|
|
147
177
|
file: fileName,
|
|
@@ -226,7 +256,7 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
226
256
|
const supportingModuleObj = buildSupportingModule([], true);
|
|
227
257
|
const supportingModulePath = path.join(sourcePath, `$${mediaType}.mjs`);
|
|
228
258
|
const moduleContent = `export default ${JSON.stringify(supportingModuleObj, null, 2)}\n`;
|
|
229
|
-
await
|
|
259
|
+
changed = (await writeAtomic(supportingModulePath, moduleContent)) || changed;
|
|
230
260
|
}
|
|
231
261
|
}
|
|
232
262
|
// 4. Process each floor (replaces legacy layers)
|
|
@@ -270,7 +300,7 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
270
300
|
});
|
|
271
301
|
wrappedCss = result.code;
|
|
272
302
|
}
|
|
273
|
-
await
|
|
303
|
+
changed = (await writeAtomic(outputFile, wrappedCss)) || changed;
|
|
274
304
|
// Calculate relative path from default output directory for imports
|
|
275
305
|
const relativePath = floorOutputPath
|
|
276
306
|
? path.relative(outputPath, outputFile)
|
|
@@ -319,17 +349,16 @@ export async function build(configPath = 'esm-styles.config.js') {
|
|
|
319
349
|
.filter(Boolean)
|
|
320
350
|
.join('\n') +
|
|
321
351
|
'\n';
|
|
322
|
-
await
|
|
323
|
-
// 6. Create timestamp file
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
await fs.rename(timestampTmpPath, timestampPath);
|
|
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
|
+
}
|
|
333
362
|
}
|
|
334
363
|
// Helper for file URL import
|
|
335
364
|
import { pathToFileURL as pathToFileUrl } from 'node:url';
|