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

Cannot call a namespace ('$') #1267

Closed
cmeijerink opened this Issue Jan 16, 2017 · 13 comments

Comments

Projects
None yet
10 participants
@cmeijerink
Copy link

cmeijerink commented Jan 16, 2017

Hi,

I'm used the have previous project using rollup/aot successfully. Angular2 projects using JQuery as well.

But now for some reason i don't manage to get it rolled-up.

  • compile ( tsc ) -> successfully
  • compile ( ngc ) -> successfully
  • the factories ( ngc ) -> are created
  • rollup -> Cannot call a namespace ('$')

I'm not able to figure out where the problem occurs.

Any suggestions?

Here are my config files:

rollup-config:

import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify';
import inject from 'rollup-plugin-inject';
//paths are relative to the execution path
export default {
    entry: 'app/main.aot.js',
    dest: 'aot/dist/build.js', // output a single application bundle
    sourceMap: true,
    sourceMapFile: 'aot/dist/build.js.map',
    format: 'iife',
    plugins: [
        nodeResolve({
            module: true,
            jsnext: true,
            main: true,
            browser: true,
            extensions: ['.js']
        }),
        inject({
            include: '**/*.js',
            exclude: 'node_modules/**',
            jQuery: 'jquery',
            $: 'jquery',
        }),
        commonjs({
            include: [
                'node_modules/**',
                'node_modules/moment/**'
            ],
            namedExports: {
                './node_modules/lodash/lodash.js': ['chain', 'keyBy', 'some']
            }
        }),
        uglify()
    ]
}```

tsconfig-aot.json

```{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true,
    "allowSyntheticDefaultImports": true,
    "typeRoots": [
      "./node_modules/@types/",
      "./@types/"
    ],
    "types": [
      "moment",
      //"jqueryui",
      "core-js",
      "node"
    ]
  },
  "files": [
    "app/app.module.ts",
    "app/main.aot.ts"
  ],
  "angularCompilerOptions": {
    "genDir": "aot",
    "skipMetadataEmit": true
  }
}

system-config.ts

/***********************************************************************************************
 * User Configuration.
 **********************************************************************************************/
var angular2ModalVer = '@2.0.0-beta.11';
var plugin = 'bootstrap'; // js-native / bootstrap

/** Map relative paths to URLs. */
var map: any = {
  'app': 'app',
  'main': 'app/main.tsc.js',
  '@angular': 'npm:@angular',
  'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
  'rxjs': 'npm:rxjs',
  'lodash': 'npm:lodash/lodash.js',
  'moment': 'npm:moment/moment.js',
  'jquery': 'npm:jquery/dist/jquery.js',
  'jquery-ui': 'npm:jqueryui/jquery-ui.js',
  'touch-punch': 'touch-punch.js',
  'bootstrap': 'npm:bootstrap/dist/js/bootstrap.js',
  'tether': 'npm:tether/dist/js/tether.js',
  'jspdf': 'npm:jspdf/dist/jspdf.min.js',
  'html2canvas': 'npm:html2canvas/dist/html2canvas.js',
  'treeview': 'npm:bootstrap-treeview/dist/bootstrap-treeview.min.js',
  'dragula': 'npm:dragula/dist/dragula.js',
  'ng2-dragula': 'npm:ng2-dragula',
  'file-saver': 'npm:file-saver/FileSaver.js',
  '@ng-bootstrap/ng-bootstrap': 'node_modules/@ng-bootstrap/ng-bootstrap/bundles/ng-bootstrap.js',
  'angular2-modal': 'npm:angular2-modal',
  'angular2-modal/plugins/${plugin}': 'npm:angular2-modal/bundles',
  'fullcalendar': 'npm:fullcalendar/dist/fullcalendar.js',
  'angular2-fullcalendar': 'npm:angular2-fullcalendar',
};

