ES Modules: suggestions for improvement
David Bruant
bruant.d at gmail.com
Tue Jun 26 11:44:05 PDT 2012
Le 26/06/2012 16:44, David Bruant a écrit :
> Also relevant to this thread, post on the same topic by Isaacs
> (node.js lead) : http://blog.izs.me/post/25906678790/on-es-6-modules
"Furthermore, |let| already gives us destructuring assignment. If a
module exports a bunch of items, and we want several of them, then do
|var {x,y,z} = import 'foo'| or some such."
=> Excellent idea. That combined with the single export idea reduces the
amount of new syntax to introduce.
The Module proposal has a local renaming feature which I think should be
kept:
Initial proposal:
import { draw: drawShape } from shape;
import { draw: drawGun } from cowboy;
Could become:
let {draw: drawShape} = import './shape.js'
let {draw: drawGun} = import './cowboy.js'
I would actually reverse the order:
let {drawShape: draw} = import './shape.js'
let {drawGun: draw} = import './cowboy.js'
But that's a matter of taste.
By the way, the local renaming for destructuring is relevant regardless
of modules.
function f(point1, point2){
let {x1:x, y1:y} = point1,
{x2:x, y2:y} = point2;
// ...
}
I'd like to answer on this post proposal as it brings some interesting
points, but also raises some questions:
3) Loader.define(<path>, <program text>) defines a module at the
specified <path>, with the <program text> contents. That <program text>
is statically analyzed for any import statements.
=> I don't understand this part. Why would you need to define a module
at a specified path? Either there is a JS file at this path already or
there is none, no?
4) Whenever an import <path> is encountered in <program text> then the
Loader.resolve(requestPath, callerPath, callback) is called. This method
should return a fully qualified path. If this method returns boolean
true, then it will not be considered resolved until the callback is
called. (The argument to the callback is the string path.) If it does
not return true, and does not return a string path, then this is an
error, and throws.
=> If syntax calls the Loader.resolve method, I don't understand who
sets the callback.
Regardless, Loader.resolve(requestPath, callerPath) seems like it could
be a synchronous operation without too big of a performance penalty.
I disagree with the idea of calling the dynamic value of Loader.resolve
on syntax. We have seen that this resulted in potential attack (like
when the dynamic Array constructor was used in Chrome's JSON.parse).
"For security, the Loader object could be frozen with |Object.freeze| to
prevent additional changes."
=> This is not enough. People shouldn't have to opt-in for security,
mostly because they don't do it. I woud call for security by default
here and having "import <path>" call the built-in Loader.resolve instead
of the dynamic one.
If people want to override the Loader API, they would have to forget
about syntax. Or a new syntax could be introduced, making clear that
it's dangerous. Maybe something like "importDyn".
5) Once a module is resolved to a full path string, then
Loader.load(fullPath, callback) is called. callback should not be called
until Loader.define(fullPath, contents) is called. This should be called
at most once for any given fullPath. (Is the callback even necessary?
Why not just wait for Loader.define and throw any errors encountered?)
=> I guess I understand better Loader.define, but I intuit that the API
could be reworked to remove it.
6) The Loader.main(fullPath) method executes the module referenced by
fullPath (which must have already been defined), as well as evaluating
each of the modules that it imports.
=> It seems that the method should be called Loader.load, here, but
that's a nitpick
7) Within a module, the export <expression> statement marks the result
of <expression> as the exported value from the module. There can be at
most one export statement in a module, and the exported expression is
the module's export. To export more than one thing, export an object
with more than one thing on it.
=> This and destructuring. I love the combinaison.
Modules export a single value. Exporting a second time throws.
=> This can even be made a parse-time error.
Maybe this is not a valid cause for syntax addition. I'm not sure.
There are hairy problems around cyclic dependencies, so it's worth at
least having the option to address with static magic that has not yet
been fully imagined.
8) The global object within a module context is equivalent to
Object.create(<global>) from the main global context. (The important
thing is that leaks aren't leaky outside the module, but for example, x
typeof Error still works, because it uses the same Error function.)
9) If a module does not contain an export statement, then its global
object is its export. This is to provide support for legacy modules that
create a global object (such as jQuery) rather than using an export
statement. (Too magical? Probably. Also, what about having exports
inheriting from global is weird. Is there a simpler way to make existing
libs place nicely with this approach?)
=> Making the global scope of a file the implicit export object sounds
like an excellent idea. I concur that Object.create(<global>) may not be
the best idea. Recently, Allen introduced different ideas to deal with
the global environment [1]. Maybe there are things to leverage here.
Still, not having syntactical "export" statements requires execution to
know what is being exported and may delay error detection. Maybe that's
a good thing to have both: either benefit from some early-error
mechanism or painlessly leverage existing code.
David
[1] https://mail.mozilla.org/pipermail/es-discuss/2012-May/022627.html
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120626/d12e9df3/attachment-0001.html>
More information about the es-discuss
mailing list