@greenfinity/rescript-typed-css-modules 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -50,9 +50,15 @@ Usage
50
50
  Options
51
51
  --watch, -w Watch for changes and regenerate bindings (directories only)
52
52
  --skip-initial, -s Skip initial compilation in watch mode
53
+ --force, -f Force compilation even if files are unchanged
54
+ --silent Only show "Generated" messages, suppress other output
55
+ --quiet, -q Suppress all output
53
56
  --output-dir, -o Directory to write generated .res files
54
57
  (multiple files or single directory only)
55
58
 
59
+ By default, files are skipped if the source CSS file has not been modified
60
+ since the last compilation. Use --force to always recompile.
61
+
56
62
  Examples
57
63
  $ css-to-rescript src/Card.module.scss
58
64
  $ css-to-rescript src/Theme.global.css
@@ -60,6 +66,7 @@ Examples
60
66
  $ css-to-rescript src/components
61
67
  $ css-to-rescript src/components src/pages --watch
62
68
  $ css-to-rescript src/components --watch --skip-initial
69
+ $ css-to-rescript src/components --force
63
70
  ```
64
71
 
65
72
  ## Example
@@ -26685,12 +26685,36 @@ function getOutputFileName(baseName, importType) {
26685
26685
  return baseName.replace(/\.global\.(css|scss)$/, "_CssGlobal") + ".res";
26686
26686
  }
26687
26687
  }
26688
- async function processFile(cssFilePath, outputDir) {
26688
+ function shouldCompile(cssFilePath, outputDir) {
26689
+ let match = getBaseNameAndImportType(cssFilePath);
26690
+ let outputFileName = getOutputFileName(match[0], match[1]);
26691
+ let outputPath = Nodepath.join(getOr(outputDir, Nodepath.dirname(cssFilePath)), outputFileName);
26692
+ if (!Nodefs.existsSync(outputPath)) {
26693
+ return true;
26694
+ }
26695
+ let sourceStat = Nodefs.lstatSync(cssFilePath);
26696
+ let outputStat = Nodefs.lstatSync(outputPath);
26697
+ return sourceStat.mtimeMs > outputStat.mtimeMs;
26698
+ }
26699
+ async function processFile(cssFilePath, outputDir, forceOpt, silentOpt, quietOpt) {
26700
+ let force = forceOpt !== void 0 ? forceOpt : false;
26701
+ let silent = silentOpt !== void 0 ? silentOpt : false;
26702
+ let quiet = quietOpt !== void 0 ? quietOpt : false;
26703
+ if (!force && !shouldCompile(cssFilePath, outputDir)) {
26704
+ if (!silent && !quiet) {
26705
+ console.log(`\u23ED\uFE0F Skipped: ` + cssFilePath + ` (unchanged)`);
26706
+ }
26707
+ return;
26708
+ }
26689
26709
  let content = Nodefs.readFileSync(cssFilePath).toString();
26690
- console.log(`Processing file: ` + cssFilePath);
26710
+ if (!silent && !quiet) {
26711
+ console.log(`Processing file: ` + cssFilePath);
26712
+ }
26691
26713
  let classNames = await extractClassNames(content, cssFilePath);
26692
26714
  if (classNames.length === 0) {
26693
- console.log(`\u26A0\uFE0F No classes found in ` + cssFilePath);
26715
+ if (!silent && !quiet) {
26716
+ console.log(`\u26A0\uFE0F No classes found in ` + cssFilePath);
26717
+ }
26694
26718
  return;
26695
26719
  }
26696
26720
  let match = getBaseNameAndImportType(cssFilePath);
@@ -26700,7 +26724,9 @@ async function processFile(cssFilePath, outputDir) {
26700
26724
  let bindings = generateReScriptBindings(baseName, importType, classNames);
26701
26725
  let outputPath = Nodepath.join(getOr(outputDir, Nodepath.dirname(cssFilePath)), outputFileName);
26702
26726
  Nodefs.writeFileSync(outputPath, Buffer.from(bindings));
26703
- console.log(`\u2705 Generated ` + outputPath + ` (` + classNames.length.toString() + ` classes)`);
26727
+ if (!quiet) {
26728
+ console.log(`\u2705 Generated ` + outputPath + ` (` + classNames.length.toString() + ` classes)`);
26729
+ }
26704
26730
  return [
26705
26731
  outputPath,
26706
26732
  classNames
@@ -26720,16 +26746,18 @@ function findCssFiles(dir) {
26720
26746
  }
26721
26747
  });
26722
26748
  }
26723
- async function watchDirectories(dirs, outputDir, skipInitial) {
26724
- console.log(`\u{1F440} Watching ` + dirs.length.toString() + ` directories for CSS module/global changes...`);
26725
- dirs.forEach((dir) => {
26726
- console.log(` ` + dir);
26727
- });
26728
- if (skipInitial) {
26729
- console.log(`Skipping initial compilation.`);
26730
- }
26731
- console.log(`Press Ctrl+C to stop.
26749
+ async function watchDirectories(dirs, outputDir, skipInitial, force, silent, quiet) {
26750
+ if (!silent && !quiet) {
26751
+ console.log(`\u{1F440} Watching ` + dirs.length.toString() + ` directories for CSS module/global changes...`);
26752
+ dirs.forEach((dir) => {
26753
+ console.log(` ` + dir);
26754
+ });
26755
+ if (skipInitial) {
26756
+ console.log(`Skipping initial compilation.`);
26757
+ }
26758
+ console.log(`Press Ctrl+C to stop.
26732
26759
  `);
26760
+ }
26733
26761
  let isIgnored = (path3) => {
26734
26762
  let isDotfile = /(^|[\/\\])\./.test(path3);
26735
26763
  let isCssFile = /\.(module|global)\.(css|scss)$/.test(path3);
@@ -26750,23 +26778,45 @@ async function watchDirectories(dirs, outputDir, skipInitial) {
26750
26778
  persistent: true
26751
26779
  }).on("ready", () => {
26752
26780
  ready.contents = true;
26753
- console.log(`Ready for changes.`);
26781
+ if (!silent && !quiet) {
26782
+ console.log(`Ready for changes.`);
26783
+ return;
26784
+ }
26754
26785
  }).on("change", (path3) => {
26755
- console.log(`
26756
- Changed: ` + path3);
26757
- processFile(path3, outputDir);
26786
+ if (!silent && !quiet) {
26787
+ console.log(`Changed: ` + path3);
26788
+ }
26789
+ processFile(path3, outputDir, true, silent, quiet);
26758
26790
  }).on("add", (path3) => {
26759
26791
  if (skipInitial && !ready.contents) {
26760
26792
  return;
26761
26793
  } else {
26762
- console.log(`
26763
- Added: ` + path3);
26764
- processFile(path3, outputDir);
26794
+ if (ready.contents) {
26795
+ if (!silent && !quiet) {
26796
+ console.log(`Added: ` + path3);
26797
+ }
26798
+ processFile(path3, outputDir, true, silent, quiet);
26799
+ } else {
26800
+ processFile(path3, outputDir, force, silent, quiet);
26801
+ }
26765
26802
  return;
26766
26803
  }
26767
26804
  }).on("unlink", (path3) => {
26768
- console.log(`
26769
- \u{1F5D1}\uFE0F Deleted: ` + path3);
26805
+ if (!silent && !quiet) {
26806
+ console.log(`\u{1F5D1}\uFE0F Deleted: ` + path3);
26807
+ }
26808
+ let match = getBaseNameAndImportType(path3);
26809
+ let outputFileName = getOutputFileName(match[0], match[1]);
26810
+ let outputPath = Nodepath.join(getOr(outputDir, Nodepath.dirname(path3)), outputFileName);
26811
+ if (Nodefs.existsSync(outputPath)) {
26812
+ Nodefs.unlinkSync(outputPath);
26813
+ if (!silent && !quiet) {
26814
+ console.log(`\u{1F5D1}\uFE0F Deleted compiled: ` + outputPath);
26815
+ return;
26816
+ } else {
26817
+ return;
26818
+ }
26819
+ }
26770
26820
  });
26771
26821
  }
