18

I'm building an Angular (2+) component library using jvandemo/generator-angular2-library as a starter, which uses Rollup as a module builder. The component I am creating in the library uses MomentJS.

I've had various build issues with the inclusion of MomentJS.

I first used import moment from 'moment'; to import moment into the component, but that produces the following error at build;

[17:26:28] Starting 'ngc'...
Error at /Users/chris/angular-library/.tmp/components/my-library/my-component.component.ts:6:8: Module '"/Users/chris/my-library/node_modules/moment/moment"' has no default export.

I found this SO question that said to use import * as moment from 'moment'; however with that I get;

'moment' is imported by build/components/my-component.component.js, but could not be resolved – treating it as an external dependency

events.js:182
      throw er; // Unhandled 'error' event
      ^
Error: Cannot call a namespace ('moment')
    at error (/Users/chris/angular-library/node_modules/rollup/dist/rollup.js:185:14)

As far as I can tell these are the only two options and I can't get either to work, what am I missing?

Edit

I've added this issue to the library's Github repo that contains minimalistic replication steps

Chris Brown
  • 4,445
  • 3
  • 28
  • 36
  • https://stackoverflow.com/a/43257938/4298881, to be short, instead of "import moment from 'moment'", do "import * as moment from 'moment'" – Surely Oct 22 '17 at 16:40
  • 1
    @Surely as mentioned above doing that leads to `Error: Cannot call a namespace ('moment')` – Chris Brown Oct 22 '17 at 16:43
  • are you using cli ? – Rahul Singh Oct 22 '17 at 16:53
  • @RahulSingh I'm using https://github.com/jvandemo/generator-angular2-library – Chris Brown Oct 22 '17 at 16:55
  • sorry didn't read it carefully enough. I think this thread is quite similar to the issue. https://stackoverflow.com/questions/39519823/using-rollup-for-angular-2s-aot-compiler-and-importing-moment-js – Surely Oct 22 '17 at 17:03
  • @Surely thanks yeah - I've tried that and still the same :( – Chris Brown Oct 22 '17 at 17:07
  • Do you use moment in component while creating the variable, or you try to pass `moment()...` virtually in html template? – Sergey Oct 22 '17 at 18:08
  • @Sergey moment is used exclusively in the component (`.ts`) – Chris Brown Oct 22 '17 at 18:10
  • Could you upload your project to somewhere? I'd like to have a look at it. – Sergey Oct 22 '17 at 18:11
  • @Sergey Thanks - I can't yet upload the project itself, but there are minimal replication steps in an issue I've just posted on Github that can be set up in a minute or two - https://github.com/jvandemo/generator-angular2-library/issues/224 – Chris Brown Oct 22 '17 at 18:13
  • @ChrisBrown have you installed moment with `save-dev` mark? – Sergey Oct 22 '17 at 18:14
  • @Sergey yes, `npm install --save-dev moment` (although that shouldn't matter for running a build) – Chris Brown Oct 22 '17 at 18:15
  • @ChrisBrown man. I, probably, found the root of the evil. It's the `save-dev` you should use it only for the things that are used only in developing and are not needed for app working. But, moment is used in app work. That means that it should be in "normal" dependencies, but not "dev". – Sergey Oct 22 '17 at 18:18
  • 1
    @Sergey I tried it just in case, but whether moment is installed as dev or not doesn't affect whether the build runs successfully - you're correct I think it should be in `dependencies` not `devDependencies` but the error remains the same – Chris Brown Oct 22 '17 at 18:23
  • To have a chance of reproducing your problem. Do I need to only install the `yo` and `generator-angular2-library`? – Sergey Oct 22 '17 at 18:29
  • @Sergey to run the generator that's correct, `npm install -g yo generator-angular2-library` (instructions are in the readme at https://github.com/jvandemo/generator-angular2-library) – Chris Brown Oct 22 '17 at 18:30
  • What version of moment are you using? – PeterS Oct 25 '17 at 15:31
  • 1
    @PeterS The latest v2 (2.19.1) – Chris Brown Oct 25 '17 at 16:59

3 Answers3

35

The error is very clear and specific

Error: Cannot call a namespace ('moment') at error (/Users/chris/angular-library/node_modules/rollup/dist/rollup.js:185:14)

This is per the ES Module Specification.

That means the following is an invalid way to import moment, or anything you intend to call, because a module namespace object, such as that created by * as ns may not be called.

import * as moment from 'moment';

The correct form is the form that ngc is raising an error on

import moment from 'moment';

Firstly to make this work, you need to specify the --allowSyntheticDefaultImports flag.

tsconfig.json

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true
  }
}

Assuming that ngc will recognize the option, you still have an additional problem to work out.

The flag above is for users of tools such as SystemJS or Webpack which perform the synthesis, allowing such code to typecheck.

As of TypeScript 2.7, you can now specify the --esModuleInterop flag to have the language provide the synthesis as part of the transpilation process.

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true
  }
}

Note that if you are using a version of TypeScript prior to 2.7 and if you are compiling to CommonJS, AMD, or UMD modules (e.g. with --module commonjs) the correct import syntax is rather

import moment = require('moment');

The import = require syntax is a TypeScript specific construct, now largely unnecessary. It exists to acquire both the type and the value of the module exports of an AMD, CommonJS, or UMD module. The "and" is important because a const, var, or let = require invocation only creates a name in the value space not in the type space.

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
6

To whoever finds this in 2018+, with Angular6 and current @angular/cli:

Today I ran into this and found solution here.

import * as moment_ from 'moment';
const moment = moment_;

Now the library compiles OK for me.

Federico Grandi
  • 6,785
  • 5
  • 30
  • 50
PeS
  • 3,757
  • 3
  • 40
  • 51
1

In the sample directive I had no problem compiling when I used the following:

import { Directive, ElementRef } from '@angular/core';
import * as moment from '../node_modules/moment/moment';
@Directive({
  selector: '[sampleDirective]'
})
export class SampleDirective {

  constructor(private el: ElementRef) {
    moment.isDate('test');
  }

}

The files are compiled from a build directory that is a sub-dir off the root. You do get a further warn about "this", mentioned here:

https://github.com/rollup/rollup/issues/794

I need to say in the gulfile that the library is external:

  external: [
    '@angular/core',
    '@angular/common',
    'moment'
  ],

And from the github link, you should add a onwarn block to both rollup sections: That is section 'rollup:umd' and 'rollup:fesm'

onwarn: function(warning) {
    // Skip certain warnings

    // should intercept ... but doesn't in some rollup versions
    if ( warning.code === 'THIS_IS_UNDEFINED' ) { return; }

    // console.warn everything else
    console.warn( warning.message );
},

Does that get you any further?

PeterS
  • 2,818
  • 23
  • 36
  • Thank you for looking - that works for me too when using `moment.isDate()` but it doesn't work for `moment()`, so sadly I'm at the same point. For what it's worth, `import * as moment from 'moment';` works as well when only using `moment.isDate()`, so the path from `node_modules` isn't necessary there – Chris Brown Oct 27 '17 at 19:38
  • Using import * as moment from 'moment'; should work fine. If not, try npm uninstall moment, then npm i moment@latest, rm -rf node/modules and reinstall all your deps (npm i) – andrea06590 Oct 28 '17 at 09:13