js bundles and scope confusion - javascript

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

Related

Define global variable before importing ES6 module

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

Import-Export Call Order in NodeJS

This is a "code theory" question.
So imagine this scenario, I am using the global namespace in a package. I have a main entrypoint file, some class files that I export, and some utility files the classes use. Here's a theoretical file structure:
/index.js (main entrypoint)
/src
|_/source.js (exporting function for src folder)
|_/utils
|_/headers.js
|_/constants.js
|_/classes
|_/class1.js
|_/class2.js
In constants.js I define some global variables and then export an object that uses some global variables.
/src/utils/constants.js
const foobar = 'foo';
global.foobar = foobar;
export default {
foo: global.foobar,
}
In some class file I import constants.js.
/src/classes/class1.js
import constants from '../utils/constants.js'
export default class Xyzzy {
function baz() {
return constants.foo + 'baz'
}
}
And finally in the entrypoint file I import source.js which imports /src/classes/class1 and /src/classes/class2 and exports both of them, then define some global variables.
/index.js
import source from '/src/source.js'
export default function index() {
global.foobar = 'bar'
return {
class1: source.class1,
class2: source.class2,
}
}
What would be the assignment order of global.foobar, what is the final result, and most importantly why does this happen?
Thanks!
From https://hacks.mozilla.org/2015/08/es6-in-depth-modules/:
When you run a module containing an import declaration, the modules it imports are loaded first, then each module body is executed in a depth-first traversal of the dependency graph, avoiding cycles by skipping anything already executed.
So in your case:
The entry point module index.js is loaded, and depends on source.js
The source.js module is loaded, and depends on class1.js and class2.js
The class1.js module is loaded, and depends on constants.js
The constants.js module is loaded, and depends on nothing, so it is evaluted. This assigns 'foo' to the foobar variable.
Now that the dependencies of class1.js are satisfied, it is executed.
(Assuming class2.js looks similar to class1.js), the class2.js module is laoded, and depends on constants.js. This is already initialised, so class2.js is executed.
Now that the dependencies of source.js are satisfied, it is executed.
Now that the dependencies of index.js are satisfied, it is executed. This assigns 'bar' to the foobar variable.
Of course, the entire purpose of modules is that you should need to reason about this, when you don't use global variables :-)
import declaration works like require-once or lazy-require. A module is evaluated once it is imported: further imports won't evaluate it again.
So, in regards to how foobar mutates, according to your imports, the following should happen, in order:
src/utils/constants.js sets global.foobar = 'foo'
index.js sets global.foobar = 'bar'
The entry code does nothing, it only exports a function as a property default.

Importing all exports in a module NodeJS

I want to be able to access all exports of a module without having to say module. before the export.
Let's say that I have a module:
// mymod.js
module.exports.foo = function() {
console.log("foo!");
}
module.exports.bar = "bar!";
And a main file:
// main.js
var mymod = require("./mymod.js");
mymod.foo();
Is there a way to call foo() without needing to say mymod. before? This can be achieved in python by saying import module as *.
What is the NodeJS equivalent to this?
In ES6 you can import modules in the following ways
import moduleName from "path/to/module"; // import default export from the file as moduleName object, moduleName can be anything
import { exportMemberName1, exportMemberName2, ... } from "path/to/module"; // destructured import, it will destructure import and can access the export module without prefixing anything
import * as moduleName from "path/to/module"; // import everything exported from the file as moduleName object, you can access every export members from that object, moduleName can be anything
These are the only methods provided by ES6 to import module (you can also use require).
If you have to import 100s of modules best ways is first method, import everything as an object and destructure on the go, I meant if you have lots of functions or methods, destructure what you want in that function in side that function, eg.
import * as moduleName from "path/to/file";
function function1(){
const { exportMember1, exportMember2 } = module;
}
function function2(){
const { exportMember1, exportMember5, exportMember7 } = module;
}
I want to be able to access all exports of a module without having to
say module. before the export.
Use the shorthand:
exports.myVar = myVar
exports.foo = () => {}
Or use an Object:
module.exports = {
foo,
myVar
}
// main.js
var mymod = require("./mymod.js");
mymod.foo();
Is there a way to call foo() without needing to say mymod. before?
This can be achieved in python by saying import module as *. What is
the NodeJS equivalent to this?
Use destructuring:
const { foo } = require("./mymod.js")
lets say that I have 100 exports in a file. Do I need to put commas
after every import inside the { }? There must be a better way to do
this
If you have 100 exports why would you want to import them all globally as their own functions? myMod.func is better for clarity.
A hacky workaround might be to do const myMod = require('myMod') then map it putting the functions on the global object. Or put them on the global from the start instead of exporting it.
You can use ES6 destructuring:
var { foo } = require("./mymod.js");
foo();
I have a situation where a I have a tiny-but-not-that-tiny generic utilities that is used along a couple of modules (all it's functions are used), in which there is a decent amount of modules already loaded. This functions are obviously named in a way you know there are a part of a generic utilities modules, so the "module.function" it's redundant, does not improve the readeability of the code. So, I prefered to mimick the "import * from module" of Python. Note that this is the first time I come across this situation, therefore, IMO, this mechanism, in almost every case, is not a good practice at all. The only way to do that, is iterating over the exports of the module, and adding the functions to the global object. I made a function to make the intention clear.
const importAll = () => {
return {
mod: null,
from(modName) {
this.mod = require(modName);
Object.keys(this.mod)
.forEach(exportedElementId => global[exportedElementId] = this.mod[exportedElementId]);
}
}
}
And it is used like this:
importAll().from('module-name');
Note that this only works if the module exports an object. Wont work if the module exports, for example, an array.
Here is another way, which may be a bit cleaner and more convenient in some cases: method importAll() is implemented inside export-heavy module, so it might be called immediately after require(), making this call very brief.
This works really well for large modules stuffed with simple standard functions and constants that are used across multiple projects.
Example:
// module.js
'use strict';
function func1() { return '4'; };
function func2() { return 2; };
function importAll() { delete this.importAll; Object.assign(global, this); };
module.exports = { func1, func2, importAll };
Then, in the main app, module can be unwrapped as follows:
// app.js
'use strict';
require('./module').importAll();
console.log("result: '%d'", func1() + func2());
There are few caveats though:
since properties/methods are added to global object, those might override some existing properties/methods, so be careful with naming.
those properties/methods will become available EVERYWHERE: in all modules, sub-modules, etc, so no need to call require() more than once.

Webpack global variable from multiple files

I'm trying to rewrite bunch of legacy JS files into module structure. I have obfuscated plugin which contains out of few files, which in turn work with single global variable. The order of execution of these files matters.
Example:
file1.js
var myModule = {someStuff};
file2.js
myModule.someProperty = someValue;
What i want to achieve is to import them all somehow and get this global variable myModule.
Possible implementation:
myModule.js
import myModule from "file1.js";
import myModule from "file2.js"; // ofc i know it does not work this way
export default class myProgramm {
constructor(){
myModule.run({options});
}
}
What i tried so far is webpack provide plugin (https://webpack.js.org/plugins/provide-plugin/), but it doesn't work with multiple files. Also i tried to use provide-multiple-plugin (adopted to webpack 4) from this gist :https://gist.github.com/shellscape/a7461022503f019598be93a512a1901a. But it seems to include files in nearly random order, so it can happen that myModule is not defined, while file2.js is executed first.

ES6 import "module" don't give acces to function of "module"

I am trying to use ES6 imorts with babel and Node.js
import "./utils.js";
log("something"); //throw an error : log is not defined
My utils.js look like this :
function log(... params){
console.log(... params);
}
log("module utils executed");
//Is executed and display "module utils executed" in the console.
I have also tryed to use export function log(... params) and export default log(... params) but it doesn't works.
So I don't understand how this is suppose to works...
EDIT:
I know that an other way to import is to do import utils from "./utils.js"
But it's not what I want. I want to be able to use log() without prefixing it with the module variable name. Like in this blog post
there Different ES6 import and Node.js require Question Describe The Difference
In case you will use Node.js require:
your utils.js File will be
function log(params) {
console.log(params);
}
module.exports = {
log
};
The other File will import your Utils Module will be
var utils = require('./utils');
utils.log("test");
In case you will use ES6 Modules:
your utils.js File will be
var log = function (params) {
console.log(params);
}
var utils = {
log: log
}
export default utils;
The other File will import your Utils Module will be
import utils from 'utils';
utils.log("test");
UPDATE
According to your Comment, Yes you can do this But using ES6 Module
your utils.js File will be
function log(params) {
console.log(params);
}
function anotherLog(params) {
console.log(params);
}
export { log, anotherLog }
The other File will import your Utils Module will be
import { log } from 'utils';
log("test");
No, there is no way to import all exported members of a module into the current namespace. Importing a module for side effects (i.e. import 'utils') does nothing with the members of utils.
The closest you can get is something like this:
utils.js
export function log(...params) { ... }
export function foo(a) { ... }
main.js
import * as u from './utils';
u.log(1, 2, 3);
u.foo(4);
or
import { log, foo } from './utils';
log(1, 2, 3);
foo(4);
One of the design goals of the ES6 module spec is a static module structure, which allows resolution of imports at compile time (before executing anything). Allowing blanket imports would make static analysis more difficult.
EDIT (do not do this!)
As #Bergi pointed out in the comments, you can add functions to the global namespace as follows:
utils.js
function log(...params) { ... }
global.log = log;
main.js
import './utils'; // import for side effects, add properties to the global object
log(1, 2, 3); // the global object is in the scope chain, so this is resolved
However this is a bad idea. Global variables are slow to resolve and in general break the modularity you try to achieve by using modules in the first place.

Categories

Resources