Define global variable before importing ES6 module - javascript

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)?

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
}

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 };

Related

100% ESM module world - what it mean?

While reading about tree shaking in webpack documentation, I came across this sentence:
In a 100% ESM module world, identifying side effects is straightforward. However, we aren't there just yet.
What do they mean by "100% ESM module" and how it is different from the current import and export that we already use today?
reference: https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free
The documentation you're reading is contrasting two types of scripts:
Scripts which expose everything they do through what they import and export
Scripts which do something in addition to importing and exporting (this could be seen as a "side effect")
Consider a big library, one that installs itself as a namespace on the global object to expose its functionality. Let's say that jQuery did this - that is, that it runs something like
const jQuery = (function() {
// lots and lots of code
})();
export default jQuery;
window.jQuery = jQuery;
This contains a side-effect: the line
window.jQuery = jQuery;
This means that other parts of your application could use window.jQuery without jQuery being specifically imported into that module. For example, you might have
// someModule.js
export const doStuff = () => {
window.jQuery('div').text('Hi!');
};
And this can work without the line
import jQuery from 'jQuery';
inside the module script, because jQuery is on the window. (For this to work, there needs to be at least one module somewhere that does import 'jQuery'; or something like that, so that jQuery's code that assigns itself to the window runs)
Because of the side-effect, Webpack will have a harder time with automatic tree-shaking - you'll have to explicitly note which modules depend on modules with side-effects.
In contrast, a module without dependency side-effects would be the someModule.js example above: all it does it export a function, without adding or changing functionality elsewhere.
So by "100% ESM module", Webpack is probably referring to scripts for which all modules' dependencies are explicit with import statements, instead of having to depend on side-effects (like a non-imported module assigning something to the window).
There are two popular module syntax nodejs use.
Commonjs: https://nodejs.org/dist/latest-v14.x/docs/api/modules.html
// exporting
module.exports.a = 1
// or, exports is an alias to module.exports, for all differences check out docs
exports.a = 1
// you can assign module.exports object as well, this sets what's exported
module.exports = {
b: 2
}
// a is not exported anymore
// importing default import, imports module.exports object
const a = require('./b')
// or via named import
const {c} = require('./b');
ES modules: https://nodejs.org/dist/latest-v14.x/docs/api/esm.html
// names export
export const a = 1;
// default export
export default const b = 2;
// importing via name
import {a} from './c'
// importing default export
import c from './b'
Commonjs and esm are still in use. So we are not in %100 esm world yet.

how do I export/import a variable in nodejs?

How do I export/import a variable in nodejs?
I've tried export and import but it says that its supposed to be a module and when I change the type to module in the json file it says that require is not defined.
with CommonJS modules (the Node.js default) you can export a variable by assigning to exports, and import it via require():
// bar.js
exports.variable = 'value';
// foo.js
const { variable } = require('./bar');
if you're using ECMAScript modules, the keywords import and export do these:
// bar.js
export const variable = 'value';
// foo.js
import { variable } from './bar';

js bundles and scope confusion

I'm trying to better understand scoping within a webpacked bundle with node components and other js.
Suppose my entry imports eight files to be bundled:
// entry point
import './components/file1';
import './components/file2';
...
import './components/file8';
And suppose in file1.js I have:
// file1.js
let bubbles = () => {
console.log('likes cats');
};
// or
function bubbles() {
console.log('likes cats');
}
Why then, if I have this in files8 (imported last), does it throw an undefined error? How do I call functions declared in other imports?
// file8.js
bubbles(); // fails in any file other than file1.js where it's declared.
You need to use export explicitly on functions/primitives you're intending to access from the outside:
https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export

How and why does `module.exports = {__dirname}` work?

How does the code from here work - and why? The object notation does not seem to be satisfied. I find it rather confusing. What does this refer to in the docs #Mozilla or #NodeJS ?
File expose.js:
module.exports = {__dirname};
File use.mjs:
import expose from './expose.js';
const {__dirname} = expose;
So shouldn't this actually work, importing only key from the object, in use.mjs? (which doesn't ... but above does!)
import {__dirname} from './expose.js';
edit: I usually do something like import electron, {ipcMain, session} from 'electron'; - importing the whole name space, then specific parts directly as needed. As well as import {inspect} from 'util';. So deconstruction should be in place here
edit 2: see this post, from the thread mentioned by willascend below. The No. part explains what the problem with the line in my question was.
This is just a simple object literal with the property name matching the value:
let ___test = 'this is a string... could be a path';
let object = {___test};
console.log(object);
Using module.exports, you've exported an object. When importing using the module system, you essentially have a module scoped object, expose. You then destructure that object's __dirname property when defining {__dirname}. I haven't tested it, but you should be able to just import __dirname as a named import like so (though you'd likely run into a conflict for the variable name):
import __dirname from './expose.js';
EDIT: Did some testing, when you do the above import, you get your exported object, so you would still need to destructure after importing (and of course, destructure into a new variable):
import __dirname from './expose.js';
const {__dirname: dirname} = __dirname;
console.log(dirname);
After some googling, it appears you cannot destructure an object when performing an ES6 import. The syntax appears similar to destructuring an object but it is not. What you are doing when you do the following is actually import variables that have been exported as named exports.
test.mjs
export const __dirname = 'this is a path';
test-import.mjs
import { __dirname } from './test.mjs';
console.log(__dirname);
Note that I didn't use export const __dirname = __dirname;, or even export const dirname = __dirname;. This is because node specific globals aren't available in .mjs files currently. So, in your example, expose.js is still a standard .js file which has access to both the module and __dirname globals. The __dirname value is then exported as a property of the default commonjs export object (i.e. module.exports = {__dirname}), which is then made available as the default export when importing in your use.mjs file. The expose variable is the module.exports object literal that was exported. Once it's been initialized as an imported variable, you can then destructure out the __dirname value.
Hope this helps.
These other sources were helpful for me in understanding this as well: Destructuring a default export object
https://github.com/babel/babel-loader/issues/194#issuecomment-168579479
https://github.com/nodejs/node/issues/16844#issuecomment-342245465
This is object shorthand in ES2015 (MDN).
{__dirname} is equal to {__dirname: __dirname}
For example:
var a = 'foo', b = 42, c = {};
var o = {a, b, c};
console.log(o);

Importing external dependencies in Typescript

I am a newbie in typescript/node. I have a typescript file "order.ts" from which I would like to import an external config dependency from "config.ts"
My config file code is as below
let config = {
mongoAddress: "mongodb://localhost:27017/dts",
dataDirectory: "../../data/"
};
module.exports = config;
I am importing the config file in the order file as below
import { config } from "../../config";
However I am getting the TS compiler throwing error "... file not a module". Any clues how I should I be importing my external dependency in typescript
The main part here is you want to export your object instance. You're on the right track with that, but there may be an easier way for you.
In this case something like wrapping it in a class and exporting that:
export class Config {
mongoAddress = 'mongodb://localhost:27017/dts';
dataDirectory = '../../data/';
}
Notice the export before class. The same can be applied to interfaces, enums etc. By exporting it this way you can then import it and initialise it:
import { Config } from '../config';
var c = new Config();
console.log(c.mongoAddress);
This will not make it a variable, like in your original example, but you'll simply wrap it in a class. This is also why you have to initialise it first using new Config().
Now, I'm assuming you want these properties simply to be accessed globally. And perhaps even static/readonly, so you don't have to initialise the class each time. Making use of the static typing of TypeScript, the sample would in this case be better refactored to something like this:
export class Config {
public static readonly mongoAddress: string = 'mongodb://localhost:27017/dts';
public static readonly dataDirectory: string = '../../data/';
}
With this, calling it is even less obtrusive - and very type safe:
console.log(Config.mongoAddress);
console.log(Config.dataDirectory);
Now exporting this way is just one of the options. It actually depends entirely on the library structure you're using throughout your application (or from third partie libraries, for that matter). It's a bit of dry reading, but I recommend you have a look at the different structures to get acquainted with terms like UMD and modules and how they relate to an import.
Hope this helps!
There are 2 ways you can do import and export.
1) default export
// config.ts
export const config = {
mongoAddress: "mongodb://localhost:27017/dts",
dataDirectory: "../../data/"
};
export default config;
// your other file
import configs from './config';
Note: Here you can give any name for the imported module;
2) normal export with exact declaration name while importing.
// config.ts
export const config = {
mongoAddress: "mongodb://localhost:27017/dts",
dataDirectory: "../../data/"
};
// your other file
import { config } from './config';
Note: Here you have to give the exact name of the module that you exported.
Best practices to follow while exporting configs.
create a static class with static variables in the code. Which likely means that these configs are fixed stuffs.
module.exports is the node syntax for exporting modules. Typescript has a keyword names export so you can just use this:
export const config = {
mongoAddress: "mongodb://localhost:27017/dts",
dataDirectory: "../../data/"
};

Categories

Resources