0

All current module loaders like AMD,CommonJS,SystemJS using variable definitions for loading external objects into current module scope

like:

var something = require('something');

or:

define('module',['something'],function(something){});

In case when you don't know what you need to import from external module, or just need to import everything, this becoming issue, because impossible to define variable in runtime.

I guess this is the main reason why ES6 translators not using

import * from 'something';

syntax and they are not included this in ES6 Spec.

So by saying dynamic module scope I mean that module variables can be defined/loaded runtime, which will allow to extend ES6 syntax to something like:

import * from 'something';
import Something.* from 'something';
import /regexp/ from 'something';

In my view this is more optimal way to define imports, rather then to list all names like:

import {
    ONE,
    TWO,
    ...
} from 'something';

Now my real question:

why to do not use with to achieve this ?

Here is simple example translations from ES6 to ES5 that can solve problem:

file: modules/module-1.js

var localVar = 'VALUE';
function localFun(a,b){
    return a +' and '+ b;
}
export {
    localVar as module1Var
    localFun as module1Fun
}

to:

(function(){
    // define module named 'modules/module-1' 
    // and return related scope object 
    // containing 'execute_module' function predefined 
    with (define_module('modules/module-1')) {
        // will register actual module execution 
        // which will be called after all imports 
        // initialized 
        execute_module(function(){
            var localVar = 'VALUE';
            function localFun(a,b){
                return a +' and '+ b;
            }
            // returns value as current module exports 
            return {
                module1Var : localVar,
                module1Fun : localFun
                // ...
            };
        });
    }
})();

file: modules/module-1.js

import * from 'modules/module-1.js';

console.info(module1Fun(module1Var)); //prints: VALUE and VALUE

to:

(function(){
    // define module named 'modules/module-2' 
    // after execute is called loader will do 
    // all imports and bind exports from modules 
    // to current module scope and then invoke callback
    // after which imported variables will appear in with scope
    // and will be visible in callback scope. 
    with (define_module('modules/module-2',{
        'modules/module-1' : '*',
        // also can filter exports by regexp
        'modules/other'    : /.*/
    })) {
        execute_module(function(){
            console.info(module1Fun(module1Var)); //prints: VALUE and VALUE                
        });
    }
})();

is it really necessary to avoid with even in transpilers/loaders ?

I will appreciate your thoughts on this guys, because I'm thinking about writing another ES6to5 translator and module loader. :)

Leonid Beschastny
  • 50,364
  • 10
  • 118
  • 122
  • Why do you want to wrote a transpiler that does not conform to the spec? That doesn't seem to make sense to me. Of course you can do whatever you want and use your own JS dialect. – Felix Kling May 25 '15 at 01:24

2 Answers2

2

So there are a few great reasons not to do this...

...the first is simply that it's bad practice in JS to slather the working context with either global or "global" variables, without knowing what those variables are.

If the content of that import changes, and I'm using a with (which isn't really valid ES5... ...ES5 is the Strict Mode subset, with is left in to allow ES3 code to run in ES5 browsers without exploding), then I have much more to worry about than just trying to call a method on the module that's changed...

// some-module/v1.0/some-module.js
// ...
export { doA, doB, a, b }

// my-app/index.js
import * with "./some-module/v1.0/some-module";

const c = 1;
let doC = ( ) => doA( a ) + doB( b );

That looks fine, sure. No harm at all.

What if some-module is an NPM package, and I'm in dev-mode, so I want to keep updating some-module across feature-adds, because I know that some features will make my life easier, when they land:

// some-module/v1.5/some-module.js
export { doA, doB, doC, a, b, c };

// my-app/index.js
import * with "./some-module/v1.5/some-module";

const c = 1;
/*
  ...
*/
let doC = ( ) => doA( a ) + doB( b );

BOOM!

c is already defined.

If you search all of your compiled import libraries/modules/components (let's say you have 8 of them in your app's entry point)... ...and you finally find the source that imports c as a global, and you update all of your code, to replace the name of your c to something else, and then replace all references to it, so that they don't explode anymore, what happens next?

BOOM! doC is already defined.

Repeat the process and find the offending file.

How do we solve these problems?
Typically, that's through namespacing.

We already have namespacing in those import statements which work mostly-fine (with further proposals for even more simplification in ES7).

import someModule from "./some-module/v1.0/some-module";

let doC = ( ) => someModule.doA( someModule.a ) + someModule.doB( someModule.b );

All of a sudden, it becomes clear to see that there is no fear of a method/value collision, anywhere in your app, unless it's your own fault.

Moreover, if you load the whole library and decide you want to save time by referencing some of those values/methods directly, you can choose to do so.

import someModule from "./some-module/v1.5/some-module";
const { a, b } = someModule;
let { doA, doB } = someModule;

const c = 1;
let doC = ( ) => doA( a ) + doB( b ) + someModule.doC( c );

There's still 0% ambiguity, and yet all of the code has been kept as small as possible, thanks to giving the consumer the choice of how to handle the import.

Norguard
  • 26,167
  • 5
  • 41
  • 49
0

@norguard, good catch on "strict mode" and module updates, but:

strict mode still can be used in actual execution callback

booms ! definitions of local execution scope will override(hide) definitions of with scope in current context, so there are no problem of redefinition.

// some-module/v1.5/some-module.js
export { doA, doB, doC, a, b, c };
// my-app/index.js
import * with "./some-module/v1.5/some-module";
const c = 1;
/*
  ...
*/
let doC = ( ) => doA( a ) + doB( b );

// translated to:
(function(){
    // with scope contain { doA, doB, doC, a, b, c }
    with (define_module('my-app/index.js',{
        'some-module/v1.5/some-module.js':'*'
    })) {
        execute_module(function(){
            "use struct"

            // local var 'c' overrides definition 'c' of 'with scope'
            // in current function context.
            var c = 1; 
            //  same for 'doC' function 
            var doC = function( ){return doA( a ) + doB( b )};

        });
    }
})();

So the everything above still will work.

  • I'm really not sure what you're hoping to get out of this. First: the spec expects `import myModule from "my-module";` to be equal to `var myModule = require("my-module").default;`, as it stands. That variable is defined *in the root scope of the module*, and ***only*** in the root scope of the module (it's actually invalid to use `import`/`export` statements at any scope other than the top level of the file you're working in. Also, like I mentioned, if you had 8 files you were `with`ing into your "index.js", each of which had 5-10 IT was `with`ing, potentially passing through... – Norguard May 25 '15 at 01:36
  • ...how do you resolve that? Is it now part of the JS spec that the modules I depend on are now order-dependent? If I import "1.js", "2.js" and "3.js", my global variables should be expected to be different than "3.js" "2.js" "1.js"? Last is the "Temporal Dead Zone". I can't use a `let` or a `const` to define something already initialized in this current scope. Moreover, I can't reference the value *above* where it's referenced, including shadowing. So assuming you conform to the whole spec, this breaks. Being non-compliant is fine... ...but that's your prerogative... – Norguard May 25 '15 at 01:40