Comments (41)
Being able to dynamically load dependencies would make a huge difference, webpack already has this supported, I'd like to see this happen sometime in the near future.
from metro.
Another reason is that we'd have to be over-eager when bundling, and include all the JS files, that would cause bigger bundles
I think that not implementing this because it could blow out bundle sizes is like saying you're not going to support node_modules
, because too many modules will blow out bundle size. Webpack allows this, but doesn't encourage it. If a developer plans on using this, of course the burden is on the developer to mitigate bundle size issues.
One place I'd like this is in importing translation files. They all sit in the same place, and if I could use a variable, I could do something like
import(`translations/${locale}.json`)
In this case, eagerly loading the whole folder is not just a side-effect, but the intention, given the alternative of
const translations = {
en: import(`translations/en.json`),
de: import(`translations/de.json`),
'en-US': import(`translations/en-US.json`),
};
And so on for more languages as my service scales out.
Not only that, but I also need to load locale data, both for the Intl
polyfill and for react-intl
. I end up having three lists that do nothing but contain imports for every file in three different folders. If anything, that'll increase bundle sizes more, because of all the code sitting there to fill the void of dynamic imports.
Additionally, it'd be nice to be able to import
via http. For instance, instead of bundling all translations with my application and increasing download for people who only need one language, have
const translations = await import(`https://some-s3-url.com/translations/${locale}.json`);
and similarly with the locale data that also needs to be loaded.
from metro.
Bump. This feature is really important....
from metro.
Is there any update status for this issue?
from metro.
Another reason is that we'd have to be over-eager when bundling, and include all the JS files, that would cause bigger bundles
I think that not implementing this because it could blow out bundle sizes is like saying you're not going to support
node_modules
, because too many modules will blow out bundle size. Webpack allows this, but doesn't encourage it. If a developer plans on using this, of course the burden is on the developer to mitigate bundle size issues.One place I'd like this is in importing translation files. They all sit in the same place, and if I could use a variable, I could do something like
import(`translations/${locale}.json`)In this case, eagerly loading the whole folder is not just a side-effect, but the intention, given the alternative of
const translations = { en: import(`translations/en.json`), de: import(`translations/de.json`), 'en-US': import(`translations/en-US.json`), };And so on for more languages as my service scales out.
Not only that, but I also need to load locale data, both for the
Intl
polyfill and forreact-intl
. I end up having three lists that do nothing but contain imports for every file in three different folders. If anything, that'll increase bundle sizes more, because of all the code sitting there to fill the void of dynamic imports.Additionally, it'd be nice to be able to
import
via http. For instance, instead of bundling all translations with my application and increasing download for people who only need one language, haveconst translations = await import(`https://some-s3-url.com/translations/${locale}.json`);and similarly with the locale data that also needs to be loaded.
This is really important use case.
from metro.
This is also important for loading storybook stories from src folder, without maintaining an index file.
https://storybook.js.org/basics/writing-stories/#loading-stories-dynamically
from metro.
Bump. It would be extremely nice to have the ability to do this.
from metro.
We plan on adding the import('foo')
syntax to be able to load modules asynchronously, paving the way of being able to download part of the code on-demand, for example. This is something I'd love to happen towards the end of the year.
However, we have no plans to support non-static imports in general, whether it's a require(foo)
or import(foo)
where foo
is a variable which value isn't known at bundling time. One reason I could point out, in particular, is that dynamic imports cannot be typecheked with Flow anyway (and I'd be surprised they are with Typescript either, but I haven't checked). Another reason is that we'd have to be over-eager when bundling, and include all the JS files, that would cause bigger bundles.
We could theoretically typecheck & support dynamic requires under the condition that the list of possible modules to be imported is known at bundling time, though it's not clear what syntax would express that. What I've seen in some projects to workaround this, for example for routing systems, is to have each require
explicit. Ex.
{
routeName: 'foo',
view: () => require('SomeComponent'),
},
{
routeName: 'bar',
view: () => require('SomeOtherComponent'),
}
That is effectively equivalent to having 'dynamic' requires with a list of fixed possibilities, but more verbose. It would be possible, for one who wishes, to implement a Babel transform that'd make that less verbose.
from metro.
Importing unbundled code is a very interesting use-case that would in the general case need to implement https://whatwg.github.io/loader/
I've gotten surprisingly far by simply combining fetch()
and eval()
, some simplified code:
const externals = {
react: require('react'),
'react-native': require('react-native'),
};
async function fetchImport(url) {
const res = await fetch(url);
const source = await res.text();
let exports = {};
const module = { exports };
const require = id => {
if (!(id in externals)) throw new Error(`Module not found: ${id}`);
return externals[id];
};
eval(source);
return module.exports;
}
The main problem with this is that error stacks in (at least android) JSC don't include frames from eval'd code at all, even with \n//# sourceURL=${url}
appended (which works when using the remote debugger)
from metro.
Is this being actively looked at/worked on by anyone at this point? This is still a feature I would very much like to see in RN, and was actually surprised not to have in the first place.
- Being able to dynamic require at runtime: no, this isn't being worked on right so far as I know.
- Being able to ignore dynamic requires (such as in moment.js) and have them throw at runtime, or replace them by static requires: this is already possible by having a custom babel transform that replaces dynamic requires by some code that throws, or just does nothing.
I'd like to publish some simple module to allow people to do the second case easily.
from metro.
This bug limit to numbers of really popular libraries like e.g. moment/moment#3624 i saw similar other items with same issue
from metro.
I was asking because readdir
would be even more of a stretch to support by default, there's no concept of filesystem anymore at runtime. I think the way you approach this, by generating an aggregated module, is correct. But ideally the bundler would be able to generate it on-demand rather than it being a separate build step.
from metro.
For example we could imagine some opt-in configuration setting that makes packager include all source files from a particular directory into a bundle
You can do this with babel-plugin-wildcard
https://www.npmjs.com/package/babel-plugin-wildcard
It works for us in production.
from metro.
@camdagr8 what you want can also be done by using babel macros / codegen
https://medium.com/@karanjthakkar/using-babel-macros-with-react-native-8615aaf5b7df
@jeanlauliac I have an app which is actually 2-in-1 apps. It's like a merge of the Uber Driver and Uber Customer app into a single app shell, with a common login interface. After login we should redirect the user to the correct app according to a boolean. I want dynamic imports because if the user is a driver there is no need to load the client app code (+ the apps were designed initially as standalone and have initialization side effects, singletons...)
Are you saying that this could be a solution to my problem?
const Main = ({user}) => {
const UberApp = user.isDriver ? require("UberDriverApp") : require("UberCustomerApp");
return <UberApp/>;
}
And this will ensure that only one of the 2 "internal apps" are effectively loaded, giving me some benefits (less JS parsing, faster initialization, no unwanted/unneeded startup side effects...) ???
from metro.
You can try metro-code-split https://github.com/wuba/metro-code-split .
it provides Dll and Dynamic Imports features.
from metro.
I switched to re-pack which uses webpack 5 to bundle your app I'm happy with it so far
tell expo developers to switch to it too pls
from metro.
@jeanlauliac can discuss more about the plans for this.
from metro.
@jeanlauliac Thanks for explaining the background! I think what you describe toward the end is exactly what people (including me) expect to happen: 1) provide a way to inform the packager which modules are potentially imported 2) support dynamic (programmatic) import of those declared in point 1. As you point out, type checking would work, bundle size would not be an issue.
Since dynamic (variable) imports will be part of the language, can RN afford not to provide some form of support?
from metro.
Yes, even though I said we have no plans internally to implement it, I'd love RN to provide some form of support for this ^_^ We have no plans ourselves because we don't have a use case requiring that right now, and are busy with other issues, but I'm happy to look at proposals/PRs.
For example we could imagine some opt-in configuration setting that makes packager include all source files from a particular directory into a bundle, regressing build duration and bundle size but allowing for a form of dynamic require. It believes in need to be opt-in as it causes these regressions. Then we would have to figure out how numeric module IDs are handled in such a model.
from metro.
Ok fair enough. I will have a think about it (I am not familiar with RN packager etc. so it may be a bit of a learning curve for me but then again, it is something I would love to use, so ... :))
from metro.
Is this being actively looked at/worked on by anyone at this point? This is still a feature I would very much like to see in RN, and was actually surprised not to have in the first place.
from metro.
This is a huge bummer for me. I'm trying to take a Domain Driven Design approach with my RN app where each screen components have a predictable file structure:
--index.js
--actions.js
--actionTypes.js
--reducers.js
Then have a main reducers.js
file that runs a loop that requires each file and automatically makes it available in the export default {...aggregatedReducersObject}
This approach simplifies our development process and reduces the number of times we've had compile errors because someone forgot to include their actions/reducers/etc into the aggregated files.
As a work around, I'm running a gulp watch on the components directory, then writing to my aggregated file on creation/delete. I hate doing it this way as there are often times when the gulp watch craps out or there's race conditions that spring up when editing that causes a need for a restart of both my gulp watch and RN.
from metro.
Then have a main reducers.js file that runs a loop that requires each file and automatically makes it available in the export default {...aggregatedReducersObject}
I'm curious: how do you get the list of files to require, do you use readdir
?
from metro.
@jeanlauliac Yes, I use readdir via a custom CLI for the project that generates components/assets/libraries and adds the file names to the aggregated object.
It ends up looking like this:
export default {
Login: {
redux: {
state: require('./components/screens/Login/state').default,
actionTypes: require('./components/screens/Login/types').default,
actions: require('./components/screens/Login/actions').default,
reducers: require('./components/screens/Login/reducers').default,
services: require('./components/screens/Login/services').default,
},
screen: require('./components/screens/Login').default,
},
Home: {
screen: require('./components/screens/Home').default,
}
}
Then I use react-native-navigation
to register the screens:
import registry from './registry';
// Redux setup
let actionTypes = {};
let actions = {};
let reducers = {};
let services = {};
let state = {};
Object.values(registry).forEach((item, i) => {
if (!item.hasOwnProperty('redux')) { return; }
let k = Object.keys(registry)[i];
if (item.redux.hasOwnProperty('actionTypes')) {
actionTypes = Object.assign({}, actionTypes, item.redux.actionTypes);
}
if (item.redux.hasOwnProperty('actions')) {
actions[k] = item.redux.actions;
}
if (item.redux.hasOwnProperty('reducers')) {
reducers[k] = item.redux.reducers;
}
if (item.redux.hasOwnProperty('state')) {
state[k] = item.redux.state;
}
if (item.redux.hasOwnProperty('services')) {
services[k] = item.redux.services;
}
});
// Create the store
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(combineReducers(reducers), state);
// Register screens and create tabs
let tabs = [];
Object.values(registry).forEach((item, i) => {
if (!item.hasOwnProperty('screen')) {
return;
}
let k = Object.keys(registry)[i];
Navigation.registerComponent(`app.${k}`, () => item.screen, store, Provider);
if (item.hasOwnProperty('tab')) {
tabs.push(item.tab);
}
});
from metro.
@serhiipalash I'll have to give this a look. Thanks!
from metro.
@camdagr8 I don't know how this plugin works, but we have folder with these files
translations/
en.json
de.json
I think it replaces our
import * as translations from '../../translations'
on this
import de from '../../translations/de'
import en from '../../translations/en'
const translations = { de, en }
And it does this before React Native packager started. If you add more files in translations folder after React Native packager start (during development), it will not know about them until you restart it.
In production as we don't have such case - read dynamic new files during runtime, we can use it.
from metro.
Webpack does this by parsing the module expression and bundling everything it could require
Typescript has been coming at typing modules for a while, but the current closest issue would be microsoft/TypeScript#14844
from metro.
I want dynamic imports because if the user is a driver there is no need to load the client app code (+ the apps were designed initially as standalone and have initialization side effects, singletons...)
Since your requires are inline, it's effectively not going to run either module factory function until the code require one or the other, at which point it'll run only one. The other module (and dependencies) won't get run and so won't affect global state. Thus you don't need dynamic import()
for that, this is already something that works 😄
What dynamic import()
might be able to provide in that example, is downloading the JavaScript code from one app or the other, only when needed, so as to make the app lighter in weight. This is not something that Metro supports out-of-the-box, and it also comes with its own sets of tradeoff (won't work offline, etc.)
from metro.
Thanks @jeanlauliac , require() will be fine for me then, as I don't need to download remote js code, and require is enough for all local code (many usecases can be solved by using codegen macro above imho)
from metro.
Hi guys!
Exist any solution to download remote bundles at runtime?
from metro.
This request is not to download resources from the Internet and make them available synchronously. It's merely about loading the bundled assets with a URL that's constructed at runtime, so that I can load "./data/" + lang + "/" + file + ".txt"
and implement a class that loads a certain type of files.
I've got thousands of data files, and all those in several languages, a total of maybe 10000 files, and which file is loaded depends on the user input, so I really need to construct the filename at runtime.
I would already be happy with a normal fetch("asset://" + filename)
with async load. I completely fail to understand why that cannot be done.
from metro.
@jeanlauliac you mentioned that async import will be supported by end of year:
We plan on adding the import('foo') syntax to be able to load modules asynchronously, paving the way of being able to download part of the code on-demand, for example. This is something I'd love to happen towards the end of the year.
do you know if that has been completed? Or what the plans are for that, if not?
from metro.
I've left the project since then so I'm not 100% sure of the current state, but I think there's some partial support. @rafeca, @rubennorte, do you know if there is any possibly import()
support in OSS Metro?
from metro.
It's not totally related, but wanted to mention here that optional requires are now possible. Not sure how but this might enable a workaround for some people interested by this feature? react-native-community/discussions-and-proposals#120 (comment)
from metro.
Looks like this would be a solution for local loading of certain kinds of content, e.g. @benbucksch 's use-case for translation files. https://github.com/itinance/react-native-fs
from metro.
Are there any updates or any solutions? We really need to require conditionally because of integrating Huawei packages. They will be available on devices with Huawei services, but unavailable on all the others. This is really a blocker 😢
from metro.
you can already do conditional requires @biancadragomir IIRC.
if (HUAWEI_DEVICE) {
require('huawei-packages');
}
this issue is specifically about not being able to use runtime-evaluated strings as import locations, for instance if you wanted to do:
const manufacturer = getManufacturerName();
require(`${manufacturer}-packages`);
edit: in fact #52 (comment) mentions it specifically - react-native-community/discussions-and-proposals#120 (comment) mentions it's released.
from metro.
@deecewan we are doing that already. But we are using a tool called handpick to exclude the huawei dependencies from normal builds. the problem is that for normal builds (non-huawei), that package isn't available and there's a crash when the build happens since the packager resolves all the "require(...)" from the project, regardless of the if branch they are on.
from metro.
Do not import/require the package at the top of the file.
Require it inside the function that it will be used in.
from metro.
You can do this with
babel-plugin-wildcard
npmjs.com/package/babel-plugin-wildcardIt works for us in production.
It does not work in dev mode since the resolved files are cached by metro. Any changes to them or adding removing will not be recognised. I wonder if there is a metro setting to exclude certain file patterns from being cached?
from metro.
I switched to re-pack which uses webpack 5 to bundle your app
I'm happy with it so far
from metro.
Related Issues (20)
- Unable to resolve module when using symlinks. HOT 6
- error Cannot read properties of undefined (reading 'transformFile'). TypeError: Cannot read properties of undefined (reading 'transformFile') HOT 2
- Metro uses watchman that leaks watched files HOT 2
- Default metro resolver slower than MetroSymlinksResolver in a monorepo HOT 3
- React Native application does not update on many code changes when using vim HOT 4
- esm HOT 1
- Performance regression between 0.73.9 and 0.76.8 HOT 4
- Unable to resolve self-referring "subpath exports" from within a haste module HOT 1
- `nodeModulesPaths` not working as expected with React Native HOT 1
- Cannot read property 'transformFile' of undefined HOT 1
- Including local packages from outside project root. Can we do better?
- [BUG] Terser mangler issue after upgrading to RN 0.74.5
- Problem during ReactNative project deployment for iOS HOT 2
- Add types for `require.context` HOT 1
- Package Exports resolution differs from Node in 2 edge cases HOT 3
- Slow bundle download(~2 mins) in dev mode by ReloadCommand(r) on iOS after RN 72 upgrade HOT 6
- Uncaught TypeError: (0 , import_toolkit.createSlice) is not a function
- [Help Wanted] Need help with the blocklist option to exclude a folder from metro build
- Metro bundle not connecting with iOS simulator in Release and Debug mode
- Metro bundle not connecting to iOS simulator in Release and Debug mode HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from metro.