-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
SystemJS and default
import with export =
proposal
#5285
Comments
Here's my understanding of the problem. import * as $ from "jquery";
console.log($ === window.$); will output So the type definition needs to be something like: declare module "jquery" {
global.$ = ...;
} The above code is not valid but is a proposal here I'm hoping that #4665 is also going to address how to make the following a compile error given such a type definition: import * as $ from "jquery"; (unless of course But equally throw compile errors when |
@myitcv I wasn't talking about global scope here, at least not intentionally. jQuery rolls its own UMD and I was intending to take advantage of that. To eliminate global pollution as a source of this confusion, instead of import * as jq from 'jquery';
console.log(jq.isFunction(jq)); // true with --module amd or commonjs, false with --module system The above ES6-style import "works correctly" (logs The ES6 default import code is currently a TypeScript error. My proposal is to allow it when the module style is Edit: I previously stated that |
I run into a similar issue when using Babel to transpile since Babel does the fancy import checking which allows something like |
@nycdotnet - got it, thanks for clarifying. The more I learn about modules, the more I realise how much I don't know.... Indeed I ran into what I think is a similar problem. I'm outputting But my conclusion wasn't as complete as yours:
👍 from me on this proposal |
Yes you hit the same issue with d3 and SystemJS. It seems a bit of improved documenation for importing modules that don't provide a default with SystemJS may be in order at a minimum. Hey, we're all learning! This just came to my attention while putting together some training content - so I had to keep staring at it instead of living in my happy RequireJS bubble. This is new stuff for everyone. :) |
To me, this seems like a problem to be solved at the library level rather than by a loader trying to be "smart" about it. I just ran into this in a project: we're compiling to CommonJS but loading things with SystemJS (Angular 2 application). I'm unable to use the default export because SystemJS blows away my default export. There's no reason for that because I'm writing an ES6-compatible CommonJS module, but because the loader guesses wrong about what I'm actually trying to do I can't. This means that libraries written for use with either SystemJS or an AMD loader won't be able to use default exports when loaded with SystemJS, which is quite silly. |
Shouldn't this be a loader feature flag, especially when the loader introduces a breaking change trying to "help" people. TypeScript chasing after a moving target of SystemJS to the detriment of everyone who isn't using SystemJS seems non-sensical. |
To be fair, it's not just SystemJS. Like I mentioned earlier, it's Babel as well. Even if it was a feature flag many people would turn it on because it's a useful feature and you'd be right back in the same boat with TypeScript not really supporting the paradigm well. The ES6 default export is meant to (sort of) take the place of CJS |
If you think of the default export that way, yes it sort of makes sense. The problem is that it's not really like it at all. I can have the following module: export class Column {
}
export default class Grid {
} And import things in the following ways: import Grid, { Column } from "./Grid"; // this is just sugar for the next line
import { default as Grid, Column } from "./Grid";
import * as grid from "./Grid"; // then use grid.default or grid.Column The third import is the closest to a pure CommonJS module import. But in ES6, there's no way to completely redefined the module's value like in AMD and Node's implementation of CJS. Using the module above, I can compile it to UMD (so it can be used in node, AMD, and [supposedly] SystemJS and Babel). The problem that @nycdotnet is having is that since SystemJS and Babel clobber It should also be noted that CJS never supported |
@bryanforbes I don't disagree with any of your points. The reality, though, is that this is currently a pain point. We are in a CJS -> ES6 transition phase. We want to write ES6 modules now and still be able to consume CJS (since most modules published to NPM are CJS). People are bound to build bridges. Since there is not a 1 to 1 mapping some decisions have been made with tradeoffs. That is the reality. It would be nice if TypeScript easily supported this reality with its definitions. This proposal is one way to help with that (maybe not the best way, but a way). At the very least it illustrates the issues. It's easy for us already in TypeScript-land to want to remain pure, but this can be a blocker to adoption. People are using SystemJS and Babel already (because they actually solve issues/make development easier), and they go to introduce TypeScript and run into stuff like this. |
@jbrantly It sounds like you and I, for the most part, are on the same page. I see the issue and it's definitely a problem. I think what's giving me pause is that while SystemJS and Babel have "solved" the issue in the short term for CJS, AMD, and UMD modules that existed before ES6 modules (like jQuery), they have broken CJS, AMD, and UMD modules written with ES6 in mind. I'd rather have a solution that works for everyone rather than one that only works for older modules and new format (SystemJS and ES6) modules. |
@bryanforbes Yea I don't think we're far apart. I think your issue is more with SystemJS. All I'm advocating for is a way for TypeScript to easily support definitions for libraries that are multi-targeted (and use a default export and/or |
For the record - I agree that a better solution would be a way to be able to specify in the SystemJS config "don't auto-detect default", and then to have a way to also specify "allow this ES6 import to be an 'is a' instead of just 'has a'" so that I'm glad I'm not the only one running into this. Relaxing this rule in TypeScript would be a convenient "quick win", but I admit it could certainly lead to a chase if the default feature were ever removed (we're perhaps already in that situation with |
Simply adding the |
Thanks for weighing in.
This is true. However, the current semantics of TypeScript allow this to work when transpiling to CommonJS or AMD format. The value brought into the local SystemJS does not allow this syntax to work. SystemJS does permit the default import syntax with ES5 libraries. However, this is equally incorrect* because jQuery doesn't have a default export. To allow the default import syntax to work with TypeScript 1.6 and SystemJS, we need to modify the type definition of jQuery to make it "lie" that jQuery has a default import even though it doesn't. *Note: I am saying "incorrect" in the strictest possible way regarding working to the spec - this is not intended to be pejorative or disrespectful of SystemJS or TypeScript in any way. I respect your work greatly. There's three things here: jQuery (or really any ES5 library), TypeScript and SystemJS. Since we can't change jQuery, we can only change the TypeScript code that uses jQuery, the TypeScript definition that describes jQuery, or SystemJS's behavior (perhaps via config). I don't think it's a good idea to change the jQuery definition because we still need it to work in the CommonJS and AMD scenarios. I've proposed a change here to allow TypeScript to work with SystemJS's behavior of automatically nominating a default export with ES5 libraries. This allows us to keep the TypeScript definition the same (we don't have to "lie" and say that jQuery has a default export when it doesn't) and it just means that when using SystemJS we have to use the default import syntax to consume a library that "is a" even though there technically isn't an export named default. I suppose an alternate approach could be a new meta property in the SystemJS config to make it allow particular imports to "be a" instead of strictly conforming to the spec and having the object always come in as a module namespace object. That would allow people to opt-in to allowing the import to be a function for particular libraries only. |
The strictest way to support interop is to treat all global, CommonJS and AMD modules as the module namespace object with a single default export taking the value of the module - A configuration option in SystemJS to inform that the I must admit my knowledge of TypeScript is quite limited, so that I'm not in a position to comment on the proposal above. Just let me know if I can provide any further help here at all. |
@guybedford Thanks for weighing in. I either didn't know or had forgotten about Module importsOld StyleGiven the following module: class Thing {
static staticMethod() { }
instanceMethod() { }
}
exports = Thing; It should be importable via the following syntax: import Thing from "./thing";
import Thing, { staticMethod } from "./thing";
import * as thing from "./thing"
// The third import could then be used as such:
var myThing = new thing.default();
thing.staticMethod(); ES6 StyleNo change would be necessary to the module import semantics Code GenerationOld StyleCode generation for old style modules would remain the same, however code generation for importing old style modules and old style ambient declarations would change. Given the following imports of an old style module: import Thing, { staticMethod } from "./thing";
new Thing();
staticMethod(); As well as the following: import * as thing from "./thing";
new thing.default();
thing.staticMethod(); Compiling to CommonJS, AMD, or UMD would produce JavaScript similar to the following: var Thing_1 = require("./thing");
new Thing_1();
Thing_1.staticMethod(); The output for compiling to SystemJS or ES6 would remain the same. ES6 StyleCode generation for ES6 style modules would need to be changed to add export default class Thing { }
export function thing () { } Compiling to CommonJS, AMD, or UMD would produce JavaScript similar to the following: var Thing = (function () { ... })();
exports.default = Thing;
function thing() { }
exports.thing = thing;
Object.defineProperty(exports, "__esModule", {
value: true
}); Compiling to SystemJS would need to add the Code generation for ES6 style modules would remain the same. @nycdotnet and @jbrantly, what do you think? |
I think one way we could solve this issue is by introducing a With this compiler option you'd be able to import jQuery either as import * as $ from "jquery"; or as import $ from "jquery"; You'd of course also still be able to import individual members import { ajax, getJSON } from "jquery"; With the BTW, I'm totally open to a better name than |
Thanks for the reply, Anders. I'm pleased that you'd be open to adding a new switch to accommodate this on the import side. I'll bike-shed I think it's entirely fair that if such a switch were provided, |
@weswigham care to send a PR for this? |
this should be handled with the new |
@weswigham @mhegazy - thanks very much for implementing this. |
- `default` export became a requirement recently. Most typescript library definitions have not been updated with a `default`. So a flag `allowSyntheticDefaultImports` was added to typescript to ignore errors related to this issue. - See microsoft/TypeScript#5285
I was recently running into some trouble with getting SystemJS to load jQuery using the ES6-style syntax.
The above code worked fine with RequireJS and AMD format, and when using SystemJS as the loader when my code was emitted by TypeScript in AMD format.
However I noticed odd behavior when emitting this code using the system format and using SystemJS. I was getting all of the static functions of
$
, but$
itself was an inert object (not a function). So at runtime,$.isArray([]);
would work, but$('#myDiv')
would throw "$ is not a function".Here's my original issue on the SystemJS repo: systemjs/systemjs#844
I kept digging around in the SystemJS documentation and eventually noticed that it referenced using a default ES6 import with jQuery:
Sure enough when I tried this, it worked, even though jQuery itself does not export a property
default
.After more research, I found this on the SystemJS site:
https://github.com/systemjs/systemjs/blob/master/docs/module-formats.md#inter-format-dependencies
So when using SystemJS, the "is a" is made available through a virtual default export.
However, this is a problem since there's not currently a way to model this in TypeScript without breaking existing code using
import $ = require('jquery')
syntax.Current jQuery definition on Definitely Typed:
Proposal
In light of the documented behavior of SystemJS, I'd like to propose a change to TypeScript to allow ES6-style default import syntax to work with an ambient external module declaration that uses
export =
syntax, as long as the module format is "system".With this change, this code would compile cleanly if TypeScript is set to "system", but would still error using "amd", "commonjs" or "umd" with "Module 'my-module' has no default export":
my-module.d.ts
test.ts
Related:
#4337
#2719
CC: @vvakame @basarat @johnnyreilly @jbrantly @guybedford
The text was updated successfully, but these errors were encountered: