Typescript module systems on momentJS behaving strangely
I resolved the issue by replacing
import moment from "moment";
import statement with
import * as moment from "moment";
this.
I did the following:
I installed moment
definition file as follows:
tsd install moment --save
Then I created main.ts:
///<reference path="typings/moment/moment.d.ts" />
import moment = require("moment");
moment(new Date());
And I ran:
$ tsc --module system --target es5 main.ts # no error
$ tsc --module commonjs --target es5 main.ts # no error
main.js
looks like this:
// https://github.com/ModuleLoader/es6-module-loader/blob/v0.17.0/docs/system-register.md - this is the corresponding doc
///<reference path="typings/moment/moment.d.ts" />
System.register(["moment"], function(exports_1) {
var moment;
return {
setters:[
function (moment_1) {
// You can place `debugger;` command to debug the issue
// "PLACE XY"
moment = moment_1;
}],
execute: function() {
moment(new Date());
}
}
});
My TypeScript version is 1.6.2.
This is what I found out:
Momentjs exports a function (i.e. _moment = utils_hooks__hooks
and utils_hooks__hooks
is a function, that's quite clear.
If you place a breakpoint at the place I denoted as PLACE XY
above, you can see that moment_1
is an object (!) and not a function. Relevant lines: 1, 2
TL;DR: To conclude it, the problem has nothing to do with TypeScript. The issue is that systemjs does not preserve the information that momentjs exports a function. Systemjs simply copy properties of the exported object from a module (a function is an object in JavaScript too). I guess you should file an issue in systemjs repository to find out if they consider it to be a bug (or a feature :)).
Here is how I did with System.js and Typescript 1.7.5
import * as moment from "moment";
...
moment.utc(); // outputs 2015 (for now).
But note that I am using utc()
method. I cannot use moment()
because as mk. has explained, this is converted into moment.default()
by System.js. Of cause Definitely Typed typings do not contain default
method, so to avoid compilation error one would need to use something like moment["default"]()
(I know, ugly).
Next step, I needed to add the following to System.js config:
System.config({
paths: {
'moment': 'node_modules/moment/moment.js'
}
});
After this, all worked as a charm.
This is happening because SystemJS is automatically converting moment
to an ES6-style module by wrapping it in a module object, while CommonJS is not.
When CommonJS pulls in moment
, we get the actual moment
function. This is what we've been doing in JavaScript for a while now, and it should look very familiar. It's as if you wrote:
var moment = function moment() {/*implementation*/}
When SystemJS pulls in moment
, it doesn't give you the moment function. It creates an object with the moment function assigned to a property named default
. It's as if you wrote:
var moment = {
default: function moment() {/*implementation*/}
}
Why does it do that? Because a module should be a map of one or more properties, not a function, according to ES6/TS. In ES6, the convention for massive external libraries that formerly exported themselves is to export themselves under the default
property of a module object using export default
(read more here; in ES6/TypeScript, you can import functions like this using the compact import moment from "moment"
syntax).
You're not doing anything wrong, you just need to pick the format of your imported modules, and stick to your choice. If you want to use both CommonJS and SystemJS, you might look into configuring them to use the same import style. A search for 'systemjs default import' led me to this discussion of your issue, in which they implement the --allowSyntheticDefaultImports
setting.