ES6: Conditional & Dynamic Import Statements - javascript

Conditional
Is it possible to have conditional import statements like below?
if (foo === bar) {
import Baz from './Baz';
}
I have tried the above but get the following error (from Babel) when compiling.
'import' and 'export' may only appear at the top level
Dynamic
Is it possible to have dynamic import statements like below?
for (let foo in bar) {
if (bar.hasOwnProperty(foo)) {
import Baz from `./${foo}`;
}
}
The above receives the same error from Babel whilst compiling.
Is this possible to do or is there something I am missing?
Reasoning
The reason I am trying to do this is that I have a lot of imports for a number of "pages" and they follow a similar pattern. I would like to clean up my code base by importing these files with a dynamic for loop.
If this is not possible then is there a better way to handle large number of imports in ES6?

We do have dynamic imports proposal now with ECMA. This is in stage 2. This is also available as babel-preset.
Following is way to do conditional rendering as per your case.
if (foo === bar) {
import('./Baz')
.then((Baz) => {
console.log(Baz.Baz);
});
}
This basically returns a promise. Resolution of promise is expected to have the module. The proposal also has things like multiple dynamic imports, default imports, js file import etc. You can find more information about dynamic imports here.

You can't resolve dynamically your dependencies, as imports are meant for static analysis. However, you can probably use some require here, something like:
for (let foo in bar) {
if (bar.hasOwnProperty(foo)) {
const Baz = require(foo).Baz;
}
}

As this question is highly-ranked by Google, it is worth pointing out that things have changed since the older answers were posted.
MDN has this entry under 'Dynamic Imports':
The import keyword may be called as a function to dynamically import a
module. When used this way, it returns a promise.
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});
This form also supports the await keyword.
let module = await import('/modules/my-module.js');
MDN also has a more detailed explanation.
A useful article on the subject can be found on Medium.

Since 2016 a lot has passed in JavaScript world, so I believe it's time to offer most updated info on this topic. Currently Dynamic imports are a reality both on Node and on browsers (natively if you don't care about IE, or with #babel/plugin-syntax-dynamic-import if you do care).
So, consider a sample module something.js with two named exports and one default export:
export const hi = (name) => console.log(`Hi, ${name}!`)
export const bye = (name) => console.log(`Bye, ${name}!`)
export default () => console.log('Hello World!')
We can use import() syntax to easily and cleanly load it conditionally:
if (somethingIsTrue) {
import('./something.js').then((module) => {
// Use the module the way you want, as:
module.hi('Erick') // Named export
module.bye('Erick') // Named export
module.default() // Default export
})
}
But since the return is a Promise, the async/await syntactic sugar is also possible:
async imAsyncFunction () {
if (somethingIsTrue) {
const module = await import('./something.js')
module.hi('Erick')
}
}
Now think about the possibilities along with Object Destructuring Assignment! For example, we are able to easily put only one of those named exports in memory for posterior use:
const { bye } = await import('./something.js')
bye('Erick')
Or maybe grab one of those named exports and rename it to anything else we want:
const { hi: hello } = await import('./something.js')
hello('Erick')
Or even rename the default exported function to something that makes more sense:
const { default: helloWorld } = await import('./something.js')
helloWorld()
Just a last (but no least) note: import() may looks like a function call, but it isn't a Function. It's a special syntax that just happens to use parentheses (similar to what happens with super()). So it's not possible to assign import to a variable or use things of the Function prototype, like call/apply.

Require will not solve your problem as it is a synchronous call. There are several options and they all involve
Asking for the module you need
Waiting for a promise to return the module
In ECMA Script there is support for lazy loading modules using SystemJS. This of course isn't supported in all browsers, so in the meantime you can use JSPM or a SystemJS shim.
https://github.com/ModuleLoader/es6-module-loader

Related

js import via building the string [duplicate]

Conditional
Is it possible to have conditional import statements like below?
if (foo === bar) {
import Baz from './Baz';
}
I have tried the above but get the following error (from Babel) when compiling.
'import' and 'export' may only appear at the top level
Dynamic
Is it possible to have dynamic import statements like below?
for (let foo in bar) {
if (bar.hasOwnProperty(foo)) {
import Baz from `./${foo}`;
}
}
The above receives the same error from Babel whilst compiling.
Is this possible to do or is there something I am missing?
Reasoning
The reason I am trying to do this is that I have a lot of imports for a number of "pages" and they follow a similar pattern. I would like to clean up my code base by importing these files with a dynamic for loop.
If this is not possible then is there a better way to handle large number of imports in ES6?
We do have dynamic imports proposal now with ECMA. This is in stage 2. This is also available as babel-preset.
Following is way to do conditional rendering as per your case.
if (foo === bar) {
import('./Baz')
.then((Baz) => {
console.log(Baz.Baz);
});
}
This basically returns a promise. Resolution of promise is expected to have the module. The proposal also has things like multiple dynamic imports, default imports, js file import etc. You can find more information about dynamic imports here.
You can't resolve dynamically your dependencies, as imports are meant for static analysis. However, you can probably use some require here, something like:
for (let foo in bar) {
if (bar.hasOwnProperty(foo)) {
const Baz = require(foo).Baz;
}
}
As this question is highly-ranked by Google, it is worth pointing out that things have changed since the older answers were posted.
MDN has this entry under 'Dynamic Imports':
The import keyword may be called as a function to dynamically import a
module. When used this way, it returns a promise.
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});
This form also supports the await keyword.
let module = await import('/modules/my-module.js');
MDN also has a more detailed explanation.
A useful article on the subject can be found on Medium.
Since 2016 a lot has passed in JavaScript world, so I believe it's time to offer most updated info on this topic. Currently Dynamic imports are a reality both on Node and on browsers (natively if you don't care about IE, or with #babel/plugin-syntax-dynamic-import if you do care).
So, consider a sample module something.js with two named exports and one default export:
export const hi = (name) => console.log(`Hi, ${name}!`)
export const bye = (name) => console.log(`Bye, ${name}!`)
export default () => console.log('Hello World!')
We can use import() syntax to easily and cleanly load it conditionally:
if (somethingIsTrue) {
import('./something.js').then((module) => {
// Use the module the way you want, as:
module.hi('Erick') // Named export
module.bye('Erick') // Named export
module.default() // Default export
})
}
But since the return is a Promise, the async/await syntactic sugar is also possible:
async imAsyncFunction () {
if (somethingIsTrue) {
const module = await import('./something.js')
module.hi('Erick')
}
}
Now think about the possibilities along with Object Destructuring Assignment! For example, we are able to easily put only one of those named exports in memory for posterior use:
const { bye } = await import('./something.js')
bye('Erick')
Or maybe grab one of those named exports and rename it to anything else we want:
const { hi: hello } = await import('./something.js')
hello('Erick')
Or even rename the default exported function to something that makes more sense:
const { default: helloWorld } = await import('./something.js')
helloWorld()
Just a last (but no least) note: import() may looks like a function call, but it isn't a Function. It's a special syntax that just happens to use parentheses (similar to what happens with super()). So it's not possible to assign import to a variable or use things of the Function prototype, like call/apply.
Require will not solve your problem as it is a synchronous call. There are several options and they all involve
Asking for the module you need
Waiting for a promise to return the module
In ECMA Script there is support for lazy loading modules using SystemJS. This of course isn't supported in all browsers, so in the meantime you can use JSPM or a SystemJS shim.
https://github.com/ModuleLoader/es6-module-loader

How to optionally import package in TypeScript? [duplicate]

TypeScripts abstracts away module imports/exports in sort of 'declarative' manner.
But what if I want to import or export something based on some runtime-computed condition?
The most common use case is sharing code between platforms like Node.js and Windows Script Host.
TypeScript's very own io.ts that abstracts input/output in TSC compiler manually hacks around the built-in TypeScript's very own module syntax. Is that the only way?
P.S. The problem with just sticking import fs = module("fs") into if statement is that TypeScript only allows import statements at a top level. Which means in WSH require("fs") will be executed and obviously failing, as require is undefined.
From TypeScript v2.4 you can use dynamic import to achieve conditional importing
An async example:
async function importModule(moduleName: string):Promise<any>{
console.log("importing ", moduleName);
const importedModule = await import(moduleName);
console.log("\timported ...");
return importedModule;
}
let moduleName:string = "module-a";
let importedModule = await importModule(moduleName);
console.log("importedModule", importedModule);
I have a slightly clunky but very effective solution for this, particularly if you're using conditional import/export for unit testing.
Have an export that is always emitted, but make the contents vary based on a runtime value. E.g.:
// outputModule.ts
export const priv = (process.env.BUILD_MODE === 'test')
? { hydrateRecords, fillBlanks, extractHeaders }
: null
Then in the consuming file, import the export, check that the imported value exists, and if it does, assign all the values you'd otherwise import stand-alone to a set of variables:
// importingModule.spec.ts
import { priv } from './outputModule';
const { hydrateRecords, fillBlanks, extractHeaders } = priv as any;
// these will exist if environment var BUILD_MODE==='test'
Limitations:
You sadly have to set the import to 'any' to make the compiler happy.
You need to check for whether or not specific imports are defined (but that comes with the territory).
The importing file will expect the values to be defined. You will thus have to ensure importing files actually need the modules (which is fine if you're e.g. dealing with files only run during testing), or you'll have to define alternative values for cases where they don't actually get exported.
Still, it worked really well for me for my purposes, hopefully it works for you too. It's particularly useful for unit testing private methods.
I agree that the fact that they can only have toplevel scope is suboptimal at best. Besides the issue you stated, it also means slower initial load times of software. For example within nodejs I now sometimes load a module in a function if that function is seldom used. So my application starts up quicker since it doesn't load that module yet.
And of course you could use require or AMD directly, but than you will miss some of the typing benefits.
I think however that the real problem lies in the fact that harmony/es6 defined modules to be toplevel and TS seems to be following that proposal. So not sure how much TS team can do without diverging from the standards.
import { something } from '../here';
import { anything } from '../there';
export const conditionalExport =
process.env.NODE_ENV === 'production' ? something : anything;
Inspiration from Andrew answer.
There is a mechanism for dynamic imports in TypeScript, although the implementation differs based on the module kind.
The example below (for AMD) will conditionally load the module:
declare function require(moduleNames: string[], onLoad: (...args: any[]) => void): void;
import * as ModuleAlias from './mod';
const someCondition = true;
if (someCondition) {
require(["./mod"], (module: typeof ModuleAlias) => {
console.log(module.go());
});
}
The import statement at the top of the file is inert, and the actual loading of the module will not happen unless the condition if (someCondition) is true.
You can test this by changing someCondition and seeing the impact on your network tab, or you can look at the generated code... in the dynamic version, "./mod" does not appear in the define call. In the non dynamic one, it does.
With Dynamic Loading
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const someCondition = true;
if (someCondition) {
require(["./mod"], (module) => {
console.log(module.go());
});
}
});
Without Dynamic Loading
define(["require", "exports", "./mod"], function (require, exports, ModuleAlias) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const someCondition = true;
if (someCondition) {
console.log(ModuleAlias.go());
}
});
Could not find a direct way to do conditional exports as conditional imports. But
I found Andrew Faulkner's answer useful but I'm not happy with the limitations.
You sadly have to set the import to 'any' to make the compiler happy.
I came up with a work around for the above limitation. Here's my steps.
Write conditional exports as one object as in Andrew's answer.
Import the exported object in another module.
De-structure it.
Define new constants by assigning all de-structured items giving the proper type.
Here's the example.
//CryptoUtil.ts
function encryptData(data : Buffer, key : Buffer) : Buffer {
// My encryption mechanism.
// I return a Buffer here.
}
function decryptData(data : Buffer, key : Buffer) : Buffer {
// My decryption mechanism.
// I return a Buffer here.
}
// Step 1
// Exporting things conditionally
export const _private = (process.env.NODE_ENV === "test") ? {
__encryptData : encryptData,
__decryptData : decryptData,
} : null;
Notice how I export encryptData as __encryptData instead of directly exporting as encryptData. I did that just because I can give the idea that __encryptData is a private function when de-structuring in the importer module. It's totally my preference.
Then when importing things...
// CryptoUtil.test.ts
// Step 2
// import the exported object.
import { _private } from "./CryptoUtil";
// Step 3. De-structuring.
const {
__encryptData,
__decryptData,
} = _private as any;
// Step 4. Define new constants giving proper type.
const _encryptData : (data : string, password : Buffer) => Buffer = __encryptData;
const _decryptData : (encryptedData : Buffer, password : Buffer) => Buffer = __decryptData;
// Now I can use _encryptData, _decryptData having the proper type.
Even though I propose this way to andrew's first limitation, my approach introduces a new limitation. That is, you have to define the type in two places. When you change the type of export function, it won't magically change the type of imported function. You've to change it manually.

ES6 import in iteration & not at top-level [duplicate]

Conditional
Is it possible to have conditional import statements like below?
if (foo === bar) {
import Baz from './Baz';
}
I have tried the above but get the following error (from Babel) when compiling.
'import' and 'export' may only appear at the top level
Dynamic
Is it possible to have dynamic import statements like below?
for (let foo in bar) {
if (bar.hasOwnProperty(foo)) {
import Baz from `./${foo}`;
}
}
The above receives the same error from Babel whilst compiling.
Is this possible to do or is there something I am missing?
Reasoning
The reason I am trying to do this is that I have a lot of imports for a number of "pages" and they follow a similar pattern. I would like to clean up my code base by importing these files with a dynamic for loop.
If this is not possible then is there a better way to handle large number of imports in ES6?
We do have dynamic imports proposal now with ECMA. This is in stage 2. This is also available as babel-preset.
Following is way to do conditional rendering as per your case.
if (foo === bar) {
import('./Baz')
.then((Baz) => {
console.log(Baz.Baz);
});
}
This basically returns a promise. Resolution of promise is expected to have the module. The proposal also has things like multiple dynamic imports, default imports, js file import etc. You can find more information about dynamic imports here.
You can't resolve dynamically your dependencies, as imports are meant for static analysis. However, you can probably use some require here, something like:
for (let foo in bar) {
if (bar.hasOwnProperty(foo)) {
const Baz = require(foo).Baz;
}
}
As this question is highly-ranked by Google, it is worth pointing out that things have changed since the older answers were posted.
MDN has this entry under 'Dynamic Imports':
The import keyword may be called as a function to dynamically import a
module. When used this way, it returns a promise.
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});
This form also supports the await keyword.
let module = await import('/modules/my-module.js');
MDN also has a more detailed explanation.
A useful article on the subject can be found on Medium.
Since 2016 a lot has passed in JavaScript world, so I believe it's time to offer most updated info on this topic. Currently Dynamic imports are a reality both on Node and on browsers (natively if you don't care about IE, or with #babel/plugin-syntax-dynamic-import if you do care).
So, consider a sample module something.js with two named exports and one default export:
export const hi = (name) => console.log(`Hi, ${name}!`)
export const bye = (name) => console.log(`Bye, ${name}!`)
export default () => console.log('Hello World!')
We can use import() syntax to easily and cleanly load it conditionally:
if (somethingIsTrue) {
import('./something.js').then((module) => {
// Use the module the way you want, as:
module.hi('Erick') // Named export
module.bye('Erick') // Named export
module.default() // Default export
})
}
But since the return is a Promise, the async/await syntactic sugar is also possible:
async imAsyncFunction () {
if (somethingIsTrue) {
const module = await import('./something.js')
module.hi('Erick')
}
}
Now think about the possibilities along with Object Destructuring Assignment! For example, we are able to easily put only one of those named exports in memory for posterior use:
const { bye } = await import('./something.js')
bye('Erick')
Or maybe grab one of those named exports and rename it to anything else we want:
const { hi: hello } = await import('./something.js')
hello('Erick')
Or even rename the default exported function to something that makes more sense:
const { default: helloWorld } = await import('./something.js')
helloWorld()
Just a last (but no least) note: import() may looks like a function call, but it isn't a Function. It's a special syntax that just happens to use parentheses (similar to what happens with super()). So it's not possible to assign import to a variable or use things of the Function prototype, like call/apply.
Require will not solve your problem as it is a synchronous call. There are several options and they all involve
Asking for the module you need
Waiting for a promise to return the module
In ECMA Script there is support for lazy loading modules using SystemJS. This of course isn't supported in all browsers, so in the meantime you can use JSPM or a SystemJS shim.
https://github.com/ModuleLoader/es6-module-loader

Augment types for imported module in TypeScript

