0

I am using node --experimental-modules test.mjs (NodeJs v11.9.0).

There are many options to implement the same class, and I need to swith by terminal,

switch (process.argv[2]) {
  case 'a':
    import MyClass from './MyClass-v1.mjs'; break;
  case 'b':
    import MyClass from './MyClass-v2.mjs'; break;
  default:
    import MyClass from './MyClass-v3.mjs';
}

ERROR on import MyClass: Unexpected identifier

PS: working fine when using isolated import MyClass from './MyClass-v3.mjs';.


NOTE: const MyClass = require('./MyClass-v3.mjs') not works with modern Javascript. (ReferenceError: require is not defined). The file have only a class definition,

export default class MyClass { ... }

PS: there are nothing like C_preprocessor for NodeJs? An old pre-compiler (with some argv access) will be fine here.

Peter Krauss
  • 13,174
  • 24
  • 167
  • 304
  • 1
    Hi. Probably you need to use Factory pattern https://en.wikipedia.org/wiki/Factory_method_pattern – Vlad Kosko Mar 03 '19 at 11:35
  • Hi @VladKosko, make sense... What the best way to implement it with Node? No easy way to dynamic loading? My code is also a kind of [proxy pattern](https://en.wikipedia.org/wiki/Proxy_pattern). – Peter Krauss Mar 03 '19 at 11:39
  • The import statement is only allowed at the top level of modules. To import anywhere else use `require`. – Dan D. Mar 03 '19 at 13:48
  • Hi @DanD., I edited, using modern Node. *"ReferenceError: require is not defined"*. – Peter Krauss Mar 03 '19 at 14:06
  • 1
    You will need a [dynamic import](http://2ality.com/2017/01/import-operator.html) for this, you cannot put an import declaration inside a switch case. Not sure though whether node already supports `import()`. – Bergi Mar 03 '19 at 14:52
  • @Bergi, yes, as I expressed *"working fine when using isolated `import ...` "*, the `import()` clause is valid, the problem is to use (how to?) in a `switch` clause. – Peter Krauss Mar 03 '19 at 15:38
  • Something like `import(getModuleName(process.argv[2])).then(MyClass => { … });` probably, where `getModuleName` can do anything to resolve the argument (using `switch`, `if`/`else`, an object literal, a `Map`, or something else) – Bergi Mar 03 '19 at 15:40
  • @Bergi I try `import(file).then(MyClass);` but error, "ReferenceError: MyClass is not defined". I try `import(file).then(MyClass => {});` same error. The `getModuleName()` is easy, we need the basic thing first, to load a class. – Peter Krauss Mar 03 '19 at 15:46
  • ... Seems that Node v11.9 not supports things like `const MyClass = await import('./file.mjs')` , error at `await`, "SyntaxError: Unexpected reserved word". The most simple, `const MyClass = import(file);` works, but not as expected, *"TypeError: MyClass is not a constructor"*. – Peter Krauss Mar 03 '19 at 15:59
  • @PeterKrauss Using promises, you need to put all the code that uses `MyClass` inside that callback (where I put the elision). If you want to use `await`, you need to put that code in an `async function` (which you can make an IIFE, though). – Bergi Mar 03 '19 at 16:07
  • @Bergi, no, as **only a version test**, I not need, I need sync... As I edited, any NodeJs pre-compiler (exist?) will be also a solution. – Peter Krauss Mar 03 '19 at 16:10
  • 1
    @PeterKrauss You cannot do synchronous dynamic imports with ES6 modules. Given that this code seems to be located in your main.js, doing it asynchronously should not be a problem. – Bergi Mar 03 '19 at 16:12
  • @Bergi, yes, I can use assync, but not ugly syntax/control with event listener where I need a trivial import. – Peter Krauss Mar 03 '19 at 17:45
  • @Bergi, using the encapsulated form `import('./MyClass.mjs').then(MyClass => { let x = new MyClass(); console.log( x ) });` the error is *(node:10431) UnhandledPromiseRejectionWarning: TypeError: MyClass is not a constructor at then.MyClass (file:///...:10:11) (node:10431) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (node:10431) [DEP0018] DeprecationWarning: Unhandled promise rejections..."* – Peter Krauss Mar 03 '19 at 17:53
  • @PeterKrauss Ah right, `import()` resolves to the namespace object, not to the default export. You'd need to use `then(({default: MyClass}) => …)` or `const {default: MyClass} = await …` – Bergi Mar 03 '19 at 17:55
  • YES, first suggestion is working! Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/189353/discussion-between-peter-krauss-and-bergi). – Peter Krauss Mar 03 '19 at 18:11

2 Answers2

2
import ClassV1 from './MyClass-v1.mjs';
import ClassV2 from './MyClass-v2.mjs'; 
import ClassV3 from './MyClass-v3.mjs';

const classes = {
  a: ClassV1,
  b: ClassV2
}

const Class = classes[process.argv[2]] || ClassV2;

You could also write an index file with all the classes and do an import * as classes from 'classes.mjs';.

Markus
  • 1,598
  • 2
  • 13
  • 32
  • Hi, is a valid solution because it works (!), but it is in the opposite of the "import only necessary" philosophy... An old pre-compiler solution will be better. – Peter Krauss Mar 03 '19 at 17:41
  • You can not do a precompiler solution if you don't know at compile time which class you want. So all modules have to be available at runtime. But if you want a precompiler, this is probably the closest you get: https://webpack.js.org/contribute/writing-a-loader/ Use can use codeshift for the transformation: https://github.com/facebook/jscodeshift – Markus Mar 05 '19 at 07:58
0

Modern Javascript (ES6+) implementations are under construction, some syntax are working with NodeJS (node --experimental-modules) and good browsers, other not.

This is the only way to do without cost of "load all" (see @Markus solution), but with the cost of non-global import, needs an ugly block to define scope and assyncronous operation... First step is to understand the basic impor block syntax:

import('./MyClass-v3.mjs').then(({default: MyClass}) => {
    // can use MyClass only here in this block
    let x = new MyClass();
    console.log("DEBUG=",x);
});

Them, putting all together, the argv selection and the optional imports.

Solution

const classes = {
  a: './MyClass-v1.mjs'
  ,b: './MyClass-v3.mjs'
  ,c: './MyClass-v3.mjs'
};
import(classes[process.argv[2]] || classes.c ).then(({default: MyClass}) => {
    let x = new MyClass();
    console.log("DEBUG=",x);
});

PS: some of the magic (no rational) is to use ({default: MyClass}) instead (MyClass).


Thanks

To @Bergi (!), for explain this solution (see question comments).

Peter Krauss
  • 13,174
  • 24
  • 167
  • 304