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

feat: Added option to --watch-file to address eager reloading #1784

Merged
merged 20 commits into from
Jan 5, 2020

Conversation

jezhou
Copy link
Contributor

@jezhou jezhou commented Dec 14, 2019

Fixes #1626
Fixes #1102
Fixes #1137

This PR adds a --watch-file parameter, which should resolve the problem of web-ext reloading changes too eagerly when using a custom builder/bundler system like Webpack. It helps with this issue by only reloading the extension when the developer touches a specific file after building their extension (this require some configuration on the developer's part).

I got confused in the implementation details since the issue describe the feature as watching a particular file, but some of the finer details seem to focus on watching a new directory.

I wasn't sure which one to implement – file or directory – so I did file because a) it provides a finer grain of control that I think solves the problem and b) the assertions in test.watcher.js were easier to write using a file.

I think this isn't too bad for a first pass, but test.watcher.js ended up being a little tricker than I thought, so definitely need some second eyes on that. I'll annotate why I thought it was tricky below.

Comment on lines 41 to 49
return whenFilesChanged
.then(() => {
return Promise.race([
whenFilesChanged
.then(() => {
watcher.close();
// This delay seems to avoid stat errors from the watcher
// which can happen when the temp dir is deleted (presumably
// before watcher.close() has removed all listeners).
return new Promise((resolve) => {
setTimeout(resolve, 2, onChange);
});
}),
// Time out if no files are changed
new Promise((resolve) => setTimeout(() => {
watcher.close();
sinon.assert.calledOnce(onChange);
// This delay seems to avoid stat errors from the watcher
// which can happen when the temp dir is deleted (presumably
// before watcher.close() has removed all listeners).
return new Promise((resolve) => setTimeout(resolve, 2));
});
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The way this was written before, whenFilesChanged will not resolve unless WatchPack actually detects the changes. This leads to a problem with the new feature because with the new option, if the detected file isn't modified, this will never resolve, which fails the test.

I added a "timeout" to the promise using Promise.race() – which after experimenting with seems to correctly fail the assertions if I want them to. The only bad thing about this I guess is that one test will always take 500ms (the timeout in that second promise). I don't think that's too bad, but if there's a better way to do all of this I'd love to learn 😄

@jezhou jezhou changed the title feat: Watch file feat: Add option to --watch-file to address eager reloading Dec 14, 2019
@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling 813a1a4 on jezhou:jz/add-option-watch-file into 7ee3c44 on mozilla:master.

@coveralls
Copy link

coveralls commented Dec 14, 2019

Coverage Status

Coverage remained the same at 100.0% when pulling afae5f9 on jezhou:jz/add-option-watch-file into 622547b on mozilla:master.

@jezhou jezhou changed the title feat: Add option to --watch-file to address eager reloading feat: Added option to --watch-file to address eager reloading Dec 14, 2019
@jezhou
Copy link
Contributor Author

jezhou commented Dec 14, 2019

I think the build is failing because of npm audit. Not sure if I should fix the vulnerability in this PR or leave it to another PR?

@jezhou jezhou marked this pull request as ready for review December 14, 2019 09:28
@Rob--W
Copy link
Member

Rob--W commented Dec 14, 2019

I think the build is failing because of npm audit. Not sure if I should fix the vulnerability in this PR or leave it to another PR?

The error is unrelated to your change, so please create a new PR (or open a new issue).

watcher.watch([], [sourceDir], Date.now());

if (watchFile) {
Copy link
Member

Choose a reason for hiding this comment

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

To avoid reloading too often, when watchFile is specified, sourceDir should not be watched any more. Could you update the implementation and tests with this expectation?

Copy link
Member

Choose a reason for hiding this comment

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

Even if watcher.watch ends up ignoring the previous call, the readability of the code would be better if you move the previous wacher.watch call into an } else { block.

Or something like this:

let watchedDirs = [];
let watchedFiles = [];
if (watchFile) {
  watchedFiles.push(watchFile);
} else {
  watchedDirs.push(sourceDir);
}
watcher.watch(watchedFiles, watchedDirs, Date.now();

@jezhou jezhou force-pushed the jz/add-option-watch-file branch from 42eb6a6 to db080fe Compare December 15, 2019 00:56
@jezhou
Copy link
Contributor Author

jezhou commented Dec 15, 2019

@Rob--W I added a few more assertions to make sure the Watchers in WatchPack were only watching either the watchFile or the sourceDir.

Implementation wise, I'm not sure if I need to add anything else. It looks like the WatchPack README says that calling watch() twice will override existing files and directories. It looks like that's the case when I look at the source too.

@jezhou jezhou requested a review from Rob--W December 16, 2019 17:23
Copy link
Member

@Rob--W Rob--W left a comment

Choose a reason for hiding this comment

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

(forgot to submit those comments when I wrote them; apologies for the delay)

watcher.watch([], [sourceDir], Date.now());

if (watchFile) {
Copy link
Member

Choose a reason for hiding this comment

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

Even if watcher.watch ends up ignoring the previous call, the readability of the code would be better if you move the previous wacher.watch call into an } else { block.

Or something like this:

let watchedDirs = [];
let watchedFiles = [];
if (watchFile) {
  watchedFiles.push(watchFile);
} else {
  watchedDirs.push(sourceDir);
}
watcher.watch(watchedFiles, watchedDirs, Date.now();

() => withTempAddonDir(
{addonPath: minimalAddonPath},
(srcDir) => {
const argv = [
'run', '--verbose', '--no-reload',
'--source-dir', srcDir,
'--watch-file', srcDir,
Copy link
Member

Choose a reason for hiding this comment

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

--watch-file is expected to be a file. I would expect an error if a directory is passed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done – I threw a UsageError here if a directory is passed in. Let me know if there's a better error to throw.

@jezhou jezhou requested a review from Rob--W January 3, 2020 06:25
return execWebExt(argv, {}).waitForExit.then(({exitCode, stdout}) => {
assert.notEqual(exitCode, 0);
// eslint-disable-next-line no-console
console.log(stdout);
Copy link
Member

Choose a reason for hiding this comment

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

Remove these two lines of code. You probably added them for debugging only.

src/cmd/run.js Outdated
fs.existsSync(watchFile) &&
fs.lstatSync(watchFile).isDirectory()
) {
throw new UsageError(`The directory "${watchFile}" cannot be ` +
Copy link
Member

Choose a reason for hiding this comment

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

This check is not strictly necessary, but it will probably help against common mistakes.

Let's move this whole check to the src/watcher.js, right before watchedFiles.push(watchFile);.
And use isFile() instead of isDirectory. That would also defend against cases where the file path refers to non-files that are not directories, such as symlinks.

@jezhou jezhou requested a review from Rob--W January 4, 2020 00:10
Copy link
Member

@Rob--W Rob--W left a comment

Choose a reason for hiding this comment

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

One small comment, and then it looks good to me.

src/watcher.js Outdated

if (watchFile) {
if (fs.existsSync(watchFile) && !fs.lstatSync(watchFile).isFile()) {
throw new UsageError(`"${watchFile}" cannot be passed` +
Copy link
Member

Choose a reason for hiding this comment

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

"cannot be passed" is vague. Could you rephrase the error message so that it is more actionable? E.g. to:

throw new UsageError(`"Invalid --watch-file value: ${watchFile}" is not a file`)

@jezhou jezhou force-pushed the jz/add-option-watch-file branch from 7d72399 to afae5f9 Compare January 4, 2020 07:17
@jezhou jezhou requested a review from Rob--W January 4, 2020 07:26
@jezhou
Copy link
Contributor Author

jezhou commented Jan 5, 2020

@Rob--W do you think you can land this? I don't have write permissions for this repo 🙂

@Rob--W Rob--W merged commit 1478d6b into mozilla:master Jan 5, 2020
@Rob--W
Copy link
Member

Rob--W commented Jan 5, 2020

Thanks, merged!

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