Typescript module systems on momentJS behaving strangely
Asked Answered
G

9

34

I'm trying to use momentJs from typescript: depending on what module system I'm using to compile typescript, I find a different behaviour on how I can use momentJs. When compiling typescript with commonJs everything works as expected and I can just follow momentJs documentation:

import moment = require("moment");
moment(new Date()); //this works

If I use "system" as typescript module system when I import "moment" I am forced to do

import moment = require("moment");
moment.default(new Date()); //this works
moment(new Date()); //this doesn't work

I found a workaround to make them both work regardless of typescript module system used

import m = require("moment")
var moment : moment.MomentStatic;
moment = (m as any).default || m;

I don't like this, and I would like to understand why it behaves like this. Am I doing something wrong? Can anybody explain me what's happening?

Guard answered 7/10, 2015 at 8:24 Comment(6)
import moment from 'moment'; seems to work, though the compiler displays a "has no default export" error (but compiles anyway). Also, see this discussion: github.com/Microsoft/TypeScript/issues/…Formalism
Also, this blog post is useful: jbrantly.com/es6-modules-with-typescript-and-webpackFormalism
For me moment.default doesn't exist any way I import it, so I actually don't know how to create an instance of a moment object. Anyone have an updated answer to this?Peru
Did you manage to find a proper solution? .. I'm having the same issue with SystemJS module system ...Cuneate
i'm still using the workaround :(Guard
have you tried import { moment } from 'moment'? this should get rid of the 'has no default export' error, and might just work as well.Andriette
P
22

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.

Plenipotent answered 8/12, 2015 at 23:28 Comment(2)
Excellent, thanks for the detailed and clear explanation. Well worth the bounty!Aquilar
--allowSyntheticDefaultImports is implemented in 1.8 which is now in betaGuard
S
39

I resolved the issue by replacing

import moment from "moment";

import statement with

import * as moment from "moment";

this.

Savona answered 19/1, 2021 at 5:4 Comment(0)
P
22

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.

Plenipotent answered 8/12, 2015 at 23:28 Comment(2)
Excellent, thanks for the detailed and clear explanation. Well worth the bounty!Aquilar
--allowSyntheticDefaultImports is implemented in 1.8 which is now in betaGuard
F
5

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 :)).

Firn answered 3/12, 2015 at 14:25 Comment(4)
error is not at compile time, but when you try using it. it seems that using system makes you find an additional field called default to be used. if you use commonjs, moment.default is undefined and you must use moment directlyGuard
Thanks, I tried to debug your issue and I have updated my answer accordingly. HTHFirn
It's not copying properties, it's wrapping moment in a module object.Plenipotent
@Plenipotent Thank you for clarification.Firn
S
4

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.

Steapsin answered 19/12, 2015 at 9:56 Comment(1)
I just wanted to say thanks, I don't use SystemJS (I use CommonJS as module in the tsconfig), but adding moment to the paths of my tsconfig did fix an issue I was having with a library I was using (react-day-picker, which uses moment.js), so thanks a lot, finally got it to work because of this :D FYI; The error I encountered was the following; MomentLocaleUtils.js:68 Uncaught TypeError: (0 , _moment2.default) is not a functionAbdicate
C
2

Moment was a pain to pull into the project that I'm working on, but we ended up solving it using this:

import momentRef = require('moment');
var moment: moment.MomentStatic = momentRef;
Chewink answered 7/10, 2015 at 11:50 Comment(3)
thanks for your answer, but even with that, using systemjs as module system, here is the error message: Uncaught (in promise) TypeError: moment is not a function. Same as beforeGuard
Sorry it didn't help you out.... I missed the systemjs part... my solution is utilizing commonjsChewink
@Chewink you might clean up your project by using either const moment: moment.MomentStatic = require("moment"); or import * as moment from "moment"Plenipotent
P
1

If you are using typescript, a default import like import moment from "moment" acts the same as const moment = require("moment").default. See https://www.typescriptlang.org/tsconfig#esModuleInterop

TLDR; add esModuleInterop: true in your tsconfig.json

Paramnesia answered 27/9, 2021 at 11:59 Comment(0)
P
0

For my system.config:

paths: {
    'moment': 'node_modules/moment/moment.js'
},
packages: {
    app: {
        format: 'register',
        defaultExtension: 'js'
    }
}

Importing momentjs in my component I removed the * which I think treats the code in the moment.js file as multiple objects.

Change:

import * as moment from 'moment';

to:

import moment from 'moment';
Prototherian answered 17/3, 2016 at 16:37 Comment(0)
C
0

I had

import * as moment from 'moment';

and changed everything that I thought should be

var date: moment = moment();

to

var date: moment.Moment = moment();

Canterbury answered 19/5, 2016 at 22:29 Comment(0)
H
0

If you are using TypeScript , import * as moment from 'moment is working , but In SonarQube is critical error as Explicitly import the specific member needed.

On the principle that clearer code is better code, you should explicitly import the things you want to use in a module. Using import * imports everything in the module, and runs the risk of confusing maintainers. Similarly, export * from "module"; imports and then re-exports everything in the module, and runs the risk of confusing not just maintainers but also users of the module.

Hygrometry answered 17/11, 2022 at 10:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.