How to initialize a shared javascript module default export - javascript

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.

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.

Related

How to use plugins in vuex (Vue 2x) [duplicate]

I declare a global variable in the main.js of the Vue.js project.
Vue.prototype.$API = "myapihere"
And I want to use this from everywhere.
and it's work properly by using this.$API.
But in Vuex it does not work.
console.log(this.$API);
Here this.$API is undefined.
How I use my $API in Vuex.
Vue 2 and Vuex 3 answer
In the store you can access the vue instance by accessing this._vm
const store = new Vuex.Store({
mutations: {
test(state) {
console.log(this._vm);
}
}
});
I'm using Vue 3 and Vue.prototype.$foo seems to have been removed for this version. I also found that in my version of VueX there is no this._vm.
I explored the Provide / Inject method which is recommended by the Vue 3 docs. This worked nicely for accessing globals from within my components, but I couldn't access them from within store.
The solution I went for was to use globalProperties on the Vue object and standard properties on store, and set them just before mounting the app.
main.js:
import store from './store/index';
import App from './App.vue';
// Load custom globals
import conf from '#/inc/myapp.config';
const app = createApp(App)
.use(store);
// Register globals in app and store
app.config.globalProperties.$conf = conf;
store.$conf = conf;
app.mount('#app');
What I like about this is that I can access the globals in the same way in both store and components.
In a component:
export default {
data() {
return {
};
},
created() {
console.log( this.$conf.API_URL );
},
}
...and you can access this.$conf.API_URL in the same way from actions, mutations and getters.
Once I'd found this solution I no longer needed access to the whole Vue instance from within store, but if you need it for some reason you can assign store.$app = app; in the same place in main.js.
You have 2 approaches:
Pass down the property (or even access the _vm property from inside Vuex) as an argument from a component
methods: {
this.$store.dispatch('someAction', this.$API)
}
Declare and export that same variable from another file and consume it from your main.js AND your Vuex file:
// api.js
export const API = "http://localhost:5000/api"
// main.js
import { API } from './api.js
...
Vue.prototype.$API = API
// store.js
import { API } from './api.js
// you can use API now!
Although I would personally lean towards the second, I would not store the API path in Vue at all as I'd rather have the api.js file as a service to perform all ajax calls and consume that file from where I need.
use this._vm
here is why
by default when you access this in vuex store it will point store so it will output something like this
so after that, you see that there is something called _vm in store here it is
so that _vm points to the vue component so to access it you will need to use this._vue
you can better create a getter of the vue instance like
const store = new Vuex.Store({
getters: {
vue(state) {
return this._vm
}
}
});
//so you can use it across your store
store.getters.vue
//Note
//the above way of accessing getter works on non `namespaced` stores
As of recently, under Vuex 4.* and Vue 3.*, this.$app hasn't been defined for the store object. Instead you have Vue Router defined as this.$router.
So for javascript, the way to get app in store would be like so:
The code would now be: router.app = app; and inside, say, an action: let app = this.$router.app;

Export a class or an object instance difference

I am confused, or I can say, I have no clue how the exporting actually works.
I have a React app, and I have some protected Routes. On login, I create a Client object instance and pass it to child components through Context, and then consume it.
Recently I saw an approach where the example code exported an object instance from a file directly and just imported it in files they wanted to consume it.
/** My approach **/
export default Example
/** The Object Instance approach **/
export default new Example()
What is the lifecycle of the object instance? Are there any disadvantages with the second approach, because it seems way easier?
If you export the class, with
export default Example
then consumers of the module will be able to instantiate their own instances, and each instance will be able to have its own data. For example
// 1.js
import TheClass from './TheClass';
const tc1 = new TheClass();
tc1.foo = 'foo';
// 2.js
import TheClass from './TheClass';
const tc2 = new TheClass();
tc2.foo = 'bar';
Both modules can continue to use tc1 and tc2 completely independently, since they're separate instances.
But if the original module exports an instance rather than a class, then all consumers of the module are forced to use the same instance:
// 1.js
import theInstance from '...';
theInstance.foo = 'foo';
// 2.js
import theInstance from '...';
// might not be a good idea to do theInstance.foo = 'bar' here
// because that will affect 1.js as well
// and will affect any other modules that imported the instance
In short - exporting the class is more reusable than exporting the instance. Sometimes potential reusability is something a script-writer will consider important, and sometimes it isn't. (And sometimes, even if you don't consider it useful initially, you may encounter a situation later that forces you to reconsider.)
And sometimes you want to make sure that there's only one instance ever in a script, in which case
export default new Example()
is a way to accomplish it.

Vuex store modules not loading in right order when importing store directly

I'm probably not seeing obvious, but after hours I don't get it still.
The problem: when I import my Vuex store into a non-component file (a api service), one module gets loaded properly while the other one only by name, but is otherwise empty.
// store.js
import * as collections from "./modules/collections";
import * as auth from "./modules/auth";
export default new Vuex.Store({
modules: {
auth,
collections
}
});
Both these modules are near-identical. Both have
export const getters = {}
export const actions = {}
export const mutations = {}
export const state = {}
Now when in some other non-component file I DON'T include the store, my vue store looks like this:
{"auth":{"authenticated":false,"authToken":"123","loginPending":false,"loginError":{}},"collections":{"collectionsPending":false,"collectionsError":null,"collections":[]}}
Now when I import the store to use it in my service like so:
import store from '../store'
// NO OTHER MENTIONS OF STORE, ONLY IMPORTING
Suddenly only my auth module is "empty"
{"auth":{},"collections":{"collectionsPending":false,"collectionsError":null,"collections":[]}}
It has something to do with module loading.
Order of loading by adding console.log statements:
Without the import:
INIT AUTH
INIT collections
INIT store
With the import:
INIT collections
INIT store
IMPORT STATEMENT -- INTO SERVICE
INIT AUTH
I'm using the Vue Webpack Boilerplate
Sigh, sorry. Circular dependency indeed. Was expecting a warning if I'd did that, but didn't.
Thanks Emile Bergeron.

