Skip to content

Add custom watched and ignored files #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 62 additions & 10 deletions src/RescriptEmbedLang.res
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module Chokidar = {
type watchOptions = {ignored?: array<string>, ignoreInitial?: bool}

@send
external watch: (t, string, ~options: watchOptions=?) => Watcher.t = "watch"
external watch: (t, array<string>, ~options: watchOptions=?) => Watcher.t = "watch"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we might have multiple globs, swapped out the overload we're hitting.

}

module Glob = {
Expand All @@ -46,6 +46,14 @@ module Glob = {
external glob: glob = "default"
}

module MicroMatch = {
type mm = {
makeRe: string => RegExp.t
}
@module("micromatch")
external mm: mm = "default"
}

Comment on lines +49 to +56
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fast-glob already depends on micromatch, so we can use this to test glob matches for free.

module Hash = {
type createHash
@module("crypto") external createHash: createHash = "createHash"
Expand Down Expand Up @@ -166,6 +174,24 @@ type handleOtherCommandConfig<'config> = {

type setupConfig = {args: CliArgs.t}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially, I had tinkered with adding a parameter here, which would have avoided the breaking change to setup's return type.

type setupConfig  = {
  args: CliArgs.t,
  addWatcher: watcher => unit
}

However, I realized that with this API, an embed developer could then start doing funny things with addWatcher (e.g. add new watchers after we've already started watching), which would be a whole new can of worms to worry about. By using the return type, we force all watchers to be known once setup is done.


type watcherOnChangeConfig = {
file: string,
runGeneration: (~files: array<string>=?) => promise<unit>,
}

type watcher = {
filePattern: string,
onChange: watcherOnChangeConfig => promise<unit>
}

@unboxed
type setupResult<'config> =
| SetupResult({
config: 'config,
additionalFileWatchers?: array<watcher>,
additionalIgnorePatterns?: array<string>
})
Comment on lines +187 to +193
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially used a plain-ol' record here, but that caused some inference issues due to the config parameter, so I swapped it out for the unboxed single-case variant to sidestep that issue.


type onWatchConfig<'config> = {
config: 'config,
runGeneration: (~files: array<string>=?) => promise<unit>,
Expand All @@ -174,14 +200,14 @@ type onWatchConfig<'config> = {

type t<'config> = {
fileName: FileName.t,
setup: setupConfig => promise<'config>,
setup: setupConfig => promise<setupResult<'config>>,
generate: generateConfig<'config> => promise<result<generated, string>>,
cliHelpText: string,
handleOtherCommand?: handleOtherCommandConfig<'config> => promise<unit>,
onWatch?: onWatchConfig<'config> => promise<unit>,
}

let defaultSetup = async _ => ()
let defaultSetup = async (_): setupResult<_> => SetupResult({config: ()})
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As promised, the return type is a breaking change.


let make = (
~extensionPattern,
Expand Down Expand Up @@ -348,6 +374,12 @@ let cleanUpExtraFiles = (t, ~outputDir, ~sourceFileModuleName="*", ~keepThese=[]
})
}

type customWatchedFile = {
glob: string,
regexp: RegExp.t,
onChange: watcherOnChangeConfig => promise<unit>
}

let runCli = async (t, ~args: option<array<string>>=?) => {
let debugging = ref(false)
let debug = msg =>
Expand Down Expand Up @@ -375,8 +407,17 @@ let runCli = async (t, ~args: option<array<string>>=?) => {
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.`)
Expand Down Expand Up @@ -488,20 +529,29 @@ let runCli = async (t, ~args: option<array<string>>=?) => {

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}`))
Comment on lines +535 to +536
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tweaked the display here so we can show all the globs we're keeping an eye on.


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}`)
Expand Down Expand Up @@ -534,8 +584,10 @@ let runCli = async (t, ~args: option<array<string>>=?) => {
| 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)
}
Expand Down
34 changes: 32 additions & 2 deletions src/RescriptEmbedLang.resi
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>=?` - Optional list of files to do generation for. Passing nothing triggers a full generation.
*/
runGeneration: (~files: array<string>=?) => promise<unit>,
}

/** 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<unit>
}

@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<watcher>,
/** Additional file patterns to ignore in watch mode */
additionalIgnorePatterns?: array<string>
})

/** A default function for `setup`, that just returns nothing. */
let defaultSetup: setupConfig => promise<unit>
let defaultSetup: setupConfig => promise<setupResult<unit>>

/** Configuration for the `onWatch` handler. */
type onWatchConfig<'config> = {
Expand Down Expand Up @@ -173,7 +203,7 @@ type onWatchConfig<'config> = {
*/
let make: (
~extensionPattern: extensionPattern,
~setup: setupConfig => promise<'config>,
~setup: setupConfig => promise<setupResult<'config>>,
~generate: generateConfig<'config> => promise<result<generated, string>>,
~cliHelpText: string,
~handleOtherCommand: handleOtherCommandConfig<'config> => promise<unit>=?,
Expand Down