diff --git a/src/RescriptEmbedLang.res b/src/RescriptEmbedLang.res index 18bf57b..64b70b9 100644 --- a/src/RescriptEmbedLang.res +++ b/src/RescriptEmbedLang.res @@ -28,7 +28,7 @@ module Chokidar = { type watchOptions = {ignored?: array, ignoreInitial?: bool} @send - external watch: (t, string, ~options: watchOptions=?) => Watcher.t = "watch" + external watch: (t, array, ~options: watchOptions=?) => Watcher.t = "watch" } module Glob = { @@ -46,6 +46,14 @@ module Glob = { external glob: glob = "default" } +module MicroMatch = { + type mm = { + makeRe: string => RegExp.t + } + @module("micromatch") + external mm: mm = "default" +} + module Hash = { type createHash @module("crypto") external createHash: createHash = "createHash" @@ -166,6 +174,24 @@ type handleOtherCommandConfig<'config> = { type setupConfig = {args: CliArgs.t} +type watcherOnChangeConfig = { + file: string, + runGeneration: (~files: array=?) => promise, +} + +type watcher = { + filePattern: string, + onChange: watcherOnChangeConfig => promise +} + +@unboxed +type setupResult<'config> = + | SetupResult({ + config: 'config, + additionalFileWatchers?: array, + additionalIgnorePatterns?: array + }) + type onWatchConfig<'config> = { config: 'config, runGeneration: (~files: array=?) => promise, @@ -174,14 +200,14 @@ type onWatchConfig<'config> = { type t<'config> = { fileName: FileName.t, - setup: setupConfig => promise<'config>, + setup: setupConfig => promise>, generate: generateConfig<'config> => promise>, cliHelpText: string, handleOtherCommand?: handleOtherCommandConfig<'config> => promise, onWatch?: onWatchConfig<'config> => promise, } -let defaultSetup = async _ => () +let defaultSetup = async (_): setupResult<_> => SetupResult({config: ()}) let make = ( ~extensionPattern, @@ -348,6 +374,12 @@ let cleanUpExtraFiles = (t, ~outputDir, ~sourceFileModuleName="*", ~keepThese=[] }) } +type customWatchedFile = { + glob: string, + regexp: RegExp.t, + onChange: watcherOnChangeConfig => promise +} + let runCli = async (t, ~args: option>=?) => { let debugging = ref(false) let debug = msg => @@ -375,8 +407,17 @@ let runCli = async (t, ~args: option>=?) => { open Process process->exitWithCode(0) | Some("generate") => - let config = await t.setup({args: args}) let watch = args->CliArgs.hasArg("--watch") + let SetupResult({config, ?additionalFileWatchers, ?additionalIgnorePatterns}) = await t.setup({ + args: args, + }) + let customWatchedFiles = + Option.getOr(additionalFileWatchers, []) + ->Array.map(w => { + glob: w.filePattern, + regexp: MicroMatch.mm.makeRe(w.filePattern), + onChange: w.onChange + }) let pathToGeneratedDir = switch args->CliArgs.getArgValue(["--output"]) { | None => panic(`--output must be set. It controls into what directory all generated files are emitted.`) @@ -488,20 +529,29 @@ let runCli = async (t, ~args: option>=?) => { if watch { await runGeneration() - Console.log(`Watching for changes in ${src}...`) + let watchedFiles = + Array.map(customWatchedFiles, f => f.glob) + ->Array.concat([`${src}/**/*.res`]) + Console.log(`Watching the following patterns for file changes...`) + Array.forEach(watchedFiles, f => Console.log(`- ${f}`)) let _theWatcher = Chokidar.watcher ->Chokidar.watch( - `${src}/**/*.res`, + watchedFiles, ~options={ - ignored: ["**/node_modules", pathToGeneratedDir], + ignored: + Option.getOr(additionalIgnorePatterns, []) + ->Array.concat(["**/node_modules", pathToGeneratedDir]), ignoreInitial: true, }, ) ->Chokidar.Watcher.onChange(async file => { debug(`[changed]: ${file}`) - await runGeneration(~files=[file]) + switch Array.find(customWatchedFiles, w => RegExp.test(w.regexp, file)) { + | Some({onChange}) => await onChange({ runGeneration, file }) + | None => await runGeneration(~files=[file]) + } }) ->Chokidar.Watcher.onAdd(async file => { debug(`[added]: ${file}`) @@ -534,8 +584,10 @@ let runCli = async (t, ~args: option>=?) => { | Some(otherCommand) => switch t.handleOtherCommand { | None => Console.log(t.cliHelpText) - | Some(handleOtherCommand) => - await handleOtherCommand({args, command: otherCommand, config: await t.setup({args: args})}) + | Some(handleOtherCommand) => { + let SetupResult({config}) = await t.setup({args: args}) + await handleOtherCommand({args, command: otherCommand, config}) + } } | None => Console.log(t.cliHelpText) } diff --git a/src/RescriptEmbedLang.resi b/src/RescriptEmbedLang.resi index f0e1e8e..e5eb4d5 100644 --- a/src/RescriptEmbedLang.resi +++ b/src/RescriptEmbedLang.resi @@ -139,8 +139,38 @@ type setupConfig = { args: CliArgs.t, } +/** Args given to a custom file watcher when a change occurs */ +type watcherOnChangeConfig = { + /** The specific filepath that triggered the watcher */ + file: string, + /** Trigger code generation. + + `@param files: array=?` - Optional list of files to do generation for. Passing nothing triggers a full generation. + */ + runGeneration: (~files: array=?) => promise, +} + +/** A custom file watcher to register when running in watch mode */ +type watcher = { + /** Pattern to register with the watcher */ + filePattern: string, + /** Callback to trigger when a matching file changes */ + onChange: watcherOnChangeConfig => promise +} + +@unboxed +type setupResult<'config> = + | SetupResult({ + /** Config object to provide to future calls */ + config: 'config, + /** Additional files to keep an eye on in watch mode */ + additionalFileWatchers?: array, + /** Additional file patterns to ignore in watch mode */ + additionalIgnorePatterns?: array + }) + /** A default function for `setup`, that just returns nothing. */ -let defaultSetup: setupConfig => promise +let defaultSetup: setupConfig => promise> /** Configuration for the `onWatch` handler. */ type onWatchConfig<'config> = { @@ -173,7 +203,7 @@ type onWatchConfig<'config> = { */ let make: ( ~extensionPattern: extensionPattern, - ~setup: setupConfig => promise<'config>, + ~setup: setupConfig => promise>, ~generate: generateConfig<'config> => promise>, ~cliHelpText: string, ~handleOtherCommand: handleOtherCommandConfig<'config> => promise=?,