I want to share an api instance across multiple modules and be able to initialize it with external configuration. My code uses Webpack and Babel to transform those nice ES6 modules into something usable by browsers. I'm trying to achieve this:
// api.js
let api = null;
export default api;
export function initApi(config) {
// use config to configure the shared api instance (e.g. with api base url)
api = ...
}
// ======================
// entry.js
import { initApi } from './api';
import App from './App';
// Initialize the single shared instance before anyone has the chance to use it
const apiConfig = ...
initApi(apiConfig);
// Create the app and run it
// ======================
// App.js
// RootComponent has an import dependency chain that eventually imports DeeplyNestedComponent.js
import RootComponent from './RootComponent';
// Actual App code not important
// ======================
// DeeplyNestedComponent.js
// PROBLEM! This "assignment" to the api var happens before initApi is run!
import api from '../../../../api';
api.getUser(123); // Fails because "api" stays null forever even after the initApi() call
The "problem" occurs because ES6 modules are imported statically and import statements are hoisted. In other words, simply moving the import App from './App'
line below initApi(apiConfig)
doesn't make the import happen after initApi
is called.
One way to solve this is to export an object from api.js
(or in another globals.js
file if I have multiple such shared objects with the same pattern) instead of a single variable like this:
// api.js
const api = {
api: null,
};
export default api;
export function initApi(config) {
// use config to configure the shared api instance (e.g. with api base url)
api.api = ... // <-- Notice the "api." notation
}
// ======================
// DeeplyNestedComponent.js
// api is now the object with an empty "api" property that will be created when initApi() is called
import api from '../../../../api';
api.api.getUser(123); // <-- Ugh :(
Is there a way to achieve initialization of a shared service instance elegantly when using ES6 modules?
In my case, DeeplyNestedComponent.js
must still import
the api instance somehow. In other words, there is unfortunately no context object passed from App
all the way down to DeeplyNestedComponent.js
that could give access the api instance.