29

I'm trying to follow the official AoT guide for Angular 2, and I'm using Moment.js in my application. Moment.js is on my packages.json file, and I'm using version 2.15.0. I've been importing it like this so far:

import * as moment from 'moment';

But when I get to the part where I have to run rollup, I end up with the following error:

Cannot call a namespace ('moment')

Which appears to be related to the way I import moment according to this. So, how am I supposed to do this? I can't seem to import moment any other way. If I use

import moment from 'moment'

I get the compile error

External module ''moment'' has no default export

Vitor Machado
  • 405
  • 4
  • 9
  • were you able to figure this out? – jjj Sep 23 '16 at 21:59
  • +1 Having the same issue with `import * as HighchartsMore from "highcharts/highcharts-more"; HighChartsMore(Highcharts)` – Tair Oct 27 '16 at 05:08
  • I am also stuck on the same error. Any progress on your side 1 month later? – j3r6me Oct 31 '16 at 15:13
  • I had problems too with loading moment.. see this answer, maybe it can help: http://stackoverflow.com/a/35350484/1354222 – 11mb Nov 22 '16 at 10:09

9 Answers9

22

I have finally managed to get rid of both errors. Indeed to avoid:

Cannot call a namespace ('moment')

You need to use:

import moment from 'moment'

Then to avoid

"moment" has no default export

You have to add into your tsconfig.json (compilerOptions):

"allowSyntheticDefaultImports": true

EDIT 17/11/2016

I also had to add the following to my rollup-config.js file:

plugins: [
  nodeResolve({jsnext: true, module: true}),
  commonjs({
    include: [
        'node_modules/rxjs/**',
        'node_modules/moment/**'
      ]
    }
  }),
  uglify()
]
j3r6me
  • 798
  • 1
  • 5
  • 21
  • 2
    This works for a AoT rollup config but does NOT work using systemjs in debug/dev build mode. – Mark Perry Nov 23 '16 at 10:58
  • 1
    @MarkPerry yes I am trying still trying with SystemJS and Webpack as well. But this thread was about rollup. I will let you know if I find something for SystemJS. – j3r6me Nov 28 '16 at 07:20
  • 1
    Marked this as accepted answer because it fixes AoT, but I really needed a solution that would work for both AoT and JIT without modifications... – Vitor Machado Feb 06 '17 at 15:58
  • 2
    It does not work: import moment from 'moment' will give the error moment_1.default is not a function at runtime with typescript 2.1.6. – Anthony Brenelière Feb 16 '17 at 09:46
  • for AoT I ended up having two different rollup config files; one for JIT and the other for AoT. I also do not use SystemJS and it works fine for me. –  Aug 21 '17 at 00:34
13

I found a nice solution for the problem at hand:

Npm-install additional package moment-es6 which provides a default export. Then import from 'moment-es6' instead of from 'moment':

import moment from 'moment-es6';

  • For use with systemjs, add the following to the systemjs.config.js map section:
    'moment-es6': 'npm:moment-es6/index.js'

  • add 'node_modules/moment-es6/**' to the include's of your rollup configs commonjs section (rollup-plugin-commonjs)

akaegi
  • 131
  • 1
  • 3
  • Interesting, this worked for system js and typescript (without additional compiler flag), but rollup is giving me an error: ` 'default' is not exported by node_modules\moment-es6\index.js` – Sean Newell Aug 17 '17 at 20:45
  • I think I just need to specify the [named export](https://github.com/rollup/rollup-plugin-commonjs#custom-named-exports) - right? – Sean Newell Aug 17 '17 at 20:46
7

Here is what I did to make work moment with typescript (at 2.1.6) and rollup (0.41.4).

  1. To import moment, keep the standard way:

    import * as moment from 'moment';

import moment from 'moment'; is non-standard for a package without a default export, it will result an error at runtime: moment_1.default is not a function

  1. In typescript use moment with by casting moment as any, and call the default function:

    var momentFunc = (moment as any).default ? (moment as any).default : moment;
    var newFormat = momentFunc(value).format( format );
    

moment(value).format(format) will result an error at rollup tree shaking: Cannot call a namespace ('moment')

Anthony Brenelière
  • 60,646
  • 14
  • 46
  • 58
  • 2
    Thank you! This have worked for me. `import * as momentLib from "moment";` `const moment = (momentLib as any).default ? (momentLib as any).default : momentLib;` – Rod Ferreira Nov 29 '17 at 14:08
5

We had a similar issue with ng-packagr which uses rollup to generate a module that can be published in an npm repo. Our project was built-up using @angular-cli (using webpack).

We have 2 dependencies that are imported using the asteriks method:

 import * as dataUrl from 'dataurl';

Worked fine, is used like:

 dataUrl.parse(url)

Another import gave the error (Cannot call a namespace) because the exported object is to be used as a function:

 import * as svgPanZoom from 'svg-pan-zoom';
 svgPanZoom(element); <== error: Cannot call a namespace

We could workaround this by assigning the exported initializer function to another const and use that in the code:

 import * as svgPanZoomImport from 'svg-pan-zoom';
 const svgPanZoom = svgPanZoomImport;

 svgPanZoom(element);

We also made the tsconfig.json config change as described above.

Versions: ng-packagr: 1.4.1 rollup: 0.50.0 typescript: 2.3.5 @angular/cli: 1.4.8 webpack: 3.7.1

Hope this help,

Rob

Rob Gansevles
  • 143
  • 1
  • 10
3

I was having the same problems described above.

import * as moment from 'moment'; - worked when developing and loading via systemjs, but not during rollup.

import moment from 'moment'; - worked in a rollup build but not during development.

To avoid having to change code depending on build type, I just added moment as a global and created a helper function that I import everywhere I need to use it instead of importing moment.

This means the same code works for both types of scenarios. It's not particularly pretty though, if there's a better way please let me/us know!

Here's the helper function, added to it's own file momentLoader.ts

import { default as mom } from 'moment';
export default function moment(args?: any): mom.Moment {
    let m = window["moment"];
    if (!m) { 
        console.error("moment does not exist globally.");
        return undefined;
    }
    return m(args);
}

To use moment in other classes I just import the function and call it as if I'd imported moment directly:

import moment from '../../momentLoader';

let d = moment().utc("1995-12-25");

let m = moment("1995-12-25");

To get systemjs to load it as a global, I just followed these steps. http://momentjs.com/docs/#/use-it/system-js/

In my case the moment config for systemjs looks like this:

let meta = {
    'lib:moment/moment.js': { format: 'global' }
};

System.config({
    paths: paths,
    map: map,
    packages: packages,
    meta: meta
});

System.import('lib:moment/moment.js');

For the rollup build you'll have to make sure moment.js is added to the page somewhere via a script tag, as it won't get included in the rollup build file unfortunately.

Nick Cameron
  • 129
  • 1
  • 1
  • 1
    I have the same problem as you and I really don't understant why everybody is not complaining about this... Creating an extra loading function is an absurd requirement, especially when we are already using tons of different loaders! – Remi D Dec 26 '16 at 15:27
  • Have just wasted the whole evening trying to rollup my website, and only now I've solved the problem thanks to your precious post. I've done the same for lodash: // lodash-loader.ts import { default as _lodash } from "lodash"; export default window["_"] as _lodash.LoDashStatic; – Etchelon Jan 10 '17 at 01:09
  • Neat idea, but I find it completely unacceptable having to use this. I just want to use an external library for Christ's sake. *sigh, moments like these (no pun intended) make me regret using Angular 2... – Vitor Machado Feb 06 '17 at 20:31
  • Totally agree that this is not ideal and an absolute PITA. Just needed something to let me keep on working until there's better options. It's still all 'fairly' new so I'm sure this will get sorted eventually....well hopefully anyway :). – Nick Cameron Feb 07 '17 at 23:23
1

Going by this thread, import moment from 'moment'; should work.

Kiara Grouwstra
  • 5,723
  • 4
  • 21
  • 36
  • 1
    this worked well for me, the only thing i had to add is in `tsconfig.json` under `compilerOptions` `"allowSyntheticDefaultImports": true` – Kukic Vladimir Mar 13 '20 at 10:33
0

As of version 2.13.0,

import * as moment from 'moment';

0

I tried all the solutions above, but none worked for me. What worked was

import moment from 'moment-with-locales-es6';
Adrian Ber
  • 20,474
  • 12
  • 67
  • 117
0

Had same problem with momentJs (2.24) usage in my Angular 5 (5.2.9) project (upgraded from Ng2) with Gulp and Rollup (0.58.0) for prod build.

As guys mentioned earlier here import * as moment from 'moment'; works only for deving (via SystemJS) with referring momentJs in packages list:

{
  name: 'moment',
  path: 'node_modules/moment/min/moment.min.js'
}

Other case is Rollup usage (Production build) - momentJs have its code as ES6 module (in moment/src), but it exports in different way (then usual export). That's why import moment from 'moment'; works with Rollup's

include: [
  'node_modules/rxjs/**',
]

and ES6 modules import.

Yes, to use ES6 wrapper (such as moment-es6 or so) is simple solution. But it always requires momentJs. Same time there is one more simple solution for this issue - replace your import row from Dev to Prod. For example, Gulp can use gulp-replace at some step:

return gulp.src([
     ...
    ])
    .pipe(replace('import * as moment from \'moment\';', 'import moment from \'moment\';'))
     ...;
PIoneer_2
  • 470
  • 4
  • 12