Skip to content
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 · 16 comments
Closed

Cannot call a namespace ('$') #1267

cmeijerink opened this issue Jan 16, 2017 · 16 comments

Comments

@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
Copy link
Contributor

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
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
Copy link
Author

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
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
Copy link

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
Copy link

Any updates on this?

@Rich-Harris
Copy link
Contributor

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
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
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
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
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
Copy link

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

@slavafomin
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
Copy link

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

@schtauffen
Copy link

From https://material.angular.io/components/datepicker/overview#watching-the-views-for-changes-on-selected-years-and-months

// Depending on whether rollup is used, moment needs to be imported differently.
// Since Moment.js doesn't have a default export, we normally need to import using the `* as`
// syntax. However, rollup creates a synthetic default module and we thus need to import it using
// the `default as` syntax.
import * as _moment from 'moment';
// tslint:disable-next-line:no-duplicate-imports
import {default as _rollupMoment, Moment} from 'moment';

const moment = _rollupMoment || _moment;

@lobsterkatie
Copy link

lobsterkatie commented Jul 20, 2022

Thanks for the workaround, @schtauffen!

Here's what it looks like when using jscodeshift with typescript:

import * as jscsTypes from "jscodeshift";
import { default as jscodeshiftDefault } from "jscodeshift";

const jscodeshiftNamespace = jscsTypes;
const jscs = jscodeshiftNamespace || jscodeshiftDefault;

// Simplified version
function findMatchingNodes(code: string): jscsTypes.Collection {
  const ast = jscs(code);
  return ast.find(jscsTypes.VariableDeclarator);
}

Basically the same as @schtauffen's above, except that this is worth noting on the TS front:

If, in the above example, you do this:

const { Collection, VariableDeclarator } = jscs;

then this will work:

return ast.find(VariableDeclarator);

but this will not:

function findMatchingNodes(code: string): Collection { ... }

In other words, you need the namespace in order to use something as a type, but can unpack it if you're only using it as a value.

@Snailedlt
Copy link

For those of you having trouble importing using one of these:

import $ from 'jquery'
import * as $ from 'jquery'

You can try doing this:

import jQuery from 'jquery'

See this related StackOverflow answer for more info: https://stackoverflow.com/a/3291916/12206312

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests