Skip to content
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

Require file dynamically by variable #6391

Closed
pie6k opened this issue Mar 9, 2016 · 28 comments
Closed

Require file dynamically by variable #6391

pie6k opened this issue Mar 9, 2016 · 28 comments
Labels
Ran Commands One of our bots successfully processed a command. Resolution: Locked This issue was locked by the bot.

Comments

@pie6k
Copy link

pie6k commented Mar 9, 2016

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

@pie6k pie6k changed the title Require file dynamically Require file dynamically by variable Mar 9, 2016
@rxb
Copy link
Contributor

rxb commented Mar 9, 2016

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; 

@pie6k
Copy link
Author

pie6k commented Mar 9, 2016

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

@rxb
Copy link
Contributor

rxb commented Mar 9, 2016

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];

@pie6k
Copy link
Author

pie6k commented Mar 9, 2016

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.

@ide
Copy link
Contributor

ide commented Mar 10, 2016

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...
}

@jaygarcia
Copy link
Contributor

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?

@jsierles
Copy link
Contributor

@facebook-github-bot stack-overflow

@facebook-github-bot
Copy link
Contributor

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.

@jarecsni
Copy link

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.

@ide
Copy link
Contributor

ide commented Aug 31, 2017

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.

@jarecsni
Copy link

jarecsni commented Sep 1, 2017

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?

@jarecsni
Copy link

jarecsni commented Sep 5, 2017

Any thoughts anyone?

@ide
Copy link
Contributor

ide commented Sep 5, 2017

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.

@jarecsni
Copy link

jarecsni commented Sep 6, 2017

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?

@averypfeiffer
Copy link

averypfeiffer commented Sep 7, 2017

+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

@jarecsni
Copy link

jarecsni commented Sep 7, 2017

@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.

@jarecsni
Copy link

jarecsni commented Sep 7, 2017

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

@averypfeiffer
Copy link

@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.

@jarecsni
Copy link

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.

@ide
Copy link
Contributor

ide commented Sep 12, 2017

@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.

@dominictracey
Copy link

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.

@jarecsni
Copy link

Opened facebook/metro#52

@serhiipalash
Copy link

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.

@geyang
Copy link

geyang commented Jan 31, 2018

@serhiipalash Thanks a lot!

@serhiipalash
Copy link

@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.

@xaphod
Copy link

xaphod commented Mar 6, 2018

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);

@phitien
Copy link

phitien commented Mar 30, 2018

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

@RobertFischer
Copy link

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

ZaBursta pushed a commit to ZaBursta/protobuf.js that referenced this issue May 17, 2018
@facebook facebook locked as resolved and limited conversation to collaborators May 24, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 20, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Ran Commands One of our bots successfully processed a command. Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests