import { foo, bar } as obj from 'module
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.
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
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.
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!
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.
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?
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.
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.
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.
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.
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.
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.
... 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
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:
- 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
- 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).
- Will this new syntax have symmetry with the export statement as well? e.g.:
export { x, y } as foo from "foo";
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:
- foo1 === bar1 is false, no exotic object whatsoever. It's a namespace with getters (that could eventually be enriched a part)
- no need for exotic objects
export { x, y } as foo from "foo";
will export afoo
object withx
andy
accessors. It'd be possible toimport {foo} from "foo"
so the answer to the third point is yes.
-
(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 expectimport { 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. -
(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.
-
(Export symmetry) That’s interesting — yeah, it would make sense to honor the contract;
import * as ns from "foo";
is permitted, andexport { foo, bar } as baz;
would not be equivalent toexport 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)
inline
On Dec 14, 2017, at 2:29 PM, Darien Valentine <valentinium at gmail.com> wrote:
- (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 expectimport { 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.
- (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.
- (Export symmetry) That’s interesting — yeah, it would make sense to honor the contract;
import * as ns from "foo";
is permitted, andexport { foo, bar } as baz;
would not be equivalent toexport 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.
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
@Andrea I think we are thinking along the same lines. I meant proxy with a lowercase p — what you described fits the bill.
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.
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.)
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"
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
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.
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.
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
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?
And Rollup already does this (although with less dramatic renaming).
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.
Could you give examples of c)? (I'm aware of direct eval
, but what else?)
Isn't direct eval working like this only in sloppy mode (and modules are always strict)?
Direct eval also works in strict mode. Such restrictions are used for things like CSP, not language safety/sanity.
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
andasync
both export amap
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:
import { map as lodashMap } from 'lodash'; import { map as asyncMap } from 'async';
import * as _ from 'lodash'; import * as async from 'async';
Neither option is ideal:
lodashLastIndexOf
vslodash.lastIndexOf
).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?