159

Is it possible to pass options to ES6 imports?

How do you translate this:

var x = require('module')(someoptions);

to ES6?

loganfsmyth
  • 135,356
  • 25
  • 296
  • 231
Fabrizio Giordano
  • 2,581
  • 3
  • 18
  • 15
  • Not sure you can, there's a module loader API, or at least there was at some time, that used something like `System.import(module)`, not sure if that allows arguments or not, someone who knows more about ES6 probably does ? – adeneo Apr 28 '15 at 15:49
  • There is a proposed solution for this, for which there are already implementations in node.js (via a plugin) and webpack: http://2ality.com/2017/01/import-operator.html – Matt Browne May 27 '17 at 06:21

7 Answers7

114

There is no way to do this with a single import statement, it does not allow for invocations.

So you wouldn't call it directly, but you can basically do just the same what commonjs does with default exports:

// module.js
export default function(options) {
    return {
        // actual module
    }
}

// main.js
import m from 'module';
var x = m(someoptions);

Alternatively, if you use a module loader that supports monadic promises, you might be able to do something like

System.import('module').ap(someoptions).then(function(x) {
    …
});

With the new import operator it might become

const promise = import('module').then(m => m(someoptions));

or

const x = (await import('module'))(someoptions)

however you probably don't want a dynamic import but a static one.

Bergi
  • 513,640
  • 108
  • 821
  • 1,164
  • 7
    Thank you I wish there was something like `import x from 'module' use someoptions;` kinda of syntax – Fabrizio Giordano Apr 28 '15 at 16:07
  • 1
    @Fabrizio: If you think about it further, it wouldn't be that helpful really. It would only work if the module exports a function and should probably not be allowed if we have named imports (i.e. `import {x, y} from 'module'`). Then what should the syntax be if I want to pass multiple arguments? Or spread an array of arguments? It's a narrow use case and basically you are trying to add a different syntax for a function call, but we already have function calls which allow us to deal with all the other cases. – Felix Kling Apr 28 '15 at 16:13
  • 3
    @FelixKling I completely agree with you. I was converting an old express webapp and I encounter `var session = require('express-session'); var RedisStore = require('connect-redis')(session);` I was just wondering if there was a one line solution. I can totally survive with split the RedisStore assignment in 2 lines :) – Fabrizio Giordano Apr 28 '15 at 16:16
  • @FabrizioGiordano: I could imagine something like `import {default(someoptions) as x} from 'module'` in ES7, if there is really a need for this. – Bergi Apr 28 '15 at 16:38
  • 2
    For the `session`/`connect-redis` example, I have been imagining syntax like this: `import session from 'express-session'); import RedisStore(session) from 'connect-redis'`. – Jeff Handley Jun 19 '15 at 20:06
  • I can def see a need for this, and I'm def trying to solve it right now. I have a module that needs to be a true singleton across my app. The problem is, one of my other modules needs to access it in it's runtime so by the time my index.js runs, and I'm able to configure the singleton, the other module's code has already been hoisted and ran. This is really frustrating. Wrapping the export works only if you're ok w/ a new instance every time. But if you want the same object to be config'd and shared, you're SOL w/ `import` :-( – RavenHursT Sep 24 '16 at 02:54
  • It looks like official support for dynamic imports will be coming aoon, and implementations already exist for node and webpack: http://2ality.com/2017/01/import-operator.html – Matt Browne May 27 '17 at 06:23
  • @MattBrowne Thanks for the info, but that doesn't really change the picture for the static case. You can't do the `const x = (await import(…))(options)` in a module scope. (Yet?) – Bergi May 27 '17 at 16:39
  • @Bergi How do you export multiple module, not just one with the same parameter? – TSR Mar 18 '19 at 00:05
  • `export default function (GreetingIntroTxt:string) { class Student { name: string; constructor(name: string) { this.name = name; } greet() { return `"${GreetingIntroTxt}, " + this.greeting`; } } return { Student,} } ` This returns a error: TS4060: Return type of exported function has or is using private name 'class' – TSR Mar 18 '19 at 04:02
26

Concept

Here's my solution using ES6

Very much inline with @Bergi's response, this is the "template" I use when creating imports that need parameters passed for class declarations. This is used on an isomorphic framework I'm writing, so will work with a transpiler in the browser and in node.js (I use Babel with Webpack):

./MyClass.js

export default (Param1, Param2) => class MyClass {
    constructor(){
        console.log( Param1 );
    }
}

./main.js

import MyClassFactory from './MyClass.js';

let MyClass = MyClassFactory('foo', 'bar');

let myInstance = new MyClass();

The above will output foo in a console

EDIT

Real World Example

For a real world example, I'm using this to pass in a namespace for accessing other classes and instances within a framework. Because we're simply creating a function and passing the object in as an argument, we can use it with our class declaration likeso:

export default (UIFramework) => class MyView extends UIFramework.Type.View {
    getModels() {
        // ...
        UIFramework.Models.getModelsForView( this._models );
        // ...
    }
}

The importation is a bit more complicated and automagical in my case given that it's an entire framework, but essentially this is what is happening:

// ...
getView( viewName ){
    //...
    const ViewFactory = require(viewFileLoc);
    const View = ViewFactory(this);
    return new View();
}
// ...

I hope this helps!

Swivel
  • 2,226
  • 23
  • 34
  • Since all your imported modules are classes, why not pass the parameter when instantiating the class? – user Nov 23 '16 at 21:09
  • 1
    @jasonszhao The most important thing to note here is that the class `MyView` extends certain items available in the framework's namespace. While it's absolutely possible to simply pass it in as a parameter to the class, it also depends on when and where the class is instantiated; portability is then affected. In practice, these classes may be handed over to other frameworks that may instantiate them differently (e.g., custom React components). When the class finds itself outside framework scope, it can still maintain access to the framework when instantiated because of this methodology. – Swivel Nov 24 '16 at 01:16
  • @Swivel Please assist I need help with similar issue: https://stackoverflow.com/questions/55214957/pass-options-to-es6-module-imports-not-working – TSR Mar 18 '19 at 06:10
13

Building on @Bergi's answer to use the debug module using es6 would be the following

// original
var debug = require('debug')('http');

// ES6
import * as Debug from 'debug';
const debug = Debug('http');

// Use in your code as normal
debug('Hello World!');
Community
  • 1
  • 1
mummybot
  • 2,214
  • 1
  • 24
  • 28
  • typescript with "module": "commonjs" and "esModuleInterop": true in tsconfig.js - `import * as createPrompt from '../node_modules/prompt-sync'; const prompt = (createPrompt.default ?? createPrompt)();` so this works with both tsc and ts-node commands – TamusJRoyce May 28 '21 at 17:21
4

I believe you can use es6 module loaders. http://babeljs.io/docs/learn-es6/

System.import("lib/math").then(function(m) {
  m(youroptionshere);
});
user895715
  • 81
  • 1
  • 6
  • 3
    But where does the result of `m(youroptionshere)` end up? I suppose you could write `System.import('lib/math').then(m => m(options)).then(module => { /* code using module here */})`... but it's not very clear. – Stijn de Witt Mar 24 '16 at 14:00
  • 2
    Wow I can't believe there's no elegant way to do this in E6. That's the way I mainly write modules. – Robert Moskal Jul 20 '16 at 21:29
3

You just need to add these 2 lines.

import xModule from 'module';
const x = xModule('someOptions');
Mansi Teharia
  • 670
  • 6
  • 10
  • 1
    That's simply passing parameters to a function you've imported and are calling. It's not passing any options *to the module you import it from*. `xModule` is misleading here. What you actually have is `import func from 'module'; func('someOptions');`. – Dan Dascalescu Sep 27 '19 at 05:44
2

Here's my take on this question using the debug module as an example;

On this module's npm page, you have this:

var debug = require('debug')('http')

In the line above, a string is passed to the module that is imported, to construct. Here's how you would do same in ES6


import { debug as Debug } from 'debug' const debug = Debug('http');


Hope this helps someone out there.

1

I've landed on this thread looking up for somewhat similar and would like to propose a sort of solution, at least for some cases (but see Remark below).

Use case

I have a module, that is running some instantiation logic immediately upon loading. I do not like to call this init logic outside the module (which is the same as call new SomeClass(p1, p2) or new ((p1, p2) => class SomeClass { ... p1 ... p2 ... }) and alike).

I do like that this init logic will run once, kind of a singular instantiation flow, but once per some specific parametrized context.

Example

service.js has at its very basic scope:

let context = null;                  // meanwhile i'm just leaving this as is
console.log('initialized in context ' + (context ? context : 'root'));

Module A does:

import * as S from 'service.js';     // console has now "initialized in context root"

Module B does:

import * as S from 'service.js';     // console stays unchanged! module's script runs only once

So far so good: service is available for both modules but was initialized only once.

Problem

How to make it run as another instance and init itself once again in another context, say in Module C?

Solution?

This is what I'm thinking about: use query parameters. In the service we'd add the following:

let context = new URL(import.meta.url).searchParams.get('context');

Module C would do:

import * as S from 'service.js?context=special';

the module will be re-imported, it's basic init logic will run and we'll see in the console:

initialized in context special

Remark: I'd myself advise to NOT practice this approach much, but leave it as the last resort. Why? Module imported more than once is more of an exception than a rule, so it is somewhat unexpected behavior and as such may confuse a consumers or even break it's own 'singleton' paradigms, if any.

GullerYA
  • 719
  • 6
  • 20