React-native: Require file dynamically by variable

Created on 10 Mar 2016  路  28Comments  路  Source: facebook/react-native

I want to require files like:

var component = require("myApp/components/" + name);

Seems like require wants passed string to be static, not dependent on any runtime actions.

var name = "myComponent";
var component = require("myApp/components/" + name); //does NOT work - returns Requiring unknown module error
var component = require("myApp/components/myComponent"); //DOES work even when those string values are EXACTLY the same

Is there any way to require files by dynamic string?

Here is similar question, but based on node.js

Ran Commands Locked

Most helpful comment

What if I'd like to require all files in dir?

All 28 comments

Based on how the packager works, this isn't really possible with require.
Packaging happens once before runtime so those variables don't have values yet.

You can do something like this though:

var firstComponent = require("myApp/components/firstComponent"); 
var secondComponent =  require("myApp/components/secondComponent"); 
var myComponent = (useFirstComponent) ? firstComponent : secondComponent; 

What if I'd like to require all files in dir?

You could also create a single file that just serves to require all of the needed components into an object and then exports that object. Then all you need to do is require that single file.

var assortedComponents = require("myApp/components/assortedComponents"); 

var myComponentName = "firstComponent";
var myComponent = assortedComponents[myComponentName];

Thanks @rxb, I know I can collect my components in many ways. I appreciate your help, but I'd like to know if there is any way to require file with dynamic string path or require all files in dir without knowing what are they.

is any way to require file with dynamic string path or require all files in dir without knowing what are they

This is not possible -- you should generate a massive switch-case statement that looks like this:

switch (name) {
  case 'a': return require('./a');
  case 'b': return require('./b');
  // etc...
}

You have to think of how this code will execute within a production app -- all assets must be present the way React Native is configured out of the box.

Does this issue need to stay open?

@facebook-github-bot stack-overflow

Hey @adampietrasiak and thanks for posting this! @jsierles tells me this issue looks like a question that would be best asked on StackOverflow. StackOverflow is amazing for Q&A: it has a reputation system, voting, the ability to mark a question as answered. Because of the reputation system it is likely the community will see and answer your question there. This also helps us use the GitHub bug tracker for bugs only. Will close this as this is really a question that should be asked on SO.

The inability to require modules programatically (as opposed to statically) is extremely limiting. There are new language constructs coming like import() which makes this mandatory in any JS environments, React Native simply cannot afford this simplistic approach. And the frustrating thing is the support is almost there, its just limited to development environment.

I suspect React Native will support import() in a static way that is useful for lazily importing modules:

async function getDataAsync() {
  const ApiClient = await import('./ApiClient').default;
  return await ApiClient.getDataAsync();
}

Perhaps Metro bundler will have a way to support semi-dynamic imports if you give it a list of files that may possibly be included up front:

function unusedThisIsJustForBundling() {
  import('./ApiClient');
}

async function getDataAsync(apiClientModuleName) {
  const ApiClient = await import(apiClientModuleName).default;
  return await ApiClient.getDataAsync();
}

getDataAsync('./ApiClient')

If you have a constructive idea that works well in React Native while meeting its constraints, please suggest it.

hey @ide

Thanks for your reply. Let me explain why I think it is important to be able to import a module without directly referencing it (using a string literal).

I recently started to use a dependency injection framework (appolo-inject) and there you can describe how your objects are wired together, etc. The framework uses require() to load the modules just in time, which is quite nice, given you can have a large dependency graph, which do not want to load in one go. But for this to work, you need to be able to pass a variable to the require() call.

And if you think about it in general, being constrained to use string literals only does feel very limiting, I mean you are stripped from the ability to make runtime, dynamic decisions on module loading.

By the way import() specification (see https://github.com/tc39/proposal-dynamic-import) describes the ability to support runtime determined strings (aka variables) for import, so I am not sure if the above described approaches would be satisfactory.

Also the current situation is rather inconsistent, these constructs work fine in Jest tests, and yet they fail when running the actual code. I do not think redefining (constraining) require() this way is a good approach.

Since this is working in development environment, could this not be allowed to work in production builds? Here is what I am referring to:

function _require(moduleId) {
  if (__DEV__ && typeof moduleId === 'string') {
    var verboseName = moduleId;
    moduleId = verboseNamesToModuleIds[moduleId];
    if (moduleId == null) {
      throw new Error('Unknown named module: \'' + verboseName + '\'');
    } else {
      console.warn('Requiring module \'' + verboseName + '\' by name is only supported for ' + 'debugging purposes and will BREAK IN PRODUCTION!');
    }
  }

  var moduleIdReallyIsNumber = moduleId;
  var module = modules[moduleIdReallyIsNumber];
  return module && module.isInitialized ? module.exports : guardedLoadModule(moduleIdReallyIsNumber, module);
}

So it is possible to do a reverse lookup to get a module id in development, I do not quite see why this should not be allowed in production?

Any thoughts anyone?

A couple of things -

  • It is possible to make some dynamic decisions at runtime but not all. The packager still needs to know the set of files that you potentially could import at runtime in order to include them in the bundle.

    • Jest tests (and Node) run in a completely different environment than on the client. There are many things that work in Jest that won't work in RN and vice versa.

    • The APIs are inconsistent because the environments are fundamentally inconsistent. A user's phone that has a JS bundle is very different from a developer's computer that has all of the source code you could ever want to import.

    • Wanting to run require('foo') at runtime (and same for import('foo')) seems reasonable and possible to me.

    • The key thing is that the packager needs to know that you might try to import foo with a variable. You need to communicate that to the packager somehow, which is why making a list of potential imports is important.

Ok I think the core problem boils down to how to communicate it to the packager that you may want to import something at runtime, as we want the packager to bundle all resources that can potentially be requested (require()-d or import()-ed) at runtime. The problem is that I think the packager (just like webpack) scans the imports and builds a dependency tree, and so if something is not literally and statically imported, it will be missed by the packager.

I can think of an easy way out of this. Since we cannot rely on the code here, we need to add this information in the configuration. Just like we use aliases, we could have configuration for dynamic ('potential') imports. I suggest the ability to mark whole folders as potentially required. The files in those folders would be added to the bundle even if they're not explicitly required by static means.

Thoughts?

+1 for this. As stated further above there are workarounds like building a gigantic switch statement, but that seems incredibly brittle and unmanageable. Since for now this seems like an edge issue would it possible to support this functionality with a flag to the react-native cli? Maybe a "dynamicImports" folder as a sort of catch al?l

@pie6k So are you saying that just because certain tools or libraries cannot support something we need to abandon it? This doesn't sound like a valid argument to me. The JavaScript language will very soon have import() with full dynamism. React Native will feel very old school at that point. As I pointed out above, there are a number of use cases (especially lower level framework code, that true) where such dynamic ability to require module is a must.

@averypfeiffer I'd rather have an ability to explicitly mark modules/folders as dynamically importable. It would result in healthier code organisation, etc.

@jarecsni I agree, just trying to think of the simplest path forward while import() is still in the proposal stage. Another possibility would be adding a "special comment" syntax similar to what flow does with // @flow. The react-native packager would then have to scan the entire codebase.

Guys, do you think anything will happen around this any time soon? Do I need to raise this somewhere else as a feature request maybe? I mean I do think it is important and would like to follow it through and maybe contribute if I can, just a bit unsure as to how. Thanks.

@jarecsni I don't think it's a priority right now. The repo to continue this conversation on is https://github.com/facebook/metro-bundler.

Is there an issue in the metro-bundler repo to keep tabs on this? I am relatively new to RN and had thought to use dynamic require() calls to solve my particular use case but suppose I'll need an alternate solution in the short term.

What if I'd like to require all files in dir?

You can do this with babel-plugin-wildcard https://www.npmjs.com/package/babel-plugin-wildcard

It works for us in production.

@serhiipalash Thanks a lot!

@episodeyang np

Be aware that if you import modules in this way, bundler will not know about new files added in folder after it started. You have to restart bundler each time you add new files in imported folder. It is important only in development when you create new files.

I had a hard time getting babel-plugin-wildcard to work, specifically the .babelrc part - to load a bunch of JSON files without me needing to type in all the filenames. Eventually I got it working. I'm pasting the relevant files here, in case others have this issue. I'm using React Native 0.54.

.babelrc:

{
  "presets": ["react-native"],
  "plugins": [
    ["wildcard", {
        "exts": ["js", "es6", "es", "jsx", "json"]
    }]
]
}

To do the loading of JSON files:

import * as recipesFromDisk from './json';

const recipes = Object.values(recipesFromDisk);

You can use eval
var path = '/path/' eval(const value=require('${path}')``

I find it odd that people are saying this is impossible, when Webpack does it and has for a long, long time: https://webpack.js.org/guides/dependency-management/#require-with-expression

Was this page helpful?
0 / 5 - 0 ratings