-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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: symlinks in node_modules #6993
Conversation
@@ -40,6 +43,11 @@ export default class HasteFS { | |||
return this._files.has(file); | |||
} | |||
|
|||
follow(file: Path): Path { | |||
const link = this._links[file]; | |||
return link ? link[0] || (link[0] = fs.realpathSync(file)) : file; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could use realpath-native
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Split the side-effect in two as well.
Needs a changelog entry 🙂 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall it looks like solid work for me. Aside from the comments I left; we'd like the file watcher exposed to trigger both when the symlink is re-assigned, and when the destination file is changed. I assume the latter only happens in case the destination file is also tracked by watchman
. Not optimal TBH, but I think we can live with it.
]); | ||
} else { | ||
// See ../constants.js |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, make it back.
@@ -40,6 +43,11 @@ export default class HasteFS { | |||
return this._files.has(file); | |||
} | |||
|
|||
follow(file: Path): Path { | |||
const link = this._links[file]; | |||
return link ? link[0] || (link[0] = fs.realpathSync(file)) : file; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Split the side-effect in two as well.
@rafeca Can we try internally the change to |
Yeah, we could resolve symlinks immediately and watch the real paths (skipping any that resolve to a |
The requested changes have been addressed. |
We should probably track both: the symlink itself, and the file that points to. Every time the symlink changes, the file it points to is stopped from watching, and we start watching the new one. Changes to any of those should be reported back as changes by the listener. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've had a bit of a discussion internally, and we think the symlink should not be exposed, or, at least, be fully transparent (i.e., you shouldn't need to call .follow
at all).
Hmm, I wonder if there would be a considerable performance cost in doing so? Perhaps, support for transparent symlink resolution can be added when/if we add comprehensive symlink support (that is, symlinks outside For now, it's not a problem, since we only call |
I think symlink support should probably be an option.
Is there a particular reason to support them only inside |
Okay, but there should be three options:
I'd be fine with comprehensive support, if it's not too expensive. But I don't use symlinks for anything except |
Kind related (maybe?) #5356 |
Preserving symlinks == the symlink is treated as the real path. This PR tracks where symlinks exist and resolves them on-demand (without ever discarding symlink paths). To resolve a symlink, its real path must be watched separately and explicitly. Otherwise, the Anyway, it's important to not discard the symlink path once resolved, because this information is crucial when resolving dependencies in Metro. The ideal behavior is for Jest to resolve symlinks eagerly, watch both the symlink and its real path, and treat symlinks as if they're hard links. To support this, you'll need a map of The For "broken" symlinks, should |
Don't disregard perf hit with symlinks without testing it (especially for windows). We've had reports of performance degradations that according to the report came after adding some |
@SimenB In that case, I can, because Unless you think the Watchman query will hurt perf. |
@rubennorte @mjesun is this ready to land? |
@aleclarson mind rebasing and fixing the failing tests? |
- add `follow` method to `HasteFS` class - add `links` property to `InternalHasteMap` type - add `LinkData` map type - add `LinkMetaData` array type The user will need Watchman installed for this commit to take effect. Support for the Node crawler should be added in a future commit. Only symlinks matching `node_modules/*` or `node_modules/@*/*` are stored in the `links` property. For each symlink found, a `[target, mtime]` tuple is created. The `target` value is undefined until `hasteFS.follow` uses `fs.realpathSync` to resolve the symlink. In this way, symlinks are lazily resolved and then cached.
Seems like the hastemap tests fail on CI? |
I could find bandwidth to write/fix the tests if a FB employee finds time to stub out tests for the expected behavior (and merge it into this PR). |
Could you add a test case to |
So, to recap on this issue. I'm uneasy going with the "follow" function strategy because it's not a very systematic approach, it puts the responsibility on the consumers to do the right thing; where I believe we could do something that is more transparent, ie. that wouldn't expose the concept of symlink to the library's consumers. The reason we don't need to expose the concept of symlink is because the library is essentially read-only. We can expose files in such a way that symlinks are exposed the same way as hard links would be, consumers don't need to know (or do they?). By implementing things in that way, it seems like things would just work out of box not only for Metro, but also for the Jest test runner. On the other hand that means Metro, for example, would have to transform the same file twice (ex. the real one, versus the symlink). But, since they live in different folders, they might have different transform options, so it might make sense anyway. I'm not entirely whether that would be prohibitive in terms of How I'd propose to have symlinks implemented:
The fact this last two implementation details are quite non-trivial leads me to believe that integrating symlink support within About testing, I don't have necessarily a guidance on which stubs/cases should be added, I trust you to add the tests that you judge necessary, and to adjust existing test cases (but they shouldn't normally be affected since we're adding support for a new feature). We'll probably want cover the initial crawling of the codebase, files getting changed, symlinks getting changed, and finally files or symlink being added/removed after the watcher already started. Implementing things that way would remove the need for facebook/metro#257, so it's not necessarily more work or more complex overall, but it makes it more self-contained. Does that sound like a plan? |
About performance concerns: I don't think there would be that much of a performance hit, as we can read symlinks the first time we find them. Then, once the process has started, we already know where symlinks are pointing at, and we only need to read them again when watchman reports a change. Note that with the logic I describe above, at no point in time would we call |
I won't be implementing transparent symlinks, because I don't need them. 😄 I still believe you could merge this PR for temporary relief, and then tackle transparent symlinks at your own pace. But if you prefer jumping right to transparent symlinks, I can't stop you. 😉 |
I won't implement transparent symlinks myself unfortunately as I don't need them at the moment either. I suppose we can merge a partial solution, though we have to keep in mind it'll likely be permanent. But I'll let the Jest maintainers make the call on that one as I don't work much on Jest these days. |
This is great! I agree with @jeanlauliac - I think we should wait until we have transparent symlinks |
Is there a way we could involve the Metro community in this decision? I strongly believe that symlinks in edit: Unless you guys decide to implement transparent symlinks immediately, this PR should be strongly considered. It's ready to solve the main use case (apart from any rebasing and testing). |
@aleclarson @jeanlauliac and @mjesun work on Metro full time |
@mjesun what's the risk of merging the partial solution? |
Okay, but they haven't said whether they'll be implementing transparent symlinks without community involvement. I'm working on an update to this PR that will make it easier to implement transparent symlinks in the future. 😊 |
Closed in favor of #7549 |
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
follow
method toHasteFS
classlinks
property toInternalHasteMap
typeLinkData
map typeLinkMetaData
array typeSummary
This PR is required by facebook/metro#257, which also adds support for symlinks in
node_modules
directories.Only symlinks matching
node_modules/*
ornode_modules/@*/*
are stored in thelinks
property. For each symlink found, a[target, mtime]
tuple is created. Thetarget
value is undefined untilhasteFS.follow
usesfs.realpathSync
to resolve the symlink. In this way, symlinks are lazily resolved and then cached.The user will need Watchman installed for this commit to take effect. Support for the Node crawler should be added in a future commit.
Test plan
None yet. Not familiar with Jest's test suite, and I assume you'll want some changes, or deny this altogether in favor of a more informed direction.
I've tested this manually with arbitrary symlinks and PNPM installations.