Are multiple objects being created?

I'm working on a simple React library but I'm unsure whether multiple objects are being created unnecessarily.
I have an app.js file:
class App {
method1() {
}
method2() {
}
}
export default new App();
I also have an index.js file:
import app from './app.js';
...
export default app;
In the index.js of my React project (where I make use of the library) I use:
import MyLibrary from 'react-library';
...
MyLibrary.method1();
and then I do the same in some of my components too:
import MyLibrary from 'react-library';
...
MyLibrary.method2();
Is the second import of MyLibrary a different object to the first MyLibrary?
Is the second import of MyLibrary a different object to the first
MyLibrary?
In general the object returned by the import is cached (same behaviour as nodejs require), multiples import of the same file will result in the same object being returned. So the answer to your question is No, you're dealing with the same reference in memory.
https://webpack.github.io/docs/resolving.html
Every filesystem access is cached so that multiple parallel or serial
requests to the same resource are merged
in your particular case, as suggested in the comments section, you're exporting an instance, not the class itself.
export default new App();
consequently each component that import that file will deal with the same instance.
This is a singleton pattern, don't know if it is the desired behaviour, if you want that each component has it's own instance you should export the class instead.
export default App;
You are creating a singgleton there, put a breakpoint in the constructor and you will see how only one instance is created.

ES6: share data between modules

To share data between modules, a usual pattern is to capsulate the data into a common module and import it in other modules.
In my case the data to be shared is a logger, which need to be initialized before used. I call init() at the entry point of my application.
// main.js
let logger = require('#my/logger');
logger.init({...});
let xxx = require('./moduleX');
let yyy = require('./moduleY');
In other modules, the initialized logger can be used:
// moduleX.js
let logger = require('#my/logger');
const log = logger('moduleX');
function x() {
log.debug('some msg');
}
Above code works well in node.js. But if I change to ES6 module syntax, it doesn't work because ES6 module import is hoisted.
// main.js
import {logger} from '#my/logger';
logger.init({...}); // this line is run after import moduleX
import {x} from './moduleX';
// moduleX.js
import {logger} from '#my/logger';
const log = logger('moduleX'); // logger is not initialized !
export function x() {
log.debug('some msg');
}
With ES6 module, how can I initialize some data and share them to other modules?
There was a similar question but the answer doesn't fit my case.
Update:
Some answers suggest to put the code which access shared data into function so that the code isn't invoked immediately at module load. But what if I really need to access it during module loading? I updated my code to demonstrate the use case -- it would be too trivial to call logger(name) in every function if not make log as module scope const.
Finally I solve it in the way that #PaoloMoretti mentioned in his comment.
Write a module in my app to init the logger for my app:
// logger_init.js
import {logger} from '#my/logger';
logger.init({...});
Import the initialization module once at the entry point of application, prior to imports of any other modules that use logger as well. It guarantees that the initialization is done before loading other modules.
// main.js
import './logger_init';
import {x} from '#my/other_module';
import {y} from './module_in_myapp';
Other modules can use initialized logger directly:
// #my/other_module
import {logger} from '#my/logger';
const log = logger('moduleX'); // logger has been initialized
export function x() {
log.debug('some msg');
}
The dependency tree is:
<init>
myapp --+--> logger_init ------------> #my/logger
| <use> ↑
+--> module_in_myapp -----------+
| <use> |
+--> #my/other_module ----------+
Why I don't adopt the way that add a wrapper module which init and return a logger (as Bergi's answer) is because the modules uses logger could be reusable modules not in my application.
Try to provide some entry points to your xxx.js and yyy.js modules or even make them as functions.
// main.js
import {logger} from '#my/logger';
import * as xxx from './xxx';
logger.init({...});
xxx.run();
// xxx.js
import {logger} from '#my/logger';
export function run () {
logger.debug('In xxx');
}
You could have each module that has a dependency on some initialisation routine return a function that can be executed manually to invoke its functionality, rather than expose that functionality as an immediate side-effect of importing it. For example, in xxx.js:
// xxx.js
export default function (log) {
log('xxx');
}
Then place all initialisation operations within an entry file and invoke the modules defined in the above way after these operations are complete:
// main.js
import xxx from './xxx';
import {logger} from '#my/logger';
logger.init({...});
xxx(logger);
But what if I really need to access it during module loading?
Please see the amended code examples above. Pass the instance of logger to the function exported by each module.
A logger, which need to be initialized before used. What if I need to access it during module loading?
Make an extra module with an initialised logger:
// main.js
import {x as xxx} from './moduleX';
import {y as yyy} from './moduleY';
// logger_ready.js
import {logger} from '#my/logger';
logger.init({…});
export default logger;
// moduleX.js
import logger from 'logger_ready';
const log = logger('moduleX'); // logger is initialized here!
export function x() {
log.debug('some msg');
}
After experiments, found that the BroadcastChannel api is perfect to share data or events easily between different es6 modules, tabs, workers, frames...
We can pass objects or arrays by using json stringify there too:
To send datas:
new BroadcastChannel('myapp_section8').postMessage('Hello world!')
Example receiving, from another module:
new BroadcastChannel('myapp_section8').addEventListener('message', function(e){
console.log(e.data)
})
It avoid to use any DOM elements to dispatchEvent, it simplify stuffs a lot!

Categories