Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

import {x,y} as v from "mod"; #11

Open
nlwillia opened this issue Mar 18, 2016 · 9 comments
Open

import {x,y} as v from "mod"; #11

nlwillia opened this issue Mar 18, 2016 · 9 comments

Comments

@nlwillia
Copy link

I'd like to suggest an additional as v extension on the import side.

The syntax would be:

import {x,y} as v from "mod";

The semantics would be to create a plain object v and populate it with properties x, y (etc.) resulting in v = {x:x, y:y} (or v = {vx:x, vy:y} if x/y are also aliased).

The reasoning is to facilitate static analysis and tree-shaking when plucking individual exports from libraries like lodash. If I import _ from "lodash"; then I get all of lodash. If I import {x, y} from "lodash"; (where x and y are specific lodash functions like debounce or omit) then a smart loader or builder can ensure that only those functions and their dependencies are included in the resulting page or build.

The current {x, y} already works, but it clutters my module with names that I'd rather encapsulate. I can use {x as _x, y as _y} (or whatever renaming scheme makes sense), but this is verbose and still produces a lot of top-level names. If I could import {x, y} as _ from "lodash"; then I'd have a top-level _ variable with everything I wanted and nothing I didn't.

@caridy
Copy link
Collaborator

caridy commented Mar 19, 2016

@nlwillia the main issue is that {} in import and export statements is not destructuring, it is just a descriptor, and changing that is a fundamental shift.

@nlwillia
Copy link
Author

It would still be hoisted, and the properties would still be views, so it's not an attempt to conflate imports with destructuring but to introduce selective structuring to keep plucked names out of the top-level namespace.

From the perspective of symmetry, an export {x,y} as name; syntax could also be a useful shorthand for const name = {x,y};export {name};

It's syntactic sugar, but I think that as we shift to the ES6+ model for modularity this sort of selective import concern is going to be increasingly important to facilitate cleanly. (This proposal project looked like a good place to discuss import concerns, but if there's a better place for it, let me know. And I should make it clear that I'm not hung up on this particular mechanism...it's just something that seems like it could be improved and I'd be glad to hear other suggestions.)

@leebyron
Copy link
Owner

Thanks for the suggestion and the idea! I agree with the problem and your solution is pretty interesting. I'll take it into consideration!

@matthewrobb
Copy link

@caridy Perhaps this type of syntax could produce a binding namespace object? In fact I would love to be able to declare binding namespaces outside of modules!

const foo.bar = "baz!"; //  generates an exotic namespace object bound to 'foo' in the current scope

@caridy
Copy link
Collaborator

caridy commented Mar 21, 2016

my personal opinion here is: exporting an object with a well defined shape is not a primary use-case, and in fact I will probably discourage its use as part of your module API, why not just adding more named exports? Remember, our goal with the module grammar is to make it easier for common, well defined cases, and not so easy for those bad patterns.

@nlwillia
Copy link
Author

Something that was not clear to me (but probably should have been) when I suggested this is that static analysis can't just go by exports and imports because a loaded module could have external side-effects that it does not export. This means that even modules like lodash-es that are optimized for selective usage are having trouble getting the full benefit with some syntax forms. That will hopefully improve in time, but it's a lot harder than it would be if there were a way of declaring a "weak reexport" for a child module known to be safe for static usage-based elimination.

@philholden
Copy link

@caridy just independently posted the same suggestion to @leebyron with same reasoning:

https://gist.github.com/philholden/25618771eed1e0fd58437f7af2fb8005

And he made me aware of your issue. I am finding this pattern would be very helpful in Redux apps as I struggle to find different names for constants and actions when most of the time they are doing CRUD. So I want to be able to do:

// todo-actions.js
export const CREATE = 'TODO.CREATE'
export const UPDATE = 'TODO.UPDATE'
export const CREATE_SUCCESSFUL = 'TODO.CREATE_SUCCESSFUL'
export const UPDATE_SUCCESSFUL = 'TODO.UPDATE_SUCCESSFUL'

//todo-reducer.js
import { CREATE_SUCCESSFUL, UPDATE_SUCCESSFUL } as TODO from './todo-actions'

reduce(state, action) {
  switch(action) {
    case TODO.CREATE_SUCCESSFUL: ...
    case TODO.UPDATE_SUCCESSFUL: ...
  }
}

In reality I would probably namespace actions further with npm-package-name to give globally unique actions:

export const CREATE = '@philholden/todo-redux/TODO.CREATE'

@caridy
Copy link
Collaborator

caridy commented Apr 25, 2016

@philholden I can definitely give more details about why I think this is

  • namespaces are 1-1 mapping to existing module records. Each module record contains a namespace object that implements sort-of a proxy, with a [[Get]] internal slot described here: https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver
  • this proposal will requires the creation of arbitrary namespace objects, in the example above, TODO will have to be a namespace object of some sort since its members can't be mutated from consumers. These namespace exotic objects are not particularly expensive IMO, but they are not as lightweight as regular objects.
  • accessing a member of a namespace is a very expensive operation IMO, as you can see in here, for every [[Get]] action on a namespace, there will be a lookup process of the named export to locate the final module that export the primitive value, then a GetBindingValue on the Environment Record of the exporter, and this will happen on every access. Obviously, with the time, engines will optimize this process, they always do, but for now, this is expensive.

A couple of questions:

  • what will be the reflective api for such new grammar?
  • can you re-export one of these namespaces? today you can only do export * from "foo".

@philholden
Copy link

philholden commented Apr 27, 2016

@caridy Thanks for reply.

For your two questions I've no strong opinions. I am not sure what the reflective API is. I have not used reflection yet in JS so might not be qualified to comment. Or do you just mean export? In which case probably the same as @nlwillia:

From the perspective of symmetry, an export {x,y} as name; syntax could also be a useful shorthand for const name = {x,y};export {name};

However if the import is exotic the export should be too. The alternative would be to have {} as and [] as being a kind of destructured import that immediately assigns import things to a regular array or object. This is fine for my usecase but inconsistent with the way * as works.

So probably the most consistent thing to do is have them being exportable exotic objects. But for the time being I only see import {} as as being useful for tree shaking, I cannot see a strong use case for export {} as.

Without export { add, remove } as foo we can do: export const foo = { add, remove }

But for import { add, remove } as foo from './foo' we need to do this to get the same functionality:

import {
  add as fooAdd,
  remove as fooRemove
} from './foo'

const foo = {
  add: fooAdd,
  remove: fooRemove
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants