Repository navigation
Proposal: new ES6 style syntax to apply typeof on an ambient external module #2357
Description
As a continuation to #2346 I decided to re-target the discussion specifically with the new ES6 module syntax (#2242) in mind, and open it as a new issue (my apologies if I got some of the ES6 module semantics wrong here, as it's still very new to me).
I propose the following syntax to get the type of an ES6 (or the older variant of an) ambient external module, or some specific export of it. There are two versions, where the second one (my personal preference) is more succinct and readable as it omits the import keyword. Here are some examples (In many cases I'm still not sure of which version is "best" so I provided multiple alternatives):
The syntax (1st version)
Get entire namespace type:
// This looks straightforward:
type FileSystemNamespaceType = typeof (import * from "FileSystem");Get specific export type:
// This seems straightforward
type FileReaderType = typeof (import FileReader from "FileSystem");
// Or maybe this would parallel the ES6 syntax better?
type FileReaderType = typeof (import {FileReader} from "FileSystem");Get default export type:
// Straightforward (assuming "default" couldn't be used as a name of an ES6 export
// there shouldn't be any conflict)
type FileSystemDefaultExportType = typeof (import default from "FileSystem");
// However ES6 spec puts the "default" keyword in curly brackets? - that's weird..
type FileSystemDefaultExportType = typeof (import {default} from "FileSystem");
// Or maybe this is better?
type FileSystemDefaultExportType = typeof (import "FileSystem");Some import expressions don't really make sense when a (single) type is needed:
// The "as" doesn't make much sense here:
type FileReaderType = typeof (import {FileReader as MyFileReader} from "FileSystem");
// Multiple exports don't make much sense here:
type ImpossibleType = typeof (import {FileReader, FileWriter} from "FileSystem"); It would also be very useful to use it directly as a type within a regular variable declaration, like this:
var FileReader: typeof (import FileReader from "FileSystem");I think this looks quite reasonable, and should be relatively easy to parse. Though it could be made even simpler..
The syntax (2nd version)
The import keyword could even be omitted entirely to yield a more succinct and natural syntax (that's still not very difficult to parse). Examples:
// Straightforward:
type FileSystemNamespaceType = typeof (* from "FileSystem");// Straightforward:
type FileSystemDefaultExportType = typeof (default from "FileSystem");
// Or in curly brackets:
type FileSystemDefaultExportType = typeof ({default} from "FileSystem");// Straightforward:
type FileReaderType = typeof (FileReader from "FileSystem");
// Or more akin to the ES6 syntax:
type FileReaderType = typeof ({FileReader} from "FileSystem");Example use cases
Since typeof only gets the type information (or symbols) for the module exports, it should be usable in any scope, including within internal modules, functions, conditional statements or even loops. This would allow to attach type information to a dynamically or conditionally loaded module, such as in:
if (runningInNodeJS())
var FileSystem: typeof (* from "fs") = require("fs");or even used with a frequent CommonJS import pattern where only a specific member is dynamically loaded from a module:
if (runningInNodeJS())
var fileReader: typeof ({FileReader} from "fs") = new require("fs").FileReader();or used to load a replacement or polyfill for some module or export that has a different name, but conforms to the same type specification, essentially using the type as an interface:
var genericFileReader: typeof ({FileReader} from "fs");
if (usePolyfill)
genericFileReader = new FileReaderPolyfill();
else
genericFileReader = new require("fs").FileReader();Your comments
I'd be interested in your opinions on this? and in case the language designers have different ideas for a syntax to get this sort of type information, I'd like to hear all about it!
Activity
[-]Proposal: new syntax to get typeof for an ES6 module import declaration[/-][+]Proposal: new ES6 style syntax to apply typeof on an ambient external module[/+]ahejlsberg commentedon Mar 23, 2015
We've heard requests for something like this many times now and it has a lot of merit. I would aim to keep it a relatively simple feature, basically an
import"operator" that can be used withtypeof:We already allow dot to select members in a
typeofargument. I think we should stay with that, but perhaps require parens around theimportoperator to make the precedence look right:rotemdan commentedon Mar 23, 2015
I think that looks quite reasonable (not sure if
typeof (import "FileSystem").*would be valid but that also looks acceptable). I would personally prefer to usetypeof (import "FileSystem")(with the parenthesis) in the first example though, as it would appear clearer to me (might not be everyone's preference). One problem may be that it is ambiguous between adefaultand a*import, but I'm not sure if that's a significant issue (the whole ES6 module design is still new to me, so I'm uncertain to what degree the concept of default imports would actually be useful or common in practice, I mean, perhaps in most cases adefaultimport would be the same as a*import anyway?).However, have you considered using the
modulekeyword instead ofimportthough? I think it would be clearer (module "FileSystem"also parallels the ambient external module declaration syntax, so it intuitively connotates a more abstract and type-oriented semantics), e.g.The problem with
importis that in English it is more commonly used as a verb (though less frequently as a noun, but I don't think most people would read it this way here). An analogous English expression would read like, for instance(your proposal), or
(my first version), instead of the more grammatically correct
e.g. the syntax:
(my second version), though under the mind-boggling ES6 semantics,
FileReader, without curly brackets, would actually imply adefaultimport here! (also, viewing it like this, it seems that the wordinmay read less ambiguously thanfromhere, but wouldn't work that well withdefaultand*though).Nevertheless having the module name first may be a big advantage for editor auto-completion and would allow quicker type-checker feedback on the module name, so I would understand why an alternative might be preferred (and now that I considered your approach, I actually like it better, but only when used with the
modulekeyword, as I mentioned above).Anyway, whatever syntax is chosen, I'm glad this feature is being considered as being able to easily extract types from external module declarations would be a highly useful feature for myself, and possibly others. I develop libraries that are mostly targeted to work both on the web and in Node, so I almost always load modules conditionally and at an as-needed basis (a pattern that is very frequent in CommonJS development - even the Node manual uses it extensively). And although having spent much time using languages like C# and C++, it seems that being imposed an (artificial, in many cases) requirement to "statically" declare module imports ahead of time has began to appear a bit outdated and rigid to me, and frankly not very characteristic of the more dynamic style of JavaScript.
Seeing the freedom and flexibility the dynamic approach gives, I would actually go as far as saying it would be interesting to consider [TypeScript] allowing modules to be ["officially"] imported everywhere, including, for example, in conditionals and loops (CommonJS/Node has a caching system, so the module will only be loaded once), however, ES seems to have gone in a different direction, but again I don't really have the knowledge and foresight to analyze that right now as I currently don't know a lot about it (and of course, haven't really used it practice).
rotemdan commentedon Mar 26, 2015
I tried to write a proposal to continue the (diverging) discussion in a different issue (mostly to express my ideas in a more coherent and organized way and to offer some of my own suggestions).
See:
"Proposal: extend legacy module import syntax to a fully dynamic module loader with polyfills" (#2508)
RyanCavanaugh commentedon May 4, 2015
Need more compelling use cases than "not wanting an intermediary import" to justify complicating the syntax here.