jQuery and AMD

Topics: General, Language Specification
Jul 1, 2013 at 3:07 AM
Edited Jul 1, 2013 at 3:07 AM
I'm putting together a VS project for a client, getting it set up with TypeScript and require.js....and for the life of me, I cannot figure out how to get jQuery to import correctly. I thought I just needed to provide a definition file like this:
/// <reference path="ts/durandal/durandal.d.ts" />
/// <reference path="ts/jquery/jquery.d.ts" />
/// <reference path="ts/knockout/knockout.amd.d.ts" />

declare module 'jquery' {
    export = $;
}
I thought that by using the new export = syntax that it would just work. The good news is that this seems to satisfy the compiler. The bad news is that there is absolutely no intellisense when you do this:
import $ = module('jquery');
I've tried tons of other combinations as well, but the above is the only one that satisfies the compiler. None of them get intellisense working. What am I missing here? Or is this an over-site in the design of export?
Coordinator
Jul 2, 2013 at 5:07 PM
In the current spec (section 11.2.4), the scope of the symbols an export= can point at is limited to being declared at scope of the export=.

"The Identifier of an export assignment must name one or more entities declared at the top level in the external module."

It may just be that we're not giving an error on the "export = $", yet.
Jul 2, 2013 at 5:09 PM
Ok. I understand if that's a constraint. But, how do I get jQuery to play in an AMD scenario? Is it really not possible?
Developer
Jul 2, 2013 at 7:46 PM
As the jquery.d.ts is purely design time information (ie interfaces and ambient declarations) it should be perfectly usable with AMD. Note that an import is generally done because the module you are importing has actual code associated with it, where our jquery.d.ts does not. I think the combination of the JS techniques listed here http://www.requirejs.org/docs/jquery.html plus the examples of using Typescript with RequireJS here http://blorkfish.wordpress.com/2012/10/23/typescript-organizing-your-code-with-amd-modules-and-require-js/ should help you get on the right path. Then you'll just reference (not import) jquery.d.ts where you need that type information. Does that make sense?
Jul 2, 2013 at 7:53 PM
I'm quite familiar with RequireJS config. I've already set up my paths correctly. What I'm trying to do is have an import statement actually cause the TypeScript compiler to generate the correct AMD wrapper AND provide intellisense. I will read the second article...but frankly, I'm getting the strong impression that the TS module system can't even handle this extremely common scenario...it's pretty disappointing.
Jul 2, 2013 at 9:13 PM
Edited Jul 2, 2013 at 9:19 PM
I think you will need to do:
import $ = require("../Scripts/jquery")

As far as a know the import $ = module('jquery'); is only for internal non AMD modules.
But i might be wrong.

I am using another approach:
You add a shim to require, in the require.config

require.config({
baseUrl: "app",
// Shims
//----------------------------------------------------------------------------------------------------
shim: {
    "jquery": {
        exports: "$"
    }
},
// Paths
//----------------------------------------------------------------------------------------------------
paths: {
    jquery: '../Scripts/jquery-2.0.2'
}
});

and then in some external module, which you have required, eg MainView.ts:

declare var $: JQueryStatic; // Must be outside of the class

class MainView {
       constructor() {   
       }
}
Jul 2, 2013 at 9:20 PM
Guys. Guys. Guys. jQuery does not require a shim. It already works, out of the box, with AMD. I just want to import it...and I don't want it to be public. If using the shim config is the only way, then I guess I have to live with that. But, it seams that it should just work. There is a KO amd d.ts that makes ko work correctly. Perhaps something like this needs to be done for jQuery? I'm just not sure what the magic sauce is to make TS understand. Also, it concerns me that a custom d.ts file will need to be created to make every js, amd-understanding library work. Is this really not supported?
Jul 2, 2013 at 11:39 PM
Edited Jul 2, 2013 at 11:41 PM
I assume you will be using Jquery in more than one module, therefore you can use the shim
and use
declare var $: JQueryStatic; 
If you just want to load jquery as a amd module, you need to set set define.amd.jQuery to true.
and then you should be able to require it the same way as your other modules.
import yourJqueryReference = require("../Scripts/jquery")
And maybe read "MAPPING MODULES TO USE NOCONFLICT" from here http://requirejs.org/docs/jquery.html
Jul 3, 2013 at 1:06 AM
Edited Jul 3, 2013 at 1:47 AM
One final time. This is not a require.js issue. I know how path, shim and no conflict work. I know how to require, define and optimize modules. I've even written a UI framework and a build tool on top of this stuff and built lots of apps this way before TypeScript. What I cannot get to work is TypeScript. I want to write this:
import $ = module('jquery');
But, I cannot figure out how to define the jquery module...from the TypeScript perspective... to get both the compiler and the intellisense to work.
Developer
Jul 3, 2013 at 1:36 AM
An import declaration is used for referencing external modules, the jquery.d.ts is not an external module (it has no top level export/import statements or an export= statement) so it isn't used with import in the fashion you're attempting. It simply provides type information to the compiler at design time via /// <reference path='jquery.d.ts'/>.

If you want to use JQuery with an import and AMD code generation then yes you will need a version of the .d.ts that is authored appropriately, namely exporting the existing definitions. It only takes a minute to convert the existing .d.ts to this form, and slightly longer than that to write a tool to do the same for an arbitrary .d.ts. That no such definition already exists on definitelytyped suggests most people are only loading this synchronously in a script tag since their app can't do much before JQuery is loaded.

In any case, changing the existing .d.ts to an external module definition I now can say this:
import JQuery = require('jquery');
var $: JQuery.JQueryStatic;
$('aThing');
var myDeferred = $.Deferred(d => { });
which generates:
define(["require", "exports"], function(require, exports) { 
    var $;
    $('aThing');
    var myDeferred = $.Deferred(function (d) {
    });
});
Is that more like what you wanted?
Jul 3, 2013 at 1:45 AM
Edited Jul 3, 2013 at 1:47 AM
That is closer, but the code that you are showing as being generated by the compiler is not correct. Here's what I want to type in my app code:
import $ = module('jquery');

$('.someSelector').show();
And it should generate something like this:
define(['require', 'exports', 'jquery'), function(require,exports,$){
    $('.someSelector').show();
});
I understand that the existing jquery file is not correctly set up to mimic the real word library (which actually provides an AMD module if requested). So, is there a way that I can easily fix it so that I can write code like above, or create a new .d.ts file that uses the existing one and then exports something. I'm happy to submit it back to Definitely Typed. I do plenty of open source work.
Jul 3, 2013 at 2:03 AM
I think this is what I was looking for: https://typescript.codeplex.com/workitem/1058
Some SO searched turned this up along with some peaved Node developers whose d.ts files broke on 0.9 because of this. I see that the issue indicates how to make the module callable. But, what if there are many callable overrides? How do you make the module callable with all the overrides?
Jul 3, 2013 at 2:10 AM
Another discussion here: https://typescript.codeplex.com/discussions/444444
This seams to be my case. If I'm reading the conversation correctly, then there is a bug that is preventing me from declaring the new jQuery module correctly. Me sad ;(
Developer
Jul 3, 2013 at 2:13 AM
All I did with the existing jquery.d.ts was add 'export' to each interface definition and declare statement. You can start there and see if it's actually modeled as you want and submit it to DefinitelyTyped.

I should've written 'declare var $: JQuery.JQueryStatic' since it doesn't need to be (and shouldn't be) emitted. So your code snippet requires one extra line in the TS to get what you want in the JS:
import JQuery = require('jquery');
declare var $: JQuery.JQueryStatic;
$('.someSelector').show();
yields:
define(["require", "exports"], function(require, exports) {  
    $('.someSelector').show();
});
//# sourceMappingURL=app.js.map
The additional line is necessary since in the Typescript file the imported $ variable is now under the JQuery namespace so you would need to write JQuery.$('.someSelector'). Instead we just tell the compiler that at runtime it can trust that there is a $ variable of the type described in the jquery.d.ts.
Developer
Jul 3, 2013 at 2:15 AM
EisenbergEffect wrote:
Another discussion here: https://typescript.codeplex.com/discussions/444444
This seams to be my case. If I'm reading the conversation correctly, then there is a bug that is preventing me from declaring the new jQuery module correctly. Me sad ;(
This thread is a better explanation of the solution and state of things https://typescript.codeplex.com/discussions/446134
Jul 3, 2013 at 6:40 AM
Jul 3, 2013 at 7:12 AM
My guess is that that works. But, I don't know if the current jQuery definition file can be made to work that way. Perhaps a separate amd defintion file can be made...bu I think it would have to duplicate part of jQuery in a clever way in order to get the callables to work correctly. I don't even know any more. I've pretty much given up.
Jul 3, 2013 at 11:20 AM
Edited Jul 3, 2013 at 7:27 PM
I had it solved quite similar to how danquirk suggests.

I wasted countless hours in attempt to have callable jquery module so I would have
import $ = module('jquery');
just like i have
import ko = module("knockout");
...
var a = ko.observable(7);
ended up with
import jquery = module("jquery");
var $ = <jquery.JQueryStatic>jquery;
that lets you load jquery as amd dependency
Generated code looks like that:
define(["require", "exports", "jquery"], function(require, exports, __jquery__) {
    var jquery = __jquery__;
    var $ = jquery;
PS. I changed interfaces in declaration files into export interface just as Dan did.

EDIT: I don't remember correctly last snippet I will correct it later at home. DONE
EDIT2: I know I cast module to type but it achieves AMD module without krazy ///<references
Jul 3, 2013 at 6:01 PM
For usage with AMD, we ended up downloading the jquery.d.ts file from definitelytyped without any modifications, then creating a separate libraries.d.ts file which contains:
/// <reference path="jquery/jquery.d.ts" />

declare module "jquery" {
  export = jQuery;
}
Then we can just use jquery directly in a TypeScript file:
/// <reference path="../../libraries/libraries.d.ts" />

import $ = require("jquery");
Then we have a requireConfig.js file which specifies that "jquery" should resolve to JQuery.
Jul 3, 2013 at 6:29 PM
Edited Jul 3, 2013 at 6:30 PM
@derekcicerone I tried exactly what you recommended...and I still don't get intellisense. Strange. Here's what I have:

references.d.ts
/// <reference path="../Typescript/durandal/durandal.d.ts" />
/// <reference path="../Typescript/jquery/jquery.d.ts" />
/// <reference path="../Typescript/knockout/knockout.amd.d.ts" />

declare module 'jquery' {
    export = jQuery;
}
foundation.ts
/// <reference path="../references.d.ts" />

import composition = module("durandal/composition");
import $ = module('jquery');

composition.addBindingHandler('foundation', {
    init: () => {
        $(document).foundation();
    }
});
Now, this compiles into the correct JS code as follows:

foundation.js
define(["require", "exports", "durandal/composition", 'jquery'], function(require, exports, __composition__, __$__) {
    var composition = __composition__;
    var $ = __$__;

    composition.addBindingHandler('foundation', {
        init: function () {
            $(document).foundation();
        }
    });
});
//@ sourceMappingURL=foundation.js.map
But, there is no intellisense on $ in foundation.ts.
Developer
Jul 3, 2013 at 7:16 PM
It's quite possible the lack of Intellisense is just a bug as export= is a fairly recent addition to the language. I'll take a look.
Jul 30, 2013 at 3:04 PM
Try this workaround. It gives me intellisense and it compiles:
///<reference path="./d.ts/DefinitelyTyped/jquery/jquery.d.ts"/>

declare module 'jquery' {
    var x: any;
    export = x;
}

import jquery = require('jquery');

var $ = <JQueryStatic>jquery;

$(document).ready( () => console.log('hi') );
MK
Aug 8, 2013 at 4:33 AM
Sorry to revive an old thread, but there is a workaround that (although somewhat brittle and hackish) I believe gets the job done the way EisenbergEffect intends (at least in ASP.NET projects within Visual Studio).
Original details are at http://absolom-programming.blogspot.ca/2012/10/typescript-amd-with-js-library.html

In this particular case there needs to be a shim in the configuration of requirejs named 'jquery' pointing to the jquery.js file, which must not be in the root directory.
In the TypeScript root (Scripts/ in my case) create jquery.ts with this content:
/// <reference path="definitelyTyped/jquery/jquery.d.ts" />

export = $;
But this export won't be used, it is there only to allow the type of $ to flow from the .d.ts to the import on the client, thus enabling intellisense.
To use it just write:
import $ = require("jquery");
During development and compilation this "jquery" should be resolved to jquery.ts because it is in the root dir and $ will be typed JQueryStatic. But at execution time it will be routed by requirejs through the shim defined to the proper jquery.js.