Import-Export Call Order in NodeJS - javascript

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.

Related

Does an imported file inherit the scope of its importer?

When a file is imported in JavaScript, does that action cause the scope of variables in the imported file become that of the importer file, thus negating the necessity to add import declarations in the second imported file since they are declared in the importer file?
For example, if this is loaded first:
/* Config.js */
import { ImportantVar, OkayVar, LastVar } from 'first/file/path.js';
import DataObject from 'second/file/path.js';
and then this is loaded:
/* Second File */
export const DataObject = function( param1, param2 ) {
return new ImportantVar( param1, param2 );
}
Should this line be added to the top of the second file?
import ImportantVar from 'first/file/path.js';
I don't care if the second file is broken should that it be run independently, i.e. not as an import from config.js.
No. Every module has its own scope.
You're not "importing a file" or "importing the code to be run", you are importing the exported bindings of the module; the module stands on its own, and its exports can be imported in multiple places (while the code is only evaluated once). Inheriting scope wouldn't work with that.
So yes, you'll need to import { ImportantVar } from 'first/file/path.js' in your example.

Navigate ES6 throught cTags

My .cTags is working fine in mostly cases. However in a scenario mentioned below it's not working as expected.
Directory Structure
root
- a
- foo.js
- bar.js
- index.js
- b
- current-file.js
current-file.js
import { foo } from './a'
foo()
index.js
export { default as foo } from './foo'
foo.js
const foo = () => 'foo'
export default foo
When i am trying to jump in definition of foo from current-file.js its navigating to a/index.js instead of a/foo.js
I think this is not a limitation about the cTags, but about the ES6 language itself.
The curly braces import will first lookup for the file named a, if not found, it will look for the directory named 'a' with its index.js file (auto-resolved, e.g: a/index.js). It won't look for a file into a directory.
The curly braces are only looking for the non-default exports from a file, and it's not looking through directories.
Example (NOTE: the directory a has been renamed to c as codesandbox reserved the name a in his engine)
You can try to remove the /c.js file to load the c/index.js file instead.
Also, I suggest you this thread which can give you more tips on curly brackets in js
You might be able to do a read through a directory with the fs.readDir api and automatically require/export the files in your index.js; But the index.js will be loaded.

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

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.

Using a backbone.d.ts

I can't seem to get any import to work when the module isn't defined as a string. What is going on?
test.ts
import b = module('Backbone')
Does not work:
backbone.d.ts
declare module Backbone {
export class Events {
...
Works:
backbone.d.ts
declare module "Backbone" {
export class Events {
...
Edit 1:
FYI From 10.1.4
An AmbientModuleIdentification with a StringLiteral declares an external module. This type of declaration is permitted only in the global module. The StringLiteral must specify a top-level external module name. Relative external module names are not permitted
I don't understand how it's useful to not specify it as the string literal format as found here and here. It works if you use ///<reference... without a string literal module but I'm trying to generate AMD modules that depend on these libraries so I need the import to work. Am I the minority and I have to go and modify each .d.ts to be the string literal version?
Edit 2:
Declaring a module using a string literal requires that your import is an exact match if that string literal. You can no longer use relative or absolute paths to the location of this module/module definition even if it is not located in the same directory as the file trying to import it. This makes it that a ///<reference and an import are required but produces a module with tsc --module AMD that was exactly looking for (path to module is as dictated by the module string literal "Backbone").
For example.
+- dep/
|- backbone.d.ts
|- test.ts
backbone.d.ts:
declare module "Backbone" {
export class Events {
Works: test.ts:
///<reference path="../dep/backbone.d.ts" />
import b = module('Backbone')
// generates
// define(["require", "exports", 'Backbone']
Does not work: test.ts:
import b = module('./dep/Backbone')
Note that this works as well...
declare module "libs/Backbone" {
...
///<reference path="dep/backbone.d.ts" />
import b = module('libs/Backbone')
...
// generates
define(["require", "exports", 'libs/Backbone']
When you write this:
declare module Backbone {
It means you have already a module which is in the global scope (so you can immeadiately use it, no import is required). But when you write this:
declare module "Backbone" {
It means you specify how the imported module (import ... = module("...")) will look.

Categories

Resources