I'm converting an existing codebase from js/react/jsx setup to TypeScript. Naturally, I'd like to do it file by file, and have a question about approaches for making TS compiler work with the existing js codebase.
I convert file index.js but want to leave foo.js in JavaScript for now:
// index.ts
import { fooFunction } from 'foo';
foo({ val: 1 });
// foo.js
export const fooFunction = ({ val, optionalProp }) => {
//...
}
The problem in this example that TypeScript automatically infers argument to foo and complains that Property 'optionalProp' is missing in type { val:string, optionalProp:any }
Needing to "stub" the type of fooFunction sent me looking and I found a few ways i may be able to do it:
1) Use require instead of import
// index.ts
var fooFunction: any = require('foo').fooFunction;
2) Merge declarations
?
3) Add a d.ts file with custom declarations for foo - haven't attempted but seems inconvenient
Ideally I don't have to do (1) because I want to keep using import syntax and I don't have to do (3) because that will require me to add declaration files only to remove them later when I'm ready to convert foo.
(2) sounds awesome, but I can't figure out a working solution.
// index.ts
declare module 'foo' {
function fooFunction(options: any): any
}
Doesn't work and throws Cannot redeclare block-scoped variable 'fooFunction'
How do I do that? Are there docs that have examples of what I'm trying to do and/or have more explanation about declaration merging and how to work with namespace/interface/value?
Are there better approaches for incrementally transitioning to TypeScript?
This may not work, just wanted to preface this with that - I don't know much at all about React - but I know for sure that you can use ? on a variable to make it "optional". I've used this before in functions
myFooFunction(requiredParam: typeA, optionalParam?: typeB) { ... }
Another thing you should consider is the fact that Typescript can add some type checking at compile time, so IMO you should leverage that as much as possible.
Edit: to work with the optional parameter, you could just check that it exists.
myFooFunction(requiredParam: typeA, optionalParam?: typeB) {
if (optionalParam) { // if this results to true, the param was given
//do something
}
}

Convert closure to es6 module

I'm using a javascript build environment that supports es6 modules (using es6-module-transpiler) so you can simply import stuff across different files.
Now I got a third party library that I'd like to be "importable".
The library populates its functionality like this:
(function () {/*...*/}).call(this);
Would it be safe to omit the closure and convert it to:
export default function () {/* ... */};
Or is there a better way?
Thanks in advance!
The original code you show invokes the anonymous function, which to make any sense must define a global variable, whereas the second code fragment you show merely exports the function, which is a different thing.
For purposes of discussion, let's assume the original code defines a global like this:
// my-third-party-module.js
(function() {
let myVar = 22;
window.MyThirdPartyModule = { log: function() { console.log(myVar); } };
}.call(this);
and you are using is as so:
// app.js
MyThirdPartyModule.log();
You could rewrite this as
// my-third-party-module.js
let myVar = 22;
export default { log: function() { console.log(myVar); } };
// app.js
import MyThirdPartyModule from `my-third-party-module';
MyThirdPartyModule.log();
Note that we have moved the variable myVar which was local to the anonymous function to the top module level.
However, depending on your preferences, rather than exporting a big object, which is sort of a pre-module mentality, you might want to export its APIs individually:
// my-third-party-module.js
let myVar = 22;
export function log { console.log(myVar); }
// app.js
import {log} from `my-third-party-module';
log();
or if you prefer
// app.js
import * as MyThirdPartyModule from `my-third-party-module';
MyThirdPartyModule.log();
However, all of these approaches assume you are able and willing to edit the source of the third party library. If that is not the case, you could write a little piece of glue code, such as
// my-third-party-module-interface.js
import 'my-third-party-module'; // This will run the module.
export default MyThirdPartyModule; // Export the global it defined.
// app.js
import MyThirdPartyModule from 'my-third-party-module-interface';
If you would prefer again to export individual APIs, you could extend the glue to re-export each of them:
// my-third-party-module-interface.js
import 'my-third-party-module'; // This will run the module.
const {log, otherAPI, ...} = MyThirdPartyModule;
export {log, otherAPI, ...};
// app.js
import {log} from 'my-third-party-module-interface';
The conversion of legacy dependencies is still an issue. And the horrible workflow they use makes things a lot harder, prefixing the actual code with browserify and webpack silliness.
So what to do? Existentially, the library is guaranteed only to deposit a global in window but by obscure and weird ways. And all slightly different.
So let the legacy simply do what it is supposed to do for you, but wrapped in a module so that you can import it rather than use a script tag:
https://medium.com/#backspaces/es6-modules-part-2-libs-wrap-em-up-8715e116d690

Categories

Resources