-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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
Policy conditions #34414
Policy conditions #34414
Conversation
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.
Makes sense to me in general. For my own understanding: Is it fair to sum up the new semantics of these mappings as (roughly) "like exports but without the arrays"?
const redirects = manifest.getRedirector(parentURL); | ||
if (redirects) { | ||
const { resolve, reaction } = redirects; | ||
const destination = resolve(specifier, new SafeSet(conditions)); |
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.
Is the copy necessarily because of potential mutations? If so, would it make sense to freeze the conditions set? Or is that a perf concern?
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.
just to keep the signature of resolve consistent
Correct. I don't think we should at least for now do fallbacks. Policies are meant to be static resolution. We do have multi-integrity for resources though so multi-paths might be fine? Each value in the array would still need to be a full absolute URL though. |
I would also prefer using It is great to see the unification here. I'm not sure I understand the use cases behind the conditions though - can you perhaps clarify in more detail why you want to split on these conditions? |
This is generally just to allow policies to model: require('foo');
import('foo'); Which can resolve to 2 different locations. Leaving it to match how "exports" works because of the cascading and in order iteration means that logic is involved in non-trivial ways and I'm unclear if only supporting a subset of conditions makes sense (and the potential for things like #33171 to appear in the future). |
Import maps do not permit conditional variations, rather conditions have to be handled by having the import map be a function of the environment. The "require" and "import" conditions are unique in that they apply within the same environment unlike all other conditions. If creating an import map for Node.js this problem is avoided by the nature of scoping permitting "import" and "require" to naturally diverage as appropriate based on the importer. This same technique should be able to apply to policy. For this reason I would prefer to use scoping-based rules over explicit conditions in mappings. |
Policies are not import maps; currently in stable Node 12 and 14 you can have code that diverges when you have the code above. Policies are meant to model the resolution of the application and needs to model this divergence within the same source text as can be seen in both, unflagged. We have 1 importer with the same import specifier but 2 different conditions (usage of
Scopes are a different feature that I have a branch to work on but are for usability not for feature parity. This PR is to enable feature parity of which import maps cannot supply. |
I suppose the missing case would be a CommonJS file that both has a I have voiced my concern that this seems a lot of functionality to support an edge case, but I'll leave it at that. |
@guybedford it also should affect the currently broken regarding policies |
I'm 100% behind the unification, my concern is more that this conditions model doesn't integrate well with loaders since the loader is the authoritative point of truth as to what is resolved (and loaders can bypass policy). |
@guybedford the loader is still the authority, policies just configure the actions of the default loader. Policies are not meant to override any loader behavior. I think we are in agreement there. The conditions are just the static actions the default loader should take when given the listed conditions (by an application or another loader), not that loaders see specific conditions. |
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 we restrict policy to only act on conditions that Node.js explicitly varies on? That is right now it would just be "import"
and "require"
. Possibly in future we might include things like "main"
or "worker"
etc. My concern is that it makes no sense for other conditions here while still allowing external loaders to drive these variations.
I suppose one argument against that would be the "development"
and "production"
conditions if we open up that feature in future via a flag or otherwise, but even then I would still prefer to have eg policy.dev
and policy.production
as separate and only have a within-environment policy handle within-environment condition variations.
Let me know your thoughts.
Also, "default"
is hard-coded I believe, so needs to be supported explictly otherwise (but double-check).
Currently the default loader appears to handle anything a user loader passes to it. That means that if you use a policy and a user loader it could still see a different kind of condition (who knows what [ no-native-modules ?]). I think if we do limit it to
I think this is very nuanced, I agree for development/production you likely would have separated files. I don't necessarily think we should focus on mixing those in the same file as that seems to be less likely to co-exist in a single file if we did support such a feature. However, if there is runtime reflection we likely would want to ensure that a policy does support both. This is all future speculation though. Currently conditions are not runtime reflected anywhere.
Good catch, will fix. |
My point specifically is that environment-level configurations interacting with policy makes policy support multiple environment invocations. Consider if we support For example, in my mind, policy generation for a specific invocation would be automated via eg |
@guybedford but doesn't that mean that if a user land loader passes a condition to the default loader that isn't in that list it will never work with policies? That doesn't seem to make sense to have that limit if so. What do we get from that limit? |
I'd note that the policy remains deterministic and static either way. |
The limit just ensures the process of creating a policy is well-defined since otherwise how do you know the full possible set of conditions loaders might pass given arbitrary runtime branches. Let me just state outright the end to end model that makes the most sense to me:
All of the above could be achieved if we had loader composition landed first and then made policy a "core loader" perhaps via That's my full say here... just wanted to share the thoughts... but I'm not against anything here. |
You would never know once a runtime loader is involved. Policies just state what the default is allowed to return. They are not a limiter/constraint system to loaders.
Policies apply to loaders currently, this would not be possible to recreate unless we have loaders instrumenting each other.
I don't understand this. A loader can currently do that by monitoring the input/output of the default loader already. You would likely not want to run this in the same situation as when the policy is applied.
I think there are a ton of interesting cases which wouldn't match up with the idea that conditions are part of the environment. Things like loader conditions that are only set before the main entry point to the application to ensure that various hardening APIs are only loaded in a higher integrity set of globals etc. Overall since there are no complaints about landing this PR as is I think we should move forward. I think if people want to expose policies / use them from loaders we can enable a reflective API somehow in a different PR. I'm not sure I understand the last few points. We do have write ups on various policy intents in https://docs.google.com/presentation/d/153ME48cGiIo7RZpORjbbtogMbA4TeBZXv2EnEeKdZGg/edit#slide=id.p and I hope to update it when we finish landing various other features since some of the points are out of date (like |
fixed a bug with localMappings that only mapped based upon specifier instead of specifier and conditions. test is permutation based, i figured a depth of 3 was a reasonable stopping point. this did introduce a compositeKey API for internal usage due to some complexity with creating that map key. CC: @devsnek |
Fresh CI then will land in a bit after |
d463fe4
to
f2183d9
Compare
0ac7513
to
52c7ed6
Compare
PR-URL: #34414 Reviewed-By: Jan Krems <[email protected]> Reviewed-By: Guy Bedford <[email protected]>
Landed in e155d96 |
PR-URL: #34414 Reviewed-By: Jan Krems <[email protected]> Reviewed-By: Guy Bedford <[email protected]>
PR-URL: #34414 Reviewed-By: Jan Krems <[email protected]> Reviewed-By: Guy Bedford <[email protected]>
@bmeck should we land this on 12.x? Seems like it would be relatively straight forward to fix the conflicts. |
@MylesBorins +0 on landing it on 12.x, the feature is nice but not worth wrangling any conflicts to me personally. if you want to, that seems fine as likely we will continue to update policies |
This PR is to add resolve conditions to policies. This helps to align the use policies as a configuration of the capabilities of the default resolver. There is some minor refactoring to match naming a bit better across the code base. I did not see a clean place to actually traverse conditions and maybe we should make one? Additionally, this fixes policy dependency redirects to be working with ESM.
Tests not yet done as we may have minor alterations needed due to my less experience with "exports"/"imports" and actual condition resolution.
CC: @nodejs/modules-active-members due to the inconsistency with
node:
vsnodejs:
prefix for builtin resolution vs policies. I think moving tonodejs:
is fine, but would prefernode:
personally. The only important bit is that they match.Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes