I have recently switched from CommonJS to ES6 modules in my NodeJS project. One of the challenge I'm facing is to define a global variable before I import one of my module. I used to do that with CommonJS in my main file:
const path = require('path');
global.appRoot = path.resolve(__dirname);
const myObj = require('./my-object-file');
where my my-object-file
uses global.appRoot
.
With ES6, I have tried the following:
import path from 'path';
global.appRoot = path.resolve(path.resolve());
import myObj from './my-object-file';
with my-object-file.js
being:
export default {
root: global.appRoot
}
But I get undefined for global.appRoot
in my-object-file.js
.
What is going on here?
Are import modules called before anything in my code?
How can I solve this (knowing that I absolutely want to be able to define the path as a global variable accessible in my modules)?
CodePudding user response:
Are import modules called before anything in my code?
Yes, all module imports are resolved before any code in the importing module runs.
However, imported modules are also executed in order, so if you do
import './setupGlobals';
import myObj from './my-object-file';
then the setupGlobals module code is executed before the my-object-file one. So it will work when do
// setupGlobals.mjs
import path from 'path';
global.appRoot = path.resolve(path.resolve());
I absolutely want to be able to define the path as a global variable accessible in my modules
No, you really don't want to do that. Instead of a global variable that might be created anywhere, explicitly declare your dependency!
If you have a separate module to define your globals anyways, just make that export
those variables instead of putting the values on the global
object:
// globals.mjs
import path from 'path';
const appRoot = path.resolve(path.resolve());
export { appRoot as default }
Then you can declaratively use this global constant in any of your modules:
// my-object-file.js:
import appRoot from './globals';
export default {
root: appRoot
}
CodePudding user response:
I like Bergi's answer with:
import appRoot from './globals';
Then, any file that wants to can get access to appRoot
and you preserve modularity.
But, in addition to that approach, my suggestion in comments was that rather than setting a global before you import, you export a function from your module and call that function, passing it the desired path. This is a general purpose way of passing initialization parameters to a module from the parent module without using globals.
And, I suggest you use the import.meta.url
work-around for creating the equivalent of __dirname
as outline here. What you were doing with path.resolve()
is just getting you the current working directory, which is not necessarily __dirname
as it depends upon how this module was loaded for whether they are the same or not. Besides if you just wanted the equivalent of cwd, you could just use process.cwd()
anyway in your child module. Here's the equivalent for __filename
and __dirname
in an ESM module.
// create __dirname and __filename equivalents in ESM module
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Then, import the module initialization function (sometimes called a module constructor) and call the module constructor:
import myObjConstructor from './my-object-file'; const myObj = myObjConstructor(__dirname);
Then, inside of my-object-file, you export a function and when that function is called, you initialize your module using the passed in __dirname
and return the myObj
.
So, inside of my-object-file
:
function init(__dirname) {
// do whatever you want for module initialization
// using __dirname
return yourObj;
}
export { init as default };