Skip to content
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

fix: Allow ignore patterns to apply to absolute path instead of relative path #656

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

kingston
Copy link
Contributor

@kingston kingston commented Sep 24, 2024

  • Upgrade chokidar to v4 for a smaller footprint
  • Add picomatch for glob matching
  • Add test for parent directory ignores

Addresses #221

…ive path

+ Upgrade chokidar to v4 for a smaller footprint
+ Add picomatch for glob matching
+ Add test for parent directory ignores
@kingston
Copy link
Contributor Author

@privatenumber just kicking this off as an initial draft PR so we can comment on raw code vs. ideas. Right now all tests pass except one (--include with glob patterns). This is because chokidar v4 removes all glob support including include patterns. This is a bit trickier to resolve since we'd need to find all the files that match that glob which gets a lot messier/more complex. We can either stay with chokidar v3 or do something a bit more complex. Looking at chokidar v3, it seems like when they encounter a glob pattern they just take the glob-parent and watch the highest parent of the glob e.g. for /blah/foo/**/test - they watch /blah/foo and filter by **/test. So we can try to mimic this behavior manually.

We could potentially do two separate watchers:

  1. Executed file + dependencies watcher (basically what we have today with this PR)
  2. If --include is present, includes-only watcher. Watch all glob-parents and apply --include globs to filter out only the files we care about. (would coincidentally also address fix: Drop default excludes that are exist in includes #643)

I don't think it adds too much of an overhead and simplifies the logic.

On a side note, I realized the docs were out of date with --exclude / --include (https://github.com/privatenumber/tsx/blob/master/docs/watch-mode.md) but can be a separate issue to resolve.

@privatenumber
Copy link
Owner

privatenumber commented Sep 25, 2024

That sounds good to me! Thanks @kingston

Will try to update the docs soon.

@kingston
Copy link
Contributor Author

@privatenumber I've updated the PR to now pass all tests including the include tests. I introduced 2 additional libraries to get the glob parent and check if it is a glob or not (otherwise glob parent would remove the ending).

Overall, I think it should be good to go. However, there's one scenario where I'm unclear what the behavior should be.

If you pass a directory to --exclude without any glob patterns, should it automatically match all its children? e.g. --exclude ./foo will match ./foo/? If so, we'd probably need to re-work the logic to create a separate matcher for the exact path and path with / appended to it for non-glob patterns.

Also left a small update the docs to make it more accurate.

@kingston
Copy link
Contributor Author

@privatenumber I don't have access to a windows instance but just uploaded a fix that should address the failing test.

@kingston
Copy link
Contributor Author

Hmm.. that didn't seem to work. I'll try to see if I can get a windows machine to test it on since I have a Mac.

@privatenumber
Copy link
Owner

I actually don't have a Windows machine either. It's not the most ergonomic environment but I just make a separate branch, update the GitHub Actions CI to only run on Windows, and debug there 😅

I'll have more time this weekend so I can take a closer look/help out.

@kingston
Copy link
Contributor Author

@privatenumber OK, I spun up a Windows VM on my Mac and was able to debug more ergonomically :). It looks like globs and Windows paths are complicated since globs are technically not allowed to have \ in them so I used a library normalize-paths (used by chokidar v3 as well) to normalize the paths to / since chokidar v4 also does that when passing to ignored. This makes all tests pass on my Windows 11 VM :).

Fun side-note: I was unable to commit to the repo because of the lint pre-commit hook since the lint command doesn't work on Windows so I had to skip the hooks when committing.

C:\...\tsx\docs\getting-started.md
  62:14  error  Parsing error: Unexpected token :

C:\...\tsx\docs\typescript.md
  108:14  error  Parsing error: Unexpected token :
  123:14  error  Parsing error: Unexpected token :

My guess is that the glob pattern for ignoring files also doesn't work on Windows :P

lintroll --node --cache --ignore-pattern 'docs/*.md' .

@kingston
Copy link
Contributor Author

kingston commented Oct 9, 2024

@privatenumber just wanted to follow up - are there any other action items for this PR?

@privatenumber
Copy link
Owner

Sorry @kingston been swamped lately. I'll try to take a look Sunday or next week.

@kingston
Copy link
Contributor Author

Sure no problem - I get it.

|| isOptionsExclude(file)
)
),
}).on('all', reRun);
Copy link
Owner

Choose a reason for hiding this comment

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

Is it possible to add to the same watcher above instead of making a new one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I created a separate one since the logic of the ignore is different for explicit includes compared to default includes (we want to include things that might be by default excluded e.g. node_modules) and I think under the hood the chokidar would just create separate watchers for the different glob parents resulting in no performance impact.

return normalizePath(pattern);
}
return normalizePath(path.join(process.cwd(), pattern));
};
Copy link
Owner

Choose a reason for hiding this comment

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

This only seems used to normalize the path slashes in user provided paths—do they need need to be normalized?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without normalization, Windows style separators break the glob logic. Normalizing the paths make all our paths we use consistent and easier to handle (e.g. we can match our globs based on Unix-style separators instead of having to figure out which it is)

},
).on('all', reRun);

if (resolvedIncludes.length > 0) {
const globParents = resolvedIncludes.map(pattern => (
isGlob(pattern)
Copy link
Owner

Choose a reason for hiding this comment

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

This seems like a pretty hefty function to run only to check if it's a glob. What if we just run globParent on all inputs regardless of whether they're a glob?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried that but a simple include like test/foo/hello becomes test/foo when run through glob-parent. We could proactively append a separator at the end but that may result in weirder behavior potentially. Coincidentally glob-parent uses isGlob under the hood.

@@ -4,7 +4,10 @@ import { constants as osConstants } from 'node:os';
import path from 'node:path';
import { command } from 'cleye';
import { watch } from 'chokidar';
import picomatch from 'picomatch';
Copy link
Owner

@privatenumber privatenumber Oct 16, 2024

Choose a reason for hiding this comment

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

Picomatch is actually the largest dependency now (bundle size):
https://pkg-size.dev/chokidar%20glob-parent%20is-glob%20normalize-path%20picomatch

Is there a lighter alternative?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm. this is the smallest library I have for globbing that I can find. Micromatch depends on picomatch and minimatch is about 5 times as large as picomatch.

@privatenumber
Copy link
Owner

I pulled out your docs changes into master so we can get it out faster. Thanks!

Copy link
Contributor Author

@kingston kingston left a comment

Choose a reason for hiding this comment

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

Sorry for the delay - had a bit of a busy past month.

@@ -4,7 +4,10 @@ import { constants as osConstants } from 'node:os';
import path from 'node:path';
import { command } from 'cleye';
import { watch } from 'chokidar';
import picomatch from 'picomatch';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm. this is the smallest library I have for globbing that I can find. Micromatch depends on picomatch and minimatch is about 5 times as large as picomatch.

},
).on('all', reRun);

if (resolvedIncludes.length > 0) {
const globParents = resolvedIncludes.map(pattern => (
isGlob(pattern)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried that but a simple include like test/foo/hello becomes test/foo when run through glob-parent. We could proactively append a separator at the end but that may result in weirder behavior potentially. Coincidentally glob-parent uses isGlob under the hood.

|| isOptionsExclude(file)
)
),
}).on('all', reRun);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I created a separate one since the logic of the ignore is different for explicit includes compared to default includes (we want to include things that might be by default excluded e.g. node_modules) and I think under the hood the chokidar would just create separate watchers for the different glob parents resulting in no performance impact.

return normalizePath(pattern);
}
return normalizePath(path.join(process.cwd(), pattern));
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without normalization, Windows style separators break the glob logic. Normalizing the paths make all our paths we use consistent and easier to handle (e.g. we can match our globs based on Unix-style separators instead of having to figure out which it is)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants