import { foo, bar } as obj from 'module

# Tiddo Langerak (7 years ago)

In modern ES code, I love the named imports & exports since it allows us to only import what we need. Tooling has jumped on this as well by stripping out any exports that aren't used (treeshaking).

However, a big downside of named imports is that you lose any "namespacing", which is especially relevant for generic function/variable names. For example, lodash and async both export a map function, and it isn't unlikely to use both packages in a single project, or even a single module. This gives naming conflicts.

We currently have 2 options to resolve these conflicts:

  • Alias individual imports:

import { map as lodashMap } from 'lodash';     import { map as asyncMap } from 'async';

  • Replace with * import

import * as _ from 'lodash';     import * as async from 'async';

Neither option is ideal:

  • Aliasing results in either inconsistent code (some imports are prefixed, others aren't) or "smurfnaming". Prefixing is often also less readable than namespacing, especially for longer names (lodashLastIndexOf vs lodash.lastIndexOf).
  • Star imports prevent treeshaking.

The solution to this seems simple enough: allow a group of named imports to be aliased as a whole:

import { map } as _ from 'lodash';     _.map();

This preserves both "namespacing" and selective importing, giving the best of both worlds.

To me this seems like such a no-brainer that I'm actually surprised that this isn't possible already. Has something like this been considered at some point? And what would be the downsides of this? Or is there just too little interest in this?

# Tiddo Langerak (7 years ago)

Thanks for pointing that out, I didn't know that (I should've tested my assumptions). It seems that Webpack does the same.

The entire motivation behind selective namespaced imports relied on that assumption, so if treeshaking works with * imports then I don't think there's much use for this feature.

# Isiah Meadows (7 years ago)

This import { ... } as foo from "..." proposal would be incredibly useful. There's been times where I frequently used a single method from an import, but I only used the rest maybe once or twice, and thus didn't want to include everything when importing. It's easier than doing either duplicate imports or a subsequent const foo = ns.foo after the import.


Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Andrea Giammarchi (7 years ago)

FWIW I agree import {a, b} as mod from './module.js' makes sense and it's handy.

I actually thought it was already possible and failed already a couple of time with that expectation.

# Darien Valentine (7 years ago)

To me this seems like such a no-brainer that I'm actually surprised that this isn't possible already.

I think it’s a no-brainer now that we’ve all been using import/export in practice for a while, but it’s understandable that it might not have been obvious on the first pass.

I don’t know how much of a factor this is in language design, but I think this addition could have a nice side effect — it might help encourage patterns that happen to play well with "tree shaking". [Edit: the OP already called this out specifically and I just missed it]

I bet this would fly if proposed!

# kai zhu (7 years ago)

I don’t know how much of a factor this is in language design, but I think this addition could have a nice side effect — it might help encourage patterns that happen to play well with "tree shaking".

tree-shaking is incompatible with class-inheritance and meta-programming in javascript. it has negligible effect in practice, unless the majority of your code uses static-functions instead of classes.

# Darien Valentine (7 years ago)

tree-shaking is incompatible with class-inheritance and meta-programming in javascript. it has negligible effect in practice, unless the majority of your code uses static-functions instead of classes.

It’s just a form of dead code elimination that takes advantage of the fact that imports/export statements are static. More often useful for library code than local app code (since if you had unused stuff there, you’d likely just delete it). I’m pretty perplexed by the suggestion that it has any sort of relationship with classes or inheritance. Are we talking about different things?

# kai zhu (7 years ago)

we're talking about the same thing. class-based libraries like backbone.js for example cannot be tree-shaken. i've done manual tree-shaking for the entire frontend-stack for swagger-ui, by writing tests and doing deadcode-elimination from code-coverage. the takeaway from that was most of its libraries could not be tree-shaken, until the underlying class methods and constructors were manually refactored into separable static-functions.

# Giancarlo Anemone (7 years ago)

Kai, I believe you are confusing classes with modules. A module can have many exports. For example, you could export 5 different classes from a single module. If you only import one of those classes, tree shaking ensures you don't include the other 4 unused Classes.

I think what you might be referring to is that tree shaking can't eliminate methods on a class that are potentially not called by a user. However that by no means makes tree shaking not valuable even when using libraries that export classes.

Finally, in my personal experience the amount of performance improvements you can make from dead code elimination are far from negligible. Of course it depends on many factors, but it is an incredibly useful feature.

# dante federici (7 years ago)

Definitely make a proposal for this -- I'm pretty tired of colliding utility function names and having to aggressively namespace them.

It's been said but I want to just throw in another voice that this will 100% work with tree shaking, and as we get better at organizing our modules and dependencies, it'll become more apparent.

With all the pushes toward pure functional code, utility modules and libraries will benefit more and more from tree shaking and clear / strong import/export syntax.

# Caridy Patiño (7 years ago)

those {} that you see in the export and import statements are not objects, it is just syntax. Yes, I know, people confuse them with objects, until they realize they aren’t. We probably should have chosen a different syntax to signal that it is a binding from the module’s environment record.

Another way to look at this is from the lenses of function declarations or function expressions, those {} that represent the body of the function are more closely related to what you use in the import/export statements that what you use for objects.

For those assuming that this will just fly through the staging process, it will not! There are many issues when considering it a object declaration.

# Darien Valentine (7 years ago)

Those {} that you see in the export and import statements are not objects, it is just syntax. Yes, I know, people confuse them with objects, until they realize they aren’t. We probably should have chosen a different syntax to signal that it is a binding from the module’s environment record.

Not sure about the other folks here, but I wasn’t under the impression that the braces were an object literal or a binding pattern — though obviously * from 'foo' does return the module namespace exotic object. The idea I think is that it would be useful to extend NamespaceImport to permit retrieving a filtered view of the MNSEO that exposes only the specified bindings. I see that it’s novel because (correct me if I’m wrong) the same MNSEO instance is returned for namespace imports across multiple importing modules and is observably the same reference; currently it’s just "create the first time, retrieve thereafter". So here we’d be explicitly requesting a new object even if the module instance is already loaded, yes — but presumably it would be a new MNSEO (or proxy thereof) with the usual binding semantics, not a pojo.

# Andrea Giammarchi (7 years ago)

but I wasn’t under the impression that the braces were an object literal

or a binding pattern

I think nobody thought that, we all assumed destructuring into a namespace.

import * as name from "module-name"; import { *export *} from "module-name";

the first is an import "everything" from module and the second destructures export from the module exports.

import { export } as name from "module" would destructure export and save it as name.export.

It's like having a namespace and use destructuring to address it.

We cannot:

const name = {}; import { export: name.export } from "module"

so that

import { a, b, c } as name from "module"

seems to be the easiest way to go/explain/use namespaces as import.

# Andrea Giammarchi (7 years ago)

... sorry, I meant:

import { export as name.export } from "module"

which still is not possible.

import { export } as name from "module"

could also be compatible with live bindings too, so it seems like a win/win however you look at it

# Caridy Patiño (7 years ago)

Ok, fair enough! grammar wise, it doesn’t seem impossible since we already have * as ns. We knew about the possibility of doing folding and tree shaking, but there was nothing practical when module syntax was approved, and at that point, importing a namespace was probably sufficient, but not anymore, I get that.

Few more questions before someone can champion this:

  1. Assuming that this new syntax produces a exotic namespace object is not trivial, e.g.:
import { x, y } as foo1 from "foo";
import { x, y } as bar1 from "foo"; // what if we use { y, x } instead?
foo1 === bar1 ; // is this true or false?

the same question applies when these are coming from different modules, while for existing namespace objects, the answer is always true:

import * as foo2 from "foo"; import * as bar2 from "foo"; foo2 === bar2; // yields true

  1. Exotic namespace objects are bound to a module and its exports, and they have a 1-1 relationship, while this new thing seems quite different: tc39.github.io/ecma262/#sec-module-namespace-exotic-objects, tc39.github.io/ecma262/#sec-module-namespace-exotic-objects

What are the reasons for this new syntax to produce exotic objects? Keep in mind that we usually favor bindings over namespace objects for performance reason (although implementers could do some optimizations with namespace objects).

  1. Will this new syntax have symmetry with the export statement as well? e.g.: export { x, y } as foo from "foo";
# Andrea Giammarchi (7 years ago)

I see the following:

import { x, y } as foo1 from "foo";

as the pseudo equivalent of:

import {x, y} from "foo";
const foo1 = {
  // live bindings survive
  get x() { return x; },
  get y() { return y; }
};
// but x and y never happened
// (pseudo-operation)
delete scope references {x, y};

Accordingly, these are my answers:

  1. foo1 === bar1 is false, no exotic object whatsoever. It's a namespace with getters (that could eventually be enriched a part)
  2. no need for exotic objects
  3. export { x, y } as foo from "foo"; will export a foo object with x and y accessors. It'd be possible to import {foo} from "foo" so the answer to the third point is yes.
# Darien Valentine (7 years ago)
  1. (Caching of same-subset namespace import) My gut feeling was that the simplest thing to do would be to treat import { x, y } as foo from 'foo'; as always creating a new MNSEO or — more likely? — not an MNSEO directly, but a proxy of it whose only job is to filter access. That is, internally there would still be a "complete" MNSEO. So even within a single module, I would expect import { x, y } as foo from 'foo'; import { x, y } as bar from 'foo'; assert(foo !== bar); My main reason for thinking this is that it’s less complex than maintaining a cache keyed on what was imported, and it doesn’t seem that a caching behavior would provide any useful advantages.

  2. (Potential conflict with current definitions of MNSEO model / behavior) I agree, this does seem different, which is why I’d suggest the value be a proxy of some sort. If it proxies the same underlying MNSEO, I’m pretty sure MNSEO’s existing 1:1 behavior would be unaffected.

  3. (Export symmetry) That’s interesting — yeah, it would make sense to honor the contract; import * as ns from "foo"; is permitted, and export { foo, bar } as baz; would not be equivalent to export const baz = { foo, bar }; since only the former would keep live bindings.

I definitely don’t have the perspective to see how these ideas would be good or bad for implementors, so I may be on the wrong track, not sure.

(BTW, when I said I thought this "would fly", I meant it in the idiomatic sense "be viable", rather than that it "would have no considerations or concerns" haha)

# Caridy Patiño (7 years ago)

inline

On Dec 14, 2017, at 2:29 PM, Darien Valentine <valentinium at gmail.com> wrote:

  1. (Caching of same-subset namespace import) My gut feeling was that the simplest thing to do would be to treat import { x, y } as foo from 'foo'; as always creating a new MNSEO or — more likely? — not an MNSEO directly, but a proxy of it whose only job is to filter access. That is, internally there would still be a "complete" MNSEO. So even within a single module, I would expect import { x, y } as foo from 'foo'; import { x, y } as bar from 'foo'; assert(foo !== bar); My main reason for thinking this is that it’s less complex that maintaining a cache keyed on what was imported, and it doesn’t seem that a caching behavior would provide any useful advantages.

No proxies please! those are another kind of exotic objects, and it will just complicate even more this feature request. Think about this as records of some sort based on what you’re importing and from where. The semantic to be figure here is how do they behave across modules when they are exported. We need a better answer here.

  1. (Potential conflict with current definitions of MNSEO model / behavior) I agree, this does seem different, which is why I’d suggest the value be a proxy of some sort. If it proxies the same underlying MNSEO, I’m pretty sure MNSEO’s existing 1:1 behavior would be affected.

same as above.

  1. (Export symmetry) That’s interesting — yeah, it would make sense to honor the contract; import * as ns from "foo"; is permitted, and export { foo, bar } as baz; would not be equivalent to export const baz = { foo, bar }; since only the former would keep live bindings.

We need to think more about this. The semantic to be figure here is whether or not { x, y } as foo should use live bindings, and that will help to clarify few things.

# Andrea Giammarchi (7 years ago)

if you import {x} from "foo" and x changes in foo, then x reflects changes in the module that imported it. if you import {x} as foo from "foo" I'd expect the same and my answer was making it possible, without using Proxies, right?

not sure why you're fully skipping my proposed solution here

# Darien Valentine (7 years ago)

@Andrea I think we are thinking along the same lines. I meant proxy with a lowercase p — what you described fits the bill.

# Andrea Giammarchi (7 years ago)

I've just tried monkey patching all the scenarios I've described/mentioned and it works right away.

// x.js export let x = 1; setTimeout(() => x = 2, 2000);

// module.js import {x} from './x.js'; const obj = Object.defineProperty({}, 'x', {get() { return x; }}); export {x, obj};

// index.js import {x, obj} from './module.js'; console.log(x, obj.x); setTimeout(() => console.log(x, obj.x), 3000);

I see 1, 1 and then 2, 2 without using proxies or exotic objects.

{a, b, c} as obj should really just avoid a, b, c scope pollution and create those accessors in the obj constant object reference.

# Isiah Meadows (7 years ago)

I see it as it should semantically be identical to this, short a duplicate module request avoided:

// Proposed
import {x, y} as foo from "mod"

// Desugared
import * as foo from "mod"
import {x, y} from "mod"

(This includes the implication x/foo.x and y/foo.y are equivalent.)

# Andrea Giammarchi (7 years ago)

that wouldn't solve the issue we are trying to solve, because the desugared version will have x, and y in scope, right?

we are trying to avoid issues with name clashes. If the new syntax doesn't solve that, it's not any more helpful than import * as foo from "mod"

# Isiah Meadows (7 years ago)

Yes, but how would that be any different from what you'd have to deal with normally?

To clarify, I was basically proposing exposing both the MNSEO with all export members (like * as foo) as well as live bindings from the module, separately. Something along the lines of a combination of the two.

FYI, this is what Haskell does with import qualified Foo.Bar (x, y) as Foo. It exposes the exports x and y as globals, and it exposes those with everything else under the Foo namespace. (Of course, this isn't the driving reason for my suggestion - I didn't think about that until now.)

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Darien Valentine (7 years ago)

To clarify, I was basically proposing exposing both the MNSEO with all export members (like * as foo) as well as live bindings from the module, separately. Something along the lines of a combination of the two.

Though I agree it would be nice to be able to do that in a single statement, too, I’d imagine that adding it would take a form along the lines of introducing NamedImports , NameSpaceImport or its inverse to ImportClause — e.g.

import { foo, bar }, * as baz from 'qux';

That is, there’s already syntax for both, they’re just not currently permitted in a single statement — whereas what’s proposed here ({} as) is a construction that doesn’t currently exist for a type of import that doesn’t currently exist.

# Andrea Giammarchi (7 years ago)

here we are trying to achieve this:

import { x, y } as foo from "foo";
import { x, y } as bar from "bar";

// so that we have
foo.x();
foo.y();

// and bar
bar.x();
bar.y();

// but NO x, y in scope

if you export x and y in scope you gotta have conflicts/clashes, which if I understand correctly, is exactly what this idea is trying to avoid.

# Isiah Meadows (7 years ago)

Okay. Thanks for the clarification for what this is actually asking for. (I take back my "I could seriously use this" note from earlier.)

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Jérémy Judéaux (7 years ago)

One of the first assumptions in this discussion is that “Star imports prevent treeshaking”

But thinking about it, I don’t see why tools cannot transform

import * as L from './lib';
console.log(L.foo, L.bar());

To

import {foo as $$generated$$1$$foo$$, bar as $$generated$$1$$bar$$} from './lib';
console.log($$generated$$1$$foo$$, $$generated$$1$$bar$$());

And then apply tree-shaking. As long as the code doesn’t contain something like Object.keys(L) it should be fine.

At least that’s for the concept, maybe tools have better ways to do it. So I tried, webpack 3 + babel 7 beta. And it works. A very big function I put inside my lib.js only get exported if I use Object.keys(L)

With treeshaking working with star imports, are there still benefits of the proposed syntax?

# Isiah Meadows (7 years ago)

And Rollup already does this (although with less dramatic renaming).

# Jordan Harband (7 years ago)

If you intend to use both Rollup and import *, be aware that when rollup's current approach, as I understand it, is either a) it statically knows with confidence which names off a ModuleRecord you use, and treeshakes the rest out; or b) it knows with confidence that you're reflecting on the ModuleRecord, and treeshakes nothing, or c) it's not certain, so it treeshakes out named exports that you may be relying on.

# Isiah Meadows (7 years ago)

Could you give examples of c)? (I'm aware of direct eval, but what else?)

# Michał Wadas (7 years ago)

Isn't direct eval working like this only in sloppy mode (and modules are always strict)?

# Isiah Meadows (7 years ago)

Direct eval also works in strict mode. Such restrictions are used for things like CSP, not language safety/sanity.