How can an es6 module import itself? - javascript

I have a module called fooModule. Inside this module, I import fooModule (itself):
import * as fooModule from './fooModule';
export function logFoo() {
console.log(fooModule)
}
When logFoo() is called, I can see all of the exports of the fooModule.
How does this work?

Circular dependencies are no problem for declarative imports/exports. In your case, the circle is of minimal length though :-)
The solution is that an import does not import a value into a variable, but that it makes a variable a reference to the exported variable. Have a look here for an example of a mutable variable, and at this question for exact terminology.
And it's the same for module namespace objects - their properties are just getters that resolve to the actual exported variable.
So when your module is loaded and evaluated, the following steps occur:
The source is statically analysed for export and import declarations to build a dependency graph
The module scope is created
Since the only dependency of your module is itself, and that already is getting initialised, it doesn't need to wait for it
The fooModule variable is created and instantiated to an object with the exported names of the module, which are known to be ["logFoo"]. The fooModule.logFoo property becomes a getter that will evaluate to the logFoo variable in the module scope (if you had used export {A as B}, then fooModule.B would resolve to A, but in your case both names are the same).
The variable declarations in the module scope create the variables, in your case logFoo, and function declarations are initialised (i.e. logFoo gets assigned the function)
The module code is run (in your case, nothing happens)
Now when you call logFoo in a module that imports it, fooModule will refer to the namespace that contains logFoo. No magic :-)

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.

How do we declare import types from NPM library that has no declaration files?

For example, if I have the following in my app,
import Node from 'infamous/motor/Node'
console.log(Node)
that works just fine. But the moment I actually do something with it,
import Node from 'infamous/motor/Node'
console.log(new Node)
then TypeScript will complain because there's no type definition for Node. How do I define the type of Node?
The library has no type declarations of it's own. I tried something like
import MotorNode from 'infamous/motor/Node'
declare class MotorNode {}
console.log(' --- ', new MotorNode)
but I get the error
./src/app.tsx(6,8): error TS2440: Import declaration conflicts with local declaration of 'MotorNode'
When I need to do what you are trying to do, I create an externals.d.ts file in which I put module augmentations for my project and make sure that my tsconfig.json includes it in the compilation.
In your case the augmentation might look something like this:
declare module "infamous/motor/Node" {
class Node {
// Whatever you need here...
}
export default Node;
}
I put it in a separate file because a module augmentation like this has to be global (must be outside any module), and a file that contains a a top-level import or export is a module. (See this comment from a TypeScript contributor.)

What does it mean by " the original exports object" in commonjs?

I was reading this article by Axel Rauschmayer, and specifically wondering about the following sentencs:
Node.js-style single-value exports don’t work. In Node.js, you can
export single values instead of objects, like this:
module.exports = function () { ... }
If you did that in module A, you wouldn’t be able to use the exported function in module B, because B’s variable a would still
refer to A’s original exports object.
what does it mean by "A’s original exports object"??
Each module in commonjs(node.js) exports a default exports object created by the module system which you can consider as an empty object (although it's not really empty).
The author called this default exports object an original exports object.

Import classes into the global scope

I am trying to import a class into the global scope, and I am able to do it, but then when I try to extend the class I get an error saying:
Type 'any' is not a constructor function type.
So this is what I am doing to get that error:
main.ts
const MyClass = require('./core/MyClass');
class MyTestClass extends MyClass {
}
I then tried using import MyClass from './core/MyClass', instead of a const, but that seems as if it is only within the scope of the current file, which doesn't help me either.
core/MyClass.ts
export default class MyClass {
}
I have tried using namespaces which works the way I want in typescript, but once compiled into javascript the namespace is undefined.
Is there a way I can import my class into the global scope and not get the error above when extending the class?
In your code you have :
require('./core/MyClass');
If you don't have import / export in your file then TypeScript assumes the file is global. However depending upon your usage of the file (e.g in NodeJS or if using a bundler like webpack) the file is still a module and not global.
Cool, with that out of the way you can put something on the global like:
export default class MyClass {
}
(global as any).MyClass = MyClass;
Be sure to include node.d.ts to get global.
More
And of course I would also like to warn against default as the const / require you wrote is also wrong. You need something like const {default} = require('module/foo');. More: https://basarat.gitbooks.io/typescript/content/docs/tips/defaultIsBad.html 🌹

Are ES6 module imports hoisted?

I know that in the new ES6 module syntax, the JavaScript engine will not have to evaluate the code to know about all the imports/exports, it will only parse it and “know” what to load.
This sounds like hoisting. Are the ES6 modules hoisted? And if so, will they all be loaded before running the code?
Is this code possible?
import myFunc1 from 'externalModule1';
myFunc2();
if (Math.random()>0.5) {
import myFunc2 from 'externalModule2';
}
After doing some more research, I've found:
Imports ARE hoisted! according to the spec of ModuleDeclarationInstantiation
ALL the dependent Modules will be loaded before running any code.
This code will have no errors, and will work:
localFunc();
import {myFunc1} from 'mymodule';
function localFunc() { // localFunc is hoisted
myFunc1();
}
It will be a SyntaxError. According to this part of specification:
Module :
ModuleBody
ModuleBody :
ModuleItemList
ModuleItemList :
ModuleItem
ModuleItemList ModuleItem
ModuleItem :
ImportDeclaration
ExportDeclaration
StatementListItem
It means that module can contain only ImportDeclaration's, ExportDeclaration's or StatementListItem's.
According to this StatementListItem could
not contain ImportDeclaration nor ExportDeclaration.
import myFunc1 from 'externalModule1';
is an import declaration, while:
if (Math.random()>0.5) {
import myFunc2 from 'externalModule2';
}
is a statement. So your code will result to a syntax error.
What about "will they all be loaded before running the code?". This part of specification contain next sentence:
NOTE: Before instantiating a module, all of the modules it requested must be available.
So, yeah. They will all be loaded before running the code.
ES6 specification is a subject to change but this draft is explicit:
The static variable resolution and linking pass checks for conflicts
in imported variable names. If there is a conflict between two
imported names, or an imported name and another local binding, then it
is a compile-time error.
And trying to import at runtime is doubtful idea, not only in ES6. Also from the draft:
Compilation resolves and validates all variable definitions and
references. Linking also happens at compile-time; linking resolves and
validates all module imports and exports.
You can see that Babel's ES6 implementation isn't too happy with it.

Categories

Resources