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.
Files changed (2) hide show
  1. package/dist/lib/build.js +37 -13
  2. 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: write to a temp file in the same directory, then
44
- * rename it over the target. rename() is atomic on the same filesystem and
45
- * swaps the inode, so file watchers (Vite, Cursor) reliably pick up a single
46
- * clean replacement instead of observing a truncate-then-write mid-flight.
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
- const { outputPath: timestampOutputPath, extension: timestampExtension } = config.timestamp || { outputPath: '', extension: 'mjs' };
336
- const timestampPath = path.join(config.basePath || '.', timestampOutputPath, 'timestamp.' + timestampExtension);
337
- await writeAtomic(timestampPath, `export default ${Date.now()}`);
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esm-styles",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "A library for working with ESM styles",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",