26772
26822
  var helpText = `
@@ -26781,15 +26831,22 @@ var helpText = `
26781
26831
  Options
26782
26832
  --watch, -w Watch for changes and regenerate bindings (directories only)
26783
26833
  --skip-initial, -s Skip initial compilation in watch mode
26834
+ --force, -f Force compilation even if files are unchanged
26835
+ --silent Only show "Generated" messages, suppress other output
26836
+ --quiet, -q Suppress all output
26784
26837
  --output-dir, -o Directory to write generated .res files
26785
26838
  (multiple files or single directory only)
26786
26839
 
26840
+ By default, files are skipped if the source CSS file has not been modified
26841
+ since the last compilation. Use --force to always recompile.
26842
+
26787
26843
  Examples
26788
26844
  $ css-to-rescript src/Card.module.scss
26789
26845
  $ css-to-rescript src/Theme.global.css
26790
26846
  $ css-to-rescript src/Button.module.css src/Card.module.scss -o src/bindings
26791
26847
  $ css-to-rescript src/components
26792
26848
  $ css-to-rescript src/components src/pages --watch
26849
+ $ css-to-rescript src/components --force
26793
26850
  `;
26794
26851
  async function main() {
26795
26852
  let cli = meow(helpText, {
@@ -26808,6 +26865,20 @@ async function main() {
26808
26865
  type: "boolean",
26809
26866
  shortFlag: "s",
26810
26867
  default: false
26868
+ },
26869
+ force: {
26870
+ type: "boolean",
26871
+ shortFlag: "f",
26872
+ default: false
26873
+ },
26874
+ silent: {
26875
+ type: "boolean",
26876
+ default: false
26877
+ },
26878
+ quiet: {
26879
+ type: "boolean",
26880
+ shortFlag: "q",
26881
+ default: false
26811
26882
  }
26812
26883
  },
26813
26884
  allowUnknownFlags: false
@@ -26820,6 +26891,9 @@ async function main() {
26820
26891
  let outputDir = cli.flags.outputDir;
26821
26892
  let watchMode = cli.flags.watch;
26822
26893
  let skipInitial = cli.flags.skipInitial;
26894
+ let force = cli.flags.force;
26895
+ let silent = cli.flags.silent;
26896
+ let quiet = cli.flags.quiet;
26823
26897
  let match = reduce(inputPaths, [
26824
26898
  [],
26825
26899
  []
@@ -26857,21 +26931,23 @@ async function main() {
26857
26931
  if (files.length > 0) {
26858
26932
  await reduce(files, Promise.resolve(), async (acc, file) => {
26859
26933
  await acc;
26860
- await processFile(file, outputDir);
26934
+ await processFile(file, outputDir, force, silent, quiet);
26861
26935
  });
26862
26936
  }
26863
26937
  if (dirs.length <= 0) {
26864
26938
  return;
26865
26939
  }
26866
26940
  if (watchMode) {
26867
- return await watchDirectories(dirs, outputDir, skipInitial);
26941
+ return await watchDirectories(dirs, outputDir, skipInitial, force, silent, quiet);
26868
26942
  }
26869
26943
  let moduleFiles = dirs.flatMap(findCssFiles);
26870
- console.log(`Found ` + moduleFiles.length.toString() + ` CSS module files
26944
+ if (!silent && !quiet) {
26945
+ console.log(`Found ` + moduleFiles.length.toString() + ` CSS module files
26871
26946
  `);
26947
+ }
26872
26948
  return await reduce(moduleFiles, Promise.resolve(), async (acc, file) => {
26873
26949
  await acc;
26874
- await processFile(file, outputDir);
26950
+ await processFile(file, outputDir, force, silent, quiet);
26875
26951
  });
26876
26952
  }
26877
26953
  main();
@@ -26890,6 +26966,7 @@ export {
26890
26966
  helpText,
26891
26967
  main,
26892
26968
  processFile,
26969
+ shouldCompile,
26893
26970
  watchDirectories
26894
26971
  };
26895
26972
  /*! Bundled license information:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@greenfinity/rescript-typed-css-modules",
3
3
  "description": "Typed CSS Modules for ReScript",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -124,12 +124,37 @@ function getOutputFileName(baseName, importType) {
124
124
  }
125
125
  }
126
126
 
127
- async function processFile(cssFilePath, outputDir) {
127
+ function shouldCompile(cssFilePath, outputDir) {
128
+ let match = getBaseNameAndImportType(cssFilePath);
129
+ let outputFileName = getOutputFileName(match[0], match[1]);
130
+ let outputPath = Nodepath.join(Core__Option.getOr(outputDir, Nodepath.dirname(cssFilePath)), outputFileName);
131
+ if (!Nodefs.existsSync(outputPath)) {
132
+ return true;
133
+ }
134
+ let sourceStat = Nodefs.lstatSync(cssFilePath);
135
+ let outputStat = Nodefs.lstatSync(outputPath);
136
+ return sourceStat.mtimeMs > outputStat.mtimeMs;
137
+ }
138
+
139
+ async function processFile(cssFilePath, outputDir, forceOpt, silentOpt, quietOpt) {
140
+ let force = forceOpt !== undefined ? forceOpt : false;
141
+ let silent = silentOpt !== undefined ? silentOpt : false;
142
+ let quiet = quietOpt !== undefined ? quietOpt : false;
143
+ if (!force && !shouldCompile(cssFilePath, outputDir)) {
144
+ if (!silent && !quiet) {
145
+ console.log(`ā­ļø Skipped: ` + cssFilePath + ` (unchanged)`);
146
+ }
147
+ return;
148
+ }
128
149
  let content = Nodefs.readFileSync(cssFilePath).toString();
129
- console.log(`Processing file: ` + cssFilePath);
150
+ if (!silent && !quiet) {
151
+ console.log(`Processing file: ` + cssFilePath);
152
+ }
130
153
  let classNames = await extractClassNames(content, cssFilePath);
131
154
  if (classNames.length === 0) {
132
- console.log(`āš ļø No classes found in ` + cssFilePath);
155
+ if (!silent && !quiet) {
156
+ console.log(`āš ļø No classes found in ` + cssFilePath);
157
+ }
133
158
  return;
134
159
  }
135
160
  let match = getBaseNameAndImportType(cssFilePath);
@@ -139,7 +164,9 @@ async function processFile(cssFilePath, outputDir) {
139
164
  let bindings = generateReScriptBindings(baseName, importType, classNames);
140
165
  let outputPath = Nodepath.join(Core__Option.getOr(outputDir, Nodepath.dirname(cssFilePath)), outputFileName);
141
166
  Nodefs.writeFileSync(outputPath, Buffer.from(bindings));
142
- console.log(`āœ… Generated ` + outputPath + ` (` + classNames.length.toString() + ` classes)`);
167
+ if (!quiet) {
168
+ console.log(`āœ… Generated ` + outputPath + ` (` + classNames.length.toString() + ` classes)`);
169
+ }
143
170
  return [
144
171
  outputPath,
145
172
  classNames
@@ -161,15 +188,17 @@ function findCssFiles(dir) {
161
188
  });
162
189
  }
163
190
 
164
- async function watchDirectories(dirs, outputDir, skipInitial) {
165
- console.log(`šŸ‘€ Watching ` + dirs.length.toString() + ` directories for CSS module/global changes...`);
166
- dirs.forEach(dir => {
167
- console.log(` ` + dir);
168
- });
169
- if (skipInitial) {
170
- console.log(`Skipping initial compilation.`);
191
+ async function watchDirectories(dirs, outputDir, skipInitial, force, silent, quiet) {
192
+ if (!silent && !quiet) {
193
+ console.log(`šŸ‘€ Watching ` + dirs.length.toString() + ` directories for CSS module/global changes...`);
194
+ dirs.forEach(dir => {
195
+ console.log(` ` + dir);
196
+ });
197
+ if (skipInitial) {
198
+ console.log(`Skipping initial compilation.`);
199
+ }
200
+ console.log(`Press Ctrl+C to stop.\n`);
171
201
  }
172
- console.log(`Press Ctrl+C to stop.\n`);
173
202
  let isIgnored = path => {
174
203
  let isDotfile = /(^|[\/\\])\./.test(path);
175
204
  let isCssFile = /\.(module|global)\.(css|scss)$/.test(path);
@@ -190,20 +219,45 @@ async function watchDirectories(dirs, outputDir, skipInitial) {
190
219
  persistent: true
191
220
  }).on("ready", () => {
192
221
  ready.contents = true;
193
- console.log(`Ready for changes.`);
222
+ if (!silent && !quiet) {
223
+ console.log(`Ready for changes.`);
224
+ return;
225
+ }
194
226
  }).on("change", path => {
195
- console.log(`\nChanged: ` + path);
196
- processFile(path, outputDir);
227
+ if (!silent && !quiet) {
228
+ console.log(`Changed: ` + path);
229
+ }
230
+ processFile(path, outputDir, true, silent, quiet);
197
231
  }).on("add", path => {
198
232
  if (skipInitial && !ready.contents) {
199
233
  return;
200
234
  } else {
201
- console.log(`\nAdded: ` + path);
202
- processFile(path, outputDir);
235
+ if (ready.contents) {
236
+ if (!silent && !quiet) {
237
+ console.log(`Added: ` + path);
238
+ }
239
+ processFile(path, outputDir, true, silent, quiet);
240
+ } else {
241
+ processFile(path, outputDir, force, silent, quiet);
242
+ }
203
243
  return;
204
244
  }
205
245
  }).on("unlink", path => {
206
- console.log(`\nšŸ—‘ļø Deleted: ` + path);
246
+ if (!silent && !quiet) {
247
+ console.log(`šŸ—‘ļø Deleted: ` + path);
248
+ }
249
+ let match = getBaseNameAndImportType(path);
250
+ let outputFileName = getOutputFileName(match[0], match[1]);
251
+ let outputPath = Nodepath.join(Core__Option.getOr(outputDir, Nodepath.dirname(path)), outputFileName);
252
+ if (Nodefs.existsSync(outputPath)) {
253
+ Nodefs.unlinkSync(outputPath);
254
+ if (!silent && !quiet) {
255
+ console.log(`šŸ—‘ļø Deleted compiled: ` + outputPath);
256
+ return;
257
+ } else {
258
+ return;
259
+ }
260
+ }
207
261
  });
208
262
  }
209
263
 
@@ -219,15 +273,22 @@ let helpText = `
219
273
  Options
220
274
  --watch, -w Watch for changes and regenerate bindings (directories only)
221
275
  --skip-initial, -s Skip initial compilation in watch mode
276
+ --force, -f Force compilation even if files are unchanged
277
+ --silent Only show "Generated" messages, suppress other output
278
+ --quiet, -q Suppress all output
222
279
  --output-dir, -o Directory to write generated .res files
223
280
  (multiple files or single directory only)
224
281
 
282
+ By default, files are skipped if the source CSS file has not been modified
283
+ since the last compilation. Use --force to always recompile.
284
+
225
285
  Examples
226
286
  $ css-to-rescript src/Card.module.scss
227
287
  $ css-to-rescript src/Theme.global.css
228
288
  $ css-to-rescript src/Button.module.css src/Card.module.scss -o src/bindings
229
289
  $ css-to-rescript src/components
230
290
  $ css-to-rescript src/components src/pages --watch
291
+ $ css-to-rescript src/components --force
231
292
  `;
232
293
 
233
294
  async function main() {
@@ -247,6 +308,20 @@ async function main() {
247
308
  type: "boolean",
248
309
  shortFlag: "s",
249
310
  default: false
311
+ },
312
+ force: {
313
+ type: "boolean",
314
+ shortFlag: "f",
315
+ default: false
316
+ },
317
+ silent: {
318
+ type: "boolean",
319
+ default: false
320
+ },
321
+ quiet: {
322
+ type: "boolean",
323
+ shortFlag: "q",
324
+ default: false
250
325
  }
251
326
  },
252
327
  allowUnknownFlags: false
@@ -259,6 +334,9 @@ async function main() {
259
334
  let outputDir = cli.flags.outputDir;
260
335
  let watchMode = cli.flags.watch;
261
336
  let skipInitial = cli.flags.skipInitial;
337
+ let force = cli.flags.force;
338
+ let silent = cli.flags.silent;
339
+ let quiet = cli.flags.quiet;
262
340
  let match = Core__Array.reduce(inputPaths, [
263
341
  [],
264
342
  []
@@ -296,20 +374,22 @@ async function main() {
296
374
  if (files.length > 0) {
297
375
  await Core__Array.reduce(files, Promise.resolve(), async (acc, file) => {
298
376
  await acc;
299
- await processFile(file, outputDir);
377
+ await processFile(file, outputDir, force, silent, quiet);
300
378
  });
301
379
  }
302
380
  if (dirs.length <= 0) {
303
381
  return;
304
382
  }
305
383
  if (watchMode) {
306
- return await watchDirectories(dirs, outputDir, skipInitial);
384
+ return await watchDirectories(dirs, outputDir, skipInitial, force, silent, quiet);
307
385
  }
308
386
  let moduleFiles = dirs.flatMap(findCssFiles);
309
- console.log(`Found ` + moduleFiles.length.toString() + ` CSS module files\n`);
387
+ if (!silent && !quiet) {
388
+ console.log(`Found ` + moduleFiles.length.toString() + ` CSS module files\n`);
389
+ }
310
390
  return await Core__Array.reduce(moduleFiles, Promise.resolve(), async (acc, file) => {
311
391
  await acc;
312
- await processFile(file, outputDir);
392
+ await processFile(file, outputDir, force, silent, quiet);
313
393
  });
314
394
  }
315
395
 
@@ -326,6 +406,7 @@ export {
326
406
  generateReScriptBindings,
327
407
  getBaseNameAndImportType,
328
408
  getOutputFileName,
409
+ shouldCompile,
329
410
  processFile,
330
411
  findCssFiles,
331
412
  watchDirectories,
@@ -9,7 +9,7 @@ module Meow = {
9
9
  default?: bool,
10
10
  }
11
11
 
12
- type flags = {"watch": flag, "outputDir": flag, "skipInitial": flag}
12
+ type flags = {"watch": flag, "outputDir": flag, "skipInitial": flag, "force": flag, "silent": flag, "quiet": flag}
13
13
 
14
14
  type importMeta
15
15
 
@@ -21,7 +21,7 @@ module Meow = {
21
21
 
22
22
  type result = {
23
23
  input: array<string>,
24
- flags: {"watch": bool, "outputDir": option<string>, "skipInitial": bool},
24
+ flags: {"watch": bool, "outputDir": option<string>, "skipInitial": bool, "force": bool, "silent": bool, "quiet": bool},
25
25
  showHelp: unit => unit,
26
26
  }
27
27
 
@@ -188,28 +188,61 @@ let getOutputFileName = (baseName, importType) => {
188
188
  }
189
189
  }
190
190
 
191
- // Process a single CSS module file
192
- let processFile = async (cssFilePath, outputDir) => {
193
- let content = NodeJs.Fs.readFileSync(cssFilePath)->NodeJs.Buffer.toString
194
- Console.log(`Processing file: ${cssFilePath}`)
195
- let classNames = await extractClassNames(content, ~from=cssFilePath)
191
+ // Check if source file is newer than output file
192
+ // Returns true if compilation should proceed (source is newer or output doesn't exist)
193
+ let shouldCompile = (cssFilePath, outputDir) => {
194
+ let (baseName, importType) = cssFilePath->getBaseNameAndImportType
195
+ let outputFileName = getOutputFileName(baseName, importType)
196
+ let outputPath = NodeJs.Path.join2(
197
+ outputDir->Option.getOr(cssFilePath->NodeJs.Path.dirname),
198
+ outputFileName,
199
+ )
196
200
 
197
- if classNames->Array.length == 0 {
198
- Console.log(`āš ļø No classes found in ${cssFilePath}`)
199
- None
201
+ if !NodeJs.Fs.existsSync(outputPath) {
202
+ true
200
203
  } else {
201
- let (baseName, importType) = cssFilePath->getBaseNameAndImportType
202
- let outputFileName = getOutputFileName(baseName, importType)
203
- let bindings = generateReScriptBindings(baseName, importType, classNames)
204
- let outputPath = NodeJs.Path.join2(
205
- outputDir->Option.getOr(cssFilePath->NodeJs.Path.dirname),
206
- outputFileName,
207
- )
204
+ let sourceStat = NodeJs.Fs.lstatSync(#String(cssFilePath))
205
+ let outputStat = NodeJs.Fs.lstatSync(#String(outputPath))
206
+ sourceStat.mtimeMs > outputStat.mtimeMs
207
+ }
208
+ }
208
209
 
209
- NodeJs.Fs.writeFileSync(outputPath, NodeJs.Buffer.fromString(bindings))
210
- Console.log(`āœ… Generated ${outputPath} (${classNames->Array.length->Int.toString} classes)`)
210
+ // Process a single CSS module file
211
+ let processFile = async (cssFilePath, outputDir, ~force=false, ~silent=false, ~quiet=false) => {
212
+ // Skip if source file is not newer than output (unless force is set)
213
+ if !force && !shouldCompile(cssFilePath, outputDir) {
214
+ if !silent && !quiet {
215
+ Console.log(`ā­ļø Skipped: ${cssFilePath} (unchanged)`)
216
+ }
217
+ None
218
+ } else {
219
+ let content = NodeJs.Fs.readFileSync(cssFilePath)->NodeJs.Buffer.toString
220
+ if !silent && !quiet {
221
+ Console.log(`Processing file: ${cssFilePath}`)
222
+ }
223
+ let classNames = await extractClassNames(content, ~from=cssFilePath)
211
224
 
212
- (outputPath, classNames)->Some
225
+ if classNames->Array.length == 0 {
226
+ if !silent && !quiet {
227
+ Console.log(`āš ļø No classes found in ${cssFilePath}`)
228
+ }
229
+ None
230
+ } else {
231
+ let (baseName, importType) = cssFilePath->getBaseNameAndImportType
232
+ let outputFileName = getOutputFileName(baseName, importType)
233
+ let bindings = generateReScriptBindings(baseName, importType, classNames)
234
+ let outputPath = NodeJs.Path.join2(
235
+ outputDir->Option.getOr(cssFilePath->NodeJs.Path.dirname),
236
+ outputFileName,
237
+ )
238
+
239
+ NodeJs.Fs.writeFileSync(outputPath, NodeJs.Buffer.fromString(bindings))
240
+ if !quiet {
241
+ Console.log(`āœ… Generated ${outputPath} (${classNames->Array.length->Int.toString} classes)`)
242
+ }
243
+
244
+ (outputPath, classNames)->Some
245
+ }
213
246
  }
214
247
  }
215
248
 
@@ -231,17 +264,19 @@ let rec findCssFiles = dir => {
231
264
  }
232
265
 
233
266
  // Watch directories for CSS module and global file changes
234
- let watchDirectories = async (dirs, outputDir, ~skipInitial) => {
235
- Console.log(
236
- `šŸ‘€ Watching ${dirs
237
- ->Array.length
238
- ->Int.toString} directories for CSS module/global changes...`,
239
- )
240
- dirs->Array.forEach(dir => Console.log(` ${dir}`))
241
- if skipInitial {
242
- Console.log(`Skipping initial compilation.`)
267
+ let watchDirectories = async (dirs, outputDir, ~skipInitial, ~force, ~silent, ~quiet) => {
268
+ if !silent && !quiet {
269
+ Console.log(
270
+ `šŸ‘€ Watching ${dirs
271
+ ->Array.length
272
+ ->Int.toString} directories for CSS module/global changes...`,
273
+ )
274
+ dirs->Array.forEach(dir => Console.log(` ${dir}`))
275
+ if skipInitial {
276
+ Console.log(`Skipping initial compilation.`)
277
+ }
278
+ Console.log(`Press Ctrl+C to stop.\n`)
243
279
  }
244
- Console.log(`Press Ctrl+C to stop.\n`)
245
280
 
246
281
  // Set up chokidar watcher for CSS module and global files
247
282
  let isIgnored = path => {
@@ -258,23 +293,49 @@ let watchDirectories = async (dirs, outputDir, ~skipInitial) => {
258
293
  Chokidar.watch(dirs, {"ignored": isIgnored, "persistent": true})
259
294
  ->Chokidar.onReady(() => {
260
295
  ready := true
261
- Console.log(`Ready for changes.`)
296
+ if !silent && !quiet {
297
+ Console.log(`Ready for changes.`)
298
+ }
262
299
  })
263
300
  ->Chokidar.on("change", path => {
264
- Console.log(`\nChanged: ${path}`)
265
- processFile(path, outputDir)->ignore
301
+ // File was explicitly changed, always compile
302
+ if !silent && !quiet {
303
+ Console.log(`Changed: ${path}`)
304
+ }
305
+ processFile(path, outputDir, ~force=true, ~silent, ~quiet)->ignore
266
306
  })
267
307
  ->Chokidar.on("add", path => {
268
308
  // Skip initial files if skipInitial is set
269
309
  if skipInitial && !ready.contents {
270
310
  ()
311
+ } else if ready.contents {
312
+ // After ready, new files should always be compiled
313
+ if !silent && !quiet {
314
+ Console.log(`Added: ${path}`)
315
+ }
316
+ processFile(path, outputDir, ~force=true, ~silent, ~quiet)->ignore
271
317
  } else {
272
- Console.log(`\nAdded: ${path}`)
273
- processFile(path, outputDir)->ignore
318
+ // Initial compilation: use skip-unchanged logic (unless force is set)
319
+ processFile(path, outputDir, ~force, ~silent, ~quiet)->ignore
274
320
  }
275
321
  })
276
322
  ->Chokidar.on("unlink", path => {
277
- Console.log(`\nšŸ—‘ļø Deleted: ${path}`)
323
+ if !silent && !quiet {
324
+ Console.log(`šŸ—‘ļø Deleted: ${path}`)
325
+ }
326
+ // Delete the corresponding compiled .res file if it exists
327
+ let (baseName, importType) = path->getBaseNameAndImportType
328
+ let outputFileName = getOutputFileName(baseName, importType)
329
+ let outputPath = NodeJs.Path.join2(
330
+ outputDir->Option.getOr(path->NodeJs.Path.dirname),
331
+ outputFileName,
332
+ )
333
+ if NodeJs.Fs.existsSync(outputPath) {
334
+ NodeJs.Fs.unlinkSync(outputPath)
335
+ if !silent && !quiet {
336
+ Console.log(`šŸ—‘ļø Deleted compiled: ${outputPath}`)
337
+ }
338
+ }
278
339
  })
279
340
  ->ignore
280
341
  }
@@ -291,15 +352,22 @@ let helpText = `
291
352
  Options
292
353
  --watch, -w Watch for changes and regenerate bindings (directories only)
293
354
  --skip-initial, -s Skip initial compilation in watch mode
355
+ --force, -f Force compilation even if files are unchanged
356
+ --silent Only show "Generated" messages, suppress other output
357
+ --quiet, -q Suppress all output
294
358
  --output-dir, -o Directory to write generated .res files
295
359
  (multiple files or single directory only)
296
360
 
361
+ By default, files are skipped if the source CSS file has not been modified
362
+ since the last compilation. Use --force to always recompile.
363
+
297
364
  Examples
298
365
  $ css-to-rescript src/Card.module.scss
299
366
  $ css-to-rescript src/Theme.global.css
300
367
  $ css-to-rescript src/Button.module.css src/Card.module.scss -o src/bindings
301
368
  $ css-to-rescript src/components
302
369
  $ css-to-rescript src/components src/pages --watch
370
+ $ css-to-rescript src/components --force
303
371
  `
304
372
 
305
373
  let main = async () => {
@@ -311,6 +379,9 @@ let main = async () => {
311
379
  "watch": {Meow.type_: "boolean", shortFlag: "w", default: false},
312
380
  "outputDir": {Meow.type_: "string", shortFlag: "o"},
313
381
  "skipInitial": {Meow.type_: "boolean", shortFlag: "s", default: false},
382
+ "force": {Meow.type_: "boolean", shortFlag: "f", default: false},
383
+ "silent": {Meow.type_: "boolean", default: false},
384
+ "quiet": {Meow.type_: "boolean", shortFlag: "q", default: false},
314
385
  },
315
386
  allowUnknownFlags: false,
316
387
  },
@@ -326,6 +397,9 @@ let main = async () => {
326
397
  let outputDir = cli.flags["outputDir"]
327
398
  let watchMode = cli.flags["watch"]
328
399
  let skipInitial = cli.flags["skipInitial"]
400
+ let force = cli.flags["force"]
401
+ let silent = cli.flags["silent"]
402
+ let quiet = cli.flags["quiet"]
329
403
 
330
404
  // Classify inputs as files or directories
331
405
  let (files, dirs) = inputPaths->Array.reduce(([], []), ((files, dirs), path) => {
@@ -358,21 +432,23 @@ let main = async () => {
358
432
  if files->Array.length > 0 {
359
433
  await files->Array.reduce(Promise.resolve(), async (acc, file) => {
360
434
  await acc
361
- (await processFile(file, outputDir))->ignore
435
+ (await processFile(file, outputDir, ~force, ~silent, ~quiet))->ignore
362
436
  })
363
437
  }
364
438
 
365
439
  // Process directories
366
440
  if dirs->Array.length > 0 {
367
441
  if watchMode {
368
- await watchDirectories(dirs, outputDir, ~skipInitial)
442
+ await watchDirectories(dirs, outputDir, ~skipInitial, ~force, ~silent, ~quiet)
369
443
  } else {
370
444
  let moduleFiles = dirs->Array.flatMap(findCssFiles)
371
- Console.log(`Found ${moduleFiles->Array.length->Int.toString} CSS module files\n`)
445
+ if !silent && !quiet {
446
+ Console.log(`Found ${moduleFiles->Array.length->Int.toString} CSS module files\n`)
447
+ }
372
448
 
373
449
  await moduleFiles->Array.reduce(Promise.resolve(), async (acc, file) => {
374
450
  await acc
375
- (await processFile(file, outputDir))->ignore
451
+ (await processFile(file, outputDir, ~force, ~silent, ~quiet))->ignore
376
452
  })
377
453
  }
378
454
  }