Metro: Passing extra options to custom resolvers #1803
motiz88
started this conversation in
Bundle Working Group
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Context
At Meta, we build several React Native apps in a large monorepo with a single shared Metro config. There is a use case we're exploring where we want some resolution behaviour to vary per app within the same Metro instance. Think of this as a form of dependency injection: the dependencies of a single module can have different resolutions, based on context that's only known at the app level. (See more about the use case under Future, below).
We could use custom transform options in the URL to change the dependencies at the source, before they're even resolved - but that would fork the transform cache for every variant, which is undesirable. Instead, we want to model the dependency injection case more closely: the importing module should be transformed (and cached) only once, and only the resolutions should change.
Proposal
Currently, a custom resolver's output is only allowed to vary based on the values available through the resolution context, and resolutions are cached in-memory inside the DependencyGraph class based on the origin directory, current platform and dependency specifier.
I propose that we:
customResolverOptions
mechanism where the bundle URL can have arbitrary extra parameters of the form&resolve.foo=bar
.graphId
for each bundle inIncrementalBundler
, and to the resolution cache key for each resolution request insideDependencyGraph
. This will have the effect of forking the in-memory resolution cache, but not the transform cache, based on the resolver options.customResolverOptions
toresolveRequest
via thecontext
parameter.customResolverOptions
, even for resolution requests that are otherwise identical.Future
Internally, what we're looking to build on top of this is a
require
-style API that conditionally resolves to one of a set of options passed at the call site, while allowing dead code elimination at build time.There may be room for this type of high-level API in Metro itself (or in React Native). For instance, the following code could resolve to one of three versions of a module based on the current "build flavor", which would be an app-defined or framework-defined variable.
This would be conceptually similar to resolving based on an environment variable[1], hence the name
require.env
. We could then create a mechanism for setting these variables in Metro's config and/or via the bundle URL, similar to what's proposed here withcustomResolverOptions
.[1] As alluded to above, the reason not to use something like
if (process.env.BUILD_FLAVOR === 'profiling') require('foo') else require('bar')
is that we'd need to fork the transform cache (i.e. transform every module with every combination of possible environment variables) in order to perform dead-code elimination.Such an API will need more careful design to be useful, and we would need to think about compatibility with other bundlers/runtimes before we introduce and recommend it in user code (and especially published packages). We therefore have no plans to do this right away (we'll only build the Meta-internal version of this for now). But we do expect that shipping the lower-level
customResolverOptions
primitive will allow us to prototype ideas for such an API and inform the eventual design.Another existing design in this space is Node's subpath imports with user-defined conditions. It will be worth evaluating that as an alternative to shipping our own conditional
require()
-style API.Beta Was this translation helpful? Give feedback.
All reactions