-
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
module: development and production exports conditions #32869
Conversation
@nodejs/modules-active-members |
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.
LGTM, I think this will be super valuable for packages that want to switch from their previous solution (e.g. conditional require in a proxy file) to a solution that works well with native module graphs. TLA+import()
won't properly preserve bindings, so it's not a full alternative to this.
I've also updated the conditional exports docs example to include this feature in the latest commit, while also demonstrating nesting and fallbacks. We don't currently have enough examples of conditional exports patterns, so hopefully this is somewhat of a first step without polluting the docs. |
@@ -358,6 +358,9 @@ Node.js supports the following conditions: | |||
* `"node"` - matched for any Node.js environment. Can be a CommonJS or ES | |||
module file. _This condition should always come after `"import"` or | |||
`"require"`._ | |||
* `"production"` - matched if `process.env.NODE_ENV` is set to `production`. |
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.
i'm not clear on how this works at all. is this production for "require" or for "import"? what about for "browser" or for "node"?
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.
I just updated the example to a case where production
and browser
are used together. These conditions are fully orthogonal if we are still allowed to use that word, so by default it applies for both require and import.
What would be the plan if we were to add additional environment values to the |
I think we need to look at this on a case-by-case basis. For example, a test runner can already enable a test condition using loaders right now, and a test runner probably already wants to use loaders anyway, so I'm not sure it's a given that Node.js core needs to implement a test condition itself. Another option would be to support comma-separated conditions in |
I'm not really interested in adding any values. I think |
@nodejs/tsc I think you might want to be aware of this, this PR makes I tend towards being -1 on this. If we want to modify Node’s behavior, the standard way to do that is through CLI flags, including ones that would end up in Alternatively, could the environment variable name be encoded into the condition in the |
I'm a bit wary of encoding things from the environment arbitrarily. We wouldn't want to include things like: {
"exports": {
"USER=root":"badThing",
"default":"safeThing"}
} Limiting to a whitelist is a possibility so that only |
I can see why having exports specific to either browser or node makes sense. Having them for NODE_ENV seems more like a foot gun than a feature, though. Should NODE_ENV really affect a module's API? Or do I misunderstand this feature? |
@sam-github the intent is more to allow a |
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.
Unfortunately there is a significant amount of tooling in use at companies that loads configuration values depending on NODE_ENV. It conflicts with this work, because it would prevent them to have a “production” build in a “staging” environment.
I’m -1.
For tools that use this solution today, isn't that already true? E.g. react will turn on its development messages and non-optimized messages. Without this feature, they are likely to go with something like |
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.
If this is done in its current form, NODE_ENV
needs to be added to lib/internal/main/print_help.js
and doc/node.1
.
@addaleax thanks for the clear direction here. @mcollina if you could get back to @jkrems on his response to your -1 here in #32869 (comment) that would be really great. We will be discussing this at the next modules meeting so it would help to ensure we have worked through the feedback as much as possible. Alternatively if you are able to make the Wednesday meeting to discuss this in more detail that would be a huge help for us to be able to assess directions forward here. |
I disagree with that choice as well, and I think it sets a bad precedent. However, it is their choice. I've seen so many cases where that implementation causes significant issues to approve this. My -1 is firm, if this should move forward it can go to a @nodesjs/tsc vote. |
Thanks @mcollina - there's a wide design space here so it's more about trying to understand the root of the concern so that we can inform the design based on this TSC feedback. But it is no use having those conversations without having the full feedback. To try and capture your hard block succinctly, would you say the following statement covers your concerns: that you are against using the existing process.env.NODE_ENV value because it might conflict with existing uses today in applications. To dig a little deeper into that:
The primary benefit of using this existing approach is that a lot of libraries like React (https://unpkg.com/react@16.13.1/index.js, and see also the inspiration for this PR in https://twitter.com/sebmarkbage/status/1250453658904326144) use this pattern already, such that it is encoded in many developer build workflows to define this environment variable. If we create a new mechanism the cost to users will be they now have to do two separate things! So we're making more work by not adopting the existing cowpath first and foremost. In particular the answer to (3) would be very useful to understand if there might be a middle ground on the cowpath as it were. |
The key problem I have with this approach is that it makes impossible to configure one library in one way vs another. It's unflexible, uncustomizable and hard to operate in production. Whenever there are incidents, it makes debugging and diagnosing thing harder. Sanctioning this as the "official and recommended" approach is what troubles me the most. Note that Node.js does not support I think having a flag or an env variable is identical for me, as long as it's specific and not overloaded with other meaning, e.g. |
Do you mean specific to In terms of the specific dev/production feature - yes it is a common deployment problem that development-time features can be kept on, and this feature exactly should be designed with this in mind. If we remove the constraint of following the existing process.env.NODE_ENV pattern, then yes we can certainly switch things around and make So with that in mind, would an endorsed Thanks for working through these details, it is very beneficial to see where the arguments land here. |
Just my 2 cents from the both user's and author's perspective. I don't mind this being based NODE_ENV, a separate process flag or anything else. Values for the thing (prod/dev/debug/*) is also irrelevant. I believe that the ecosystem can adapt to a new standard - truth to be told existing NODE_ENV conditions are somewhat quirky and often requires some additional "proxy" files to make library setup "right". I strongly believe though that there is a big interest from authors' side (which translates directly to users' side as well) to be able to ship 2 versions of their library and have the appropriate one being picked based on a single, standard~, value in the build pipeline/process. I see this PR as a great standardization point because those things are being already spread across the ecosystem, but yet it's all based on some assumption and aggregated knowledge in people's minds. There is no existing good point of reference on how to:
And I see people tripping over this on a regular basis because of that. @guybedford thanks for championing this 🙏 |
I would prefer to have individual flag first and a “development” mode potentially added later as a preset (a different PR). We need to consider the fact that a lot of devs change NODE_ENV from within their Node.js application to alter these behavior. Do you think it would be possible somehow? I agree that having an endorsed way of doing this is important for the ecosystem. It would fix a lot of issues. |
This would be possible once we have a VM/Realm API for spawning subloaders (we had this discussion previously). Otherwise it needs to be set in the application loader on init (and the boot loader is becoming a more important concept in Node.js). We likely wouldn't get consensus to have a dynamic aspect in the resolver from within the environment itself, as that breaks the static invariant of resolution. It's important to set it on loader init so that the resolution process itself remains deterministic from within the environment.
So we do actually have a way to do this right now with a loader like: devloader.js export function resolve (specifier, context, parentResolve) {
return parentResolve(specifier, { ...context, conditions: [...context.conditions, 'development'] });
} and running If we want a generic dedicated flag or environment variable that could be an option, we just need to be sure we want to expose it at this high level and which of those two to choose. Alternatively, given that the above already provides the means, perhaps we could look at a So there seem to be these two main questions:
I'm still leaning towards the specific development scenario because that solves the problem and provides guidance for the ecosystem, given that the overall use cases are possible with loaders. In that case having a corresponding environment variable seems sensible to me. I keep trying to come up with examples of other useful conditions to set here, but tend to worry about exposing this stuff too and it does feel like this is one of the most important ones. |
tbh I think it's far too early to be convinced that this is the pattern that should guide the ecosystem. |
I think this depends on having that available. I would prefer not to commit to any mechanism that provides a worse user experience that that a dynamic evaluation of |
@mcollina you seem to be saddling a very fine line of not wanting things like NODE_ENV but also wanting the same experience of NODE_ENV. Can you refine what the exact thing you are trying to preserve about dynamically altering NODE_ENV at runtime, that behavior does not mesh with loading in most libraries I've seen that have an |
Came here a bit late, but this seems like EXACTLY the thing we have loaders for, especially considering it involves development environments. |
This was my first thought as well, especially considering that the loader in question is so simple (see Guy's one-liner above). However I think the reason to put this in core is to standardize a pattern for doing this, so that it's not just useful for the user's own code but also the libraries they want to import. If React and Vue and so on all know to publish a |
seems like more discussion on what a "development mode" for node would be is needed, how that plays into the wider ecosystem, etc. I think it involves a lot more than just how node resolves files. |
My 2 cents regarding interop with webpack: In general I like this idea. I would favor an CLI argument Arguments above about only having a In general I'm a fan of custom conditions and in webpack we plan to have that independent of how node decides about custom conditions. I'm fine if node doesn't support it, but also think it would be kind of useful. This could be another discussion after this PR. As far as I know ESM adoption of react is blocked by this feature. I don't think |
This supports two new conditions in conditional exports -
"development"
and"production"
. By default the"development"
condition is always matched unlessprocess.env.NODE_ENV
is set to"production"
in which case the"production"
condition is matched.By making this implementation simply a specification of what the standard in the ecosystem already is, the hope is that this will provide a simple to adopt incremental approach to the problem of production and development condition branching.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes