Are ES6 module imports hoisted? - javascript

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.

Related

Correct way to export/define functions in Electron's Renderer

I have a JS file that I'm importing into my Electron's "main" (or background process), app.js, using require (eg: const myJS = require("./pathToMyJS/myJS");)
Contents of myJS.js:
module.exports = {
mFunc: function mFunc(param1) {
...
}
};
And I can use mFunc in app.js as myJS.mFunc(param1); & everything's great.
Then, I tried to follow the same process for the "renderer" JS. So my renderer.js now imports const myOtherJS = require("./myJS/myOtherJS"); where this other JS file follows the exact same module.exports logic as myJS.
And the root HTML (app.html) declares the renderer as <script defer src="./renderer/renderer.js"></script>.
But on launch, I get:
Uncaught TypeError: Cannot set property 'exports' of undefined
at renderer.js? [sm]:34
Searching online, I came across this answer that mentions that the AMD way could be used instead of the commonJS way. So I tried the following: (not sure whether this is syntactically correct!)
define(
["renderer"],
function rFunc(param1) {
... }
)
But that fails with:
Uncaught ReferenceError: define is not defined
So what's the correct way to have functions defined for export when using them in the renderer? What I've been doing so far is just to write the functions in their own JS files (eg: function func1() { ...}) & declaring all of these files in the app.html as <script defer src="./funcFile1.js"></script>.
Turns out, I was just exporting incorrectly. modules.export was the point of failure as modules is undefined on the renderer.
Instead, if I do the following to export individual functions:
// ./myJS/myOtherJS.js
export function rFunc() { ...}
And then import into my renderer.js like:
import { rFunc } from './myJS/myOtherJS';
rFunc();
Things work as I originally expected.
This Google Developers Primer on modules was useful in understanding the concepts.
AMD is not provided by node.js by default. It's used by Require.js and other FWs. Here is a link on how you can use it with node:
https://requirejs.org/docs/node.html

Accessing Window Object from .tsx file

Typically in my .ts files I can access the window object by calling something such as:
(<any>window).myObject
I am getting compilation errors for this in my .tsx files. Is there any way I can access it from a .tsx file?
Thanks.
You can use the as syntax for type assertion. This is the alternate syntax for type assertion as <type>obj conflicts with JSX syntax:
(window as any).myObject
The above will work, however if you want strong typing consider augmenting the Window interface to add your property so you will get compile-time type checking:
declare global {
interface Window {
myObject: YourObjectType;
}
}
The syntax <type> is being deprecated by the ts team. This is because there is too much ambiguity between it and the new jsx syntax. Instead the ts team introduced the as operator for type assertions.
So this syntax:
(window as any).things
is more update to date.
This change was done, because basically, its very tough to tell a compiler when is such a syntax relating to a type or a jsx-element. Likewise the notation becomes much harder to read (see below for example):
<Component>
{<String>something}
</Component>
Some more detail can be found here https://basarat.gitbooks.io/typescript/docs/types/type-assertion.html#as-foo-vs-foo and here https://github.com/Microsoft/TypeScript/issues/296
Further Explanation on #Saravana Answer,
best way is to have this defined in your types folder, and add a definition file with .d.ts extention, i.e: window.d.ts and place your extended definitions for the window object, as typescript global augmentation typically offers merging interfaces.
declare global {
interface Window {
myObject: YourObjectType;
}
}
// but make sure to export that as default so Typescript will consider it automatically on the project
export default global;

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

How can an es6 module import itself?

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

How to import a js library without definition file in typescript file

I want to switch from JavaScript to TypeScript to help with code management as our project gets larger. We utilize, however, lots of libraries as amd Modules, which we do not want to convert to TypeScript.
We still want to import them into TypeScript files, but we also do not want to generate definition files. How can we achieve that?
e.g. The new Typescript file:
/// <reference path="../../../../definetelyTyped/jquery.d.ts" />
/// <reference path="../../../../definetelyTyped/require.d.ts" />
import $ = require('jquery');
import alert = require('lib/errorInfoHandler');
Here, lib/errorInfoHandler is an amd module included in a huge JavaScript library that we do not want to touch.
Using the above code produces the following errors:
Unable to resolve external module ''lib/errorInfoHandler''
Module cannot be aliased to a non-module type.
This should actually produce the following code:
define(["require", "exports", "jquery", "lib/errorInfoHandler"], function(require, exports, $, alert) {
...
}
Is there a way to import a JavaScript library into TypeScript as an amd Module and use it inside the TypeScript file without making a definition file?
A combination of the 2 answers given here worked for me.
//errorInfoHandler.d.ts
declare module "lib/errorInfoHandler" {
var noTypeInfoYet: any; // any var name here really
export = noTypeInfoYet;
}
I'm still new to TypeScript but it looks as if this is just a way to tell TypeScript to leave off by exporting a dummy variable with no type information on it.
EDIT
It has been noted in the comments for this answer that you can achieve the same result by simply declaring:
//errorInfoHandler.d.ts
declare module "*";
See the github comment here.
Either create your own definition file with following content:
declare module "lib/errorInfoHandler" {}
And reference this file where you want to use the import.
Or add the following line to the top of your file:
/// <amd-dependency path="lib/errorInfoHandler">
Note: I do not know if the latter still works, it's how I initially worked with missing AMD dependencies. Please also note that with this approach you will not have IntelliSense for that file.
Create a file in lib called errorInfoHandler.d.ts. There, write:
var noTypeInfoYet: any; // any var name here really
export = noTypeInfoYet;
Now the alert import will succeed and be of type any.
Typically if you just want to need a temporary-faster-solution, that could be done by defining a new index.d.ts in the root of the project folder, then make a module name like described inside package.json file
for example
// somefile.ts
import Foo from '#awesome/my-module'
// index.d.ts on #awesome/my-module
declare module '#awesome/my-module' {
const bind: any;
export default bind;
}
Ran into that that problem in 2020, and found an easy solution:
Create a decs.d.ts file in the root of your TS project.
Place this declaration:
declare module 'lib/errorInfoHandler';
This eliminates the error in my case. I'm using TypeScript 3.9.7

Categories

Resources