Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to initialize a shared javascript module default export

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.

like image 897
bernie Avatar asked Dec 01 '25 09:12

bernie


1 Answers

The problem with your code is that

let api = null;
export default api;

does export the value null in the implicitly generated binding for the default export. However, you can also export arbitrary bindings under the name default by using the syntax

let api = null;
export { api as default };

This will work as expected. But you still need to make sure that no module accesses this export before you called initApi.

like image 194
Bergi Avatar answered Dec 03 '25 22:12

Bergi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!