// packages tells the System loader how to load when no filename and/or no
// extension
var packages: any = {
  'app': { main: 'main.tsc.js', defaultExtension: 'js' },
  'api': { defaultExtension: 'js' },
  'rxjs': { defaultExtension: 'js' },
  'ng2-dragula': { format: 'cjs', defaultExtension: 'js' },
  'angular2-fullcalendar': { format: 'cjs', defaultExtension: 'js' },
  'jquery-ui': { format: 'cjs', defaultExtension: 'js' },
  'angular2-modal': { defaultExtension: 'js', main: 'bundles/angular2-modal.umd' },
  'jquery': {
    format: 'global',
    exports: '$'
  },
  'lodash': {
    format: 'global',
    exports: '_'
  },
  'tether': {
    format: 'global',
  },
  'bootstrap': {
    format: 'global',
    exports: 'ScrollSpy'
  },
  'bootstrap-toggle': { format: 'global' },
  'moment': {
    format: 'global'
  }
};

// UMD bundles
map[`angular2-modal/plugins/${plugin}`] = map['angular2-modal'] + '/bundles';
packages[`angular2-modal/plugins/${plugin}`] = { defaultExtension: 'js', main: `angular2-modal.${plugin}.umd` };

// Uncomment to use Individual files/modules
// map[`angular2-modal/plugins/${plugin}`] = map['angular2-modal'] + `/plugins/${plugin}`;
// packages['angular2-modal'] = { defaultExtension: 'js', main: 'index' };
// packages[`angular2-modal/plugins/${plugin}`] =  { defaultExtension: 'js', main: `index` }; 

const barrels: any = [
  // App specific barrels.
  'app/routing',
  'app/shared',
  'app/models',
];

barrels.forEach((barrelName: string) => {
  packages[barrelName] = { main: 'index' };
});

const meta: any = {
  'bootstrap': {
    deps: ['jquery', 'tether']
  },
  'treeview': {
    deps: ['jquery']
  },
  'timepicker': {
    deps: ['jquery']
  },
  'touch-punch': {
    deps: ['jquery', 'jquery-ui']
  },
  'jquery-ui': {
    deps: ['jquery']
  },
  'moment': {
    format: 'global'
  },
  
}

////////////////////////////////////////////////////////////////////////////////////////////////
/***********************************************************************************************
 * Everything underneath this line is managed by the CLI.
 **********************************************************************************************/

const ngPackageNames: string[] = [
  'common',
  'compiler',
  'core',
  'forms',
  'http',
  'platform-browser',
  'platform-browser-dynamic',
  'router',
  'router-deprecated',
  'upgrade'
];

// Individual files (~300 requests):
function packIndex(pkgName: string) {
  packages['@angular/' + pkgName] = { main: 'index.js', defaultExtension: 'js' };
}

// Bundled (~40 requests):
function packUmd(pkgName: string) {
  packages['@angular/' + pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
}

declare var System: any;

// Most environments should use UMD; some (Karma) need the individual index
// files
var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;

// Add package entries for angular packages
ngPackageNames.forEach(setPackageConfig);

console.log(packages);

var config = {
  paths: {
    'npm:': 'node_modules/'
  },
  map: map,
  packages: packages,
  meta: meta
};

System.config(config);

package.json

  "name": "commaan",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "setup": "npm install -g browser-sync typescript@next typings node-sass postcss && typings install",
    "start": "npm run build && concurrently \"npm run clean\" \"npm run build:js:w\" \"npm run build:sass:w\" \"npm run lite\" ",
    "lite": "lite-server",
    "lite:aot": "lite-server -c aot/bs-config.json",
    "postinstall": "typings install",
    "build": "npm run build:js && npm run build:sass",
    "build:js": "tsc",
    "build:js:w": "tsc -w",
    "build:ngc": "ngc -p tsconfig-aot.json",
    "build:rollup": "rollup -c rollup-config.js",
    "build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js && node copy-dist-files",
    "typings": "typings",
    "clean": "rimraf -rf dist && mkdir dist",
    "build:sass": "node-sass -o app/ app/",
    "build:sass:w": "node-sass -o app/ app/ -w",
    "build:css": "postcss --use autoprefixer app/*.css -d app/"
  },
  "license": "ISC",
  "dependencies": {
    "@angular/common": "^2.4.2",
    "@angular/compiler": "^2.4.2",
    "@angular/compiler-cli": "^2.4.2",
    "@angular/core": "^2.4.2",
    "@angular/forms": "^2.4.2",
    "@angular/http": "^2.4.2",
    "@angular/platform-browser": "^2.4.2",
    "@angular/platform-browser-dynamic": "^2.4.2",
    "@angular/platform-server": "^2.4.2",
    "@angular/router": "^3.4.2",
    "@angular/upgrade": "^2.4.2",
    "@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.18",
    "@types/jquery": "^2.0.39",
    "@types/node": "^6.0.59",
    "@types/tether": "^1.4.0",
    "angular-file-saver": "^1.1.3",
    "angular-in-memory-web-api": "0.1.13",
    "angular2-fullcalendar": "^1.1.1",
    "angular2-timepicker": "^1.1.3",
    "angular2-tree-component": "^2.7.0",
    "awesome-typescript-loader": "^3.0.0-beta.17",
    "bootstrap": "^4.0.0-alpha.6",
    "bootstrap-toggle": "^2.2.2",
    "bootstrap-treeview": "^1.2.0",
    "core-js": "2.4.1",
    "dragula": "3.7.2",
    "es6-shim": "^0.35.2",
    "font-awesome": "4.7.0",
    "jquery": "^3.1.1",
    "jquery-ui": "^1.12.1",
    "jqueryui": "^1.11.1",
    "moment": "^2.17.1",
    "ng2-dragula": "1.2.2-0",
    "pdfmake": "0.1.18",
    "reflect-metadata": "0.1.8",
    "rollup": "^0.36.4",
    "rxjs": "^5.0.3",
    "systemjs": "0.19.40",
    "tether": "^1.4.0",
    "timepicker": "^1.11.9",
    "typescript": "^2.1.4",
    "webpack": "^1.14.0",
    "zone.js": "^0.6.26"
  },
  "devDependencies": {
    "@types/core-js": "^0.9.35",
    "@types/filesaver": "0.0.30",
    "@types/html2canvas": "0.5.32",
    "@types/jquery": "^2.0.34",
    "@types/jquery.timepicker": "^0.3.28",
    "@types/jqueryui": "^1.11.32",
    "@types/jspdf": "1.1.31",
    "@types/lodash": "4.14.38",
    "@types/moment": "^2.13.0",
    "@types/node": "^6.0.46",
    "@types/selenium-webdriver": "2.53.33",
    "@types/tether": "^1.1.27",
    "autoprefixer": "6.5.1",
    "awesome-typescript-loader": "latest",
    "browser-sync": "2.17.5",
    "concurrently": "3.1.0",
    "cp": "^0.2.0",
    "flexboxgrid": "6.3.1",
    "html2canvas": "0.5.0-beta4",
    "lite-server": "2.2.2",
    "node-cp": "^0.1.1",
    "node-sass": "3.10.1",
    "node-sass-cli": "0.0.4",
    "postcss": "5.2.5",
    "rollup": "^0.36.3",
    "rollup-plugin-commonjs": "^5.0.5",
    "rollup-plugin-inject": "^2.0.0",
    "rollup-plugin-node-resolve": "^2.0.0",
    "rollup-plugin-uglify": "^1.0.1",
    "tslint": "3.15.1",
    "typescript": "latest",
    "webpack": "latest",
    "webpack-dev-server": "latest"
  }
}
@Rich-Harris

This comment has been minimized.

Copy link
Contributor

Rich-Harris commented Jan 16, 2017

Use triple backticks (```) to enclose code blocks please — your issue is unreadable. See this page for info.

It sounds like you're doing this:

import * as $ from 'jquery';

It should be this:

import $ from 'jquery';
@cmeijerink

This comment has been minimized.

Copy link
Author

cmeijerink commented Jan 17, 2017

@Rich-Harris thnx, for the reply.

I updated the comment.

in my system-config.ts i set $ as a global...

'jquery': {
    format: 'global',
    exports: '$'
  }

Which i use in previous projects and does seem to work.

I think the problem is in one of the 3th party libraries using jquery, but no clue how to find it.

@cmeijerink

This comment has been minimized.

Copy link
Author

cmeijerink commented Jan 18, 2017

Ok, i have some new info

�   Cannot call a namespace ('$')
node_modules\angular2-fullcalendar\src\calendar\calendar.js (29:12)
27:         setTimeout(function () {
28:             // console.log("100ms after ngAfterViewInit ");
29:             $('angular2-fullcalendar').fullCalendar(_this.options);
                ^
30:         }, 100);
31:     };

I'm using fullcalendar which breaks...

Changing the jquery import as mentioned by @Rich-Harris solved it....

But, now i modified a 3th party lib, which i dont like to much... so, is there a beter way to do this?

@Hikariii

This comment has been minimized.

Copy link

Hikariii commented Jan 20, 2017

Same issue here when importing jQuery.

We're running typescript and have to import jQuery as:
import * as $ from 'jquery';
because we're compiling (on the fly) for commonjs

https://www.typescriptlang.org/docs/handbook/modules.html#import-the-entire-module-into-a-single-variable-and-use-it-to-access-the-module-exports

When we compile for es2015 and run rollupJs, then we get this error.
There seems no way of rewriting this import or changing it so both Typescript and rollup require this correctly.

@Sleepyowl

This comment has been minimized.

Copy link

Sleepyowl commented Jan 31, 2017

To add to the previous comment, changing jquery.d.ts to declare module "jquery" { export default $; } helps with es2015 modules and rollup, but tsc --module commonjs produces something like var jquery_1 = require("jquery"); jquery_1.default("#blabla").

@StevenDoesStuffs

This comment has been minimized.

Copy link

StevenDoesStuffs commented Apr 14, 2017

Any updates on this?

@Rich-Harris

This comment has been minimized.

Copy link
Contributor

Rich-Harris commented Apr 14, 2017

Unfortunately this is out of our hands — it needs to be fixed on the TypeScript end. Calling a namespace isn't possible, libraries like jQuery need to be imported as a default export...

import $ from 'jquery';

...which means TypeScript needs to generate code like that. I'll close this issue as there isn't anything Rollup can do differently. Thanks all

@epiqueras

This comment has been minimized.

Copy link

epiqueras commented Apr 22, 2017

@Rich-Harris

Typescript is following the ES6 module spec and not introducing a '.default guard' for CommonJS modules that do things like module.exports = $.

The correct way to consume jQuery is const $ = require('jquery'). The direct translation to ES6 is import * as $ from 'jQuery'.

Rollup's CommonJS proxy is doing something like (module && module['default']) || module. Essentially returning the entire object if a default property is not present.

When Rollup imports a namespace it does an Object.freeze on it's exports. Is there a way we could bypass this to be able to call it as a function?

@epiqueras

This comment has been minimized.

Copy link

epiqueras commented Apr 22, 2017

I fixed it by adding my own check after the import:
import * as jqueryProxy from 'jquery'
const jquery: JQueryStatic = (<any>jqueryProxy).default || jqueryProxy

In all environments except the Rollup bundle, jquery will be the entire imported object. Rollup will generate something like this:
var jqueryProxy = Object.freeze({ default: jquery$1, __moduleExports: jquery$1 })
So in the Rollup bundle jquery will be set to jqueryProxy.default.

I think it would be better if Rollup somehow detected if a namespace import had a function signature and allowed you to call it directly.

kktam added a commit to kktam/fullcalendar-ag4 that referenced this issue Jun 30, 2017

Add FullCalendar.io
Fixed @types/jquery issues  and rollup issues

DefinitelyTyped/DefinitelyTyped#17239
DefinitelyTyped/DefinitelyTyped#17239
rollup/rollup#1267

by pairing "typescript": "~2.3.4" with "@types//jquery": "3.2.5"

import * as jqueryProxy from 'jquery'
const jquery: JQueryStatic = (<any>jqueryProxy).default || jqueryProxy

In all environments except the Rollup bundle, jquery will be the entire imported object. Rollup will generate something like this:
var jqueryProxy = Object.freeze({ default: jquery$1, __moduleExports: jquery$1 })
So in the Rollup bundle jquery will be set to jqueryProxy.default.
@nghuuphuoc

This comment has been minimized.

Copy link

nghuuphuoc commented Aug 4, 2017

I have a working environment in jQuery, TypeScript and Rollup with the following setups:

  1. npm install --save-dev @types/jquery to help TypeScript understand jQuery types

  2. You can access jQuery in your TypeScript as

import $ from 'jquery';

$.fn['yourFunction'] = function(options) {
  ...
};
  1. In your tsconfig.json:
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true
  }
}
  1. Options for bundling with Rollup:
globals: {
    jquery: 'jQuery'
},
format: 'iife'

And you'll get output like the following:

(function ($) {
'use strict';

$ = $ && $.hasOwnProperty('default') ? $['default'] : $;

$.fn['yourFunction'] = function (options) {
    ...
};

}(jQuery));

Updated: Here is my package.json if you're curious on which versions/packages I am using

"devDependencies": {
  "@types/jquery": "^3.2.10",
  "rollup": "^0.45.2",
  "rollup-plugin-typescript": "^0.8.1",
  "typescript": "^2.4.2"
  ...
}
@TakanashiOuken

This comment has been minimized.

Copy link

TakanashiOuken commented Sep 14, 2017

Followed @nghuuphuoc
but getting
default is not exported by node_modules/jquery/dist/jquery.js

@slavafomin

This comment has been minimized.

Copy link

slavafomin commented Jul 3, 2018

I'm experiencing the same issue.

I have a third-party module exported like this: module.exports = () => {};.

When I'm importing it as import * as foo from 'foo'; (as I should), I'm getting the following error: Cannot call a namespace….

When I'm importing it as import foo from 'foo';, I'm getting the following error: 'default' is not exported by….

The workaround, suggested by Enrique helps, but is there a better way?

jcowman2 added a commit to regal/regal-bundler that referenced this issue Dec 12, 2018

@jcowman2

This comment has been minimized.

Copy link

jcowman2 commented Dec 12, 2018

I was having the same issue with two libraries, cosmiconfig and filenamify.

I used Enrique's workaround like so:

import * as _cosmiconfig from "cosmiconfig";
import * as _filenamify from "filenamify";

const cosmiconfig = (_cosmiconfig as any).default || _cosmiconfig;
const filenamify = (_filenamify as any).default || _filenamify;

But I didn't like how this forced the types of cosmiconfig and filenamify to be any. In my opinion, this kills the point of using TypeScript.

I ended up using rollup-plugin-re to replace the problematic import lines whenever Rollup is used.

My project source file:

import * as cosmiconfig from "cosmiconfig";
import * as filenamify from "filenamify";

My rollup.config.js:

import replace from "rollup-plugin-re";
plugins: [
  replace({
    exclude: "node_modules/**",
    replaces: {
      'import \* as cosmiconfig from "cosmiconfig";': 'import cosmiconfig from "cosmiconfig";',
      'import \* as filenamify from "filenamify";': 'import filenamify from "filenamify";',
    }
  }),
// other plugins

It's not the prettiest solution, but I prefer it because:

  • I can use import * and get type benefits in my source code
  • It moves the Rollup hack to rollup.config.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment