wetz
wetz

Reputation: 86

Integrating dated JavaScript libraries with updated TypeScript

My team and I are building on a large web application project that uses older versions of popular JavaScript libraries (jQuery, Backbone, Underscore, etc.). We're currently trying to phase in the use of TypeScript for new solutions as well as for replacing existing JavaScript code. Because our project is heavily dependent on specific JS library versions (e.g. jQuery 1.8.3), updating these libraries to the newest versions is slightly more complicated than just downloading and dropping in the most recent version.

This creates a problem for us in regard to phasing in TypeScript; we need declarations of dated JS library versions that are compatible with the latest TypeScript standards. Although DefinitelyTyped does a great job at providing declaration files for the newest versions of JS libraries specific to the latest version of TypeScript, it doesn't provide declaration files for older JS library versions that are compatible with the latest TypeScript standards.

It seems that as TypeScript grows, all of the older library declarations (e.g. jQuery-1.8.3.d.ts) are left in the dust because those libraries are simultaneously evolving. This is completely understandable as I don't expect there to be much of an effort in the community to continuously update dated library declaration files so that they are compatible with the new TypeScript standards.

So my question is: what is the best way to go about phasing in new TypeScript code that relies heavily on older JS libraries?

Below is an example of a problem that I'm running into. I'm essentially trying to create a Backbone application using TypeScript.

// Model.ts 
import Backbone = require("backbone");
export class NewModel extends Backbone.Model {
    ...
}

And because we're using AMD with require.js we get the something like the following:

// Model.js
...
define(['require', 'exports', 'backbone'], function(require, exports, Backbone) {
...
}

This is great and it's exactly the output that we want because our require.config file defines all of the libraries that we need:

// Config.ts
require.config({
    baseUrl: './',
    paths: {
        'jquery': '../jquery-1.8.3.min',
        'underscore': '../underscore/underscore.min',
        'backbone': '../backbone/backbone',
        'text': '../require/text'
    },
    shim: {
        jquery: {
            exports: '$'
        },
            underscore: {
            exports: '_'
        },
        backbone: {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        }
    }
});

However, TypeScript won't let us compile this code unless we have a backbone.d.ts that describes the structure of our backbone.js library and provides an ambient declaration of the "backbone" module.

// backbone.d.ts
declare module Backbone {
    ...
    export class Model extends ModelBase {
        ...
    }
    ...
}
declare module 'backbone' {
    export = Backbone;
}

// Model.ts
/// <reference path="path/to/backbone.d.ts" /> 
import Backbone = require("backbone")
export class NewModel extends Backbone.Model {
    ...
}

If we don't have these declarations then we have no ambient declaration of the Backbone module and thus can't extend Backbone.Model in TypeScript because Backbone.Model is not defined as a class anywhere. This is a bit of an exaggerated sample problem because the Model class type exists in all 'backbone.d.ts' files regardless of the version. However, there are more subtle changes to JS libraries that can cause your code to break if you don't have an accurate .d.ts file to represent it.

That being said, you can also get away with using a more recent version of the .d.ts file (which we have been doing) as long as you only use the parts that actually exist the associated JS library and as long as you use them directly. Otherwise, if you use a function that doesn't exist or if you pass a function too many parameters, then you will only see that error when the function is actually invoked. TypeScript won't pick up on it because its information is based solely on that of the .d.ts file.

We could generate the define requirements using the amd dependency work-around with something like:

// Model.ts 
/// <amd-dependency path="backbone" />
var Backbone = require("backbone");
export class NewModel extends Backbone.Model {
    ...
}

Which would generate the following:

// Model.js
...
define(['require', 'exports', 'backbone'], function(require, exports) {
var Backbone = require('backbone');
...
}

This gives us the Backbone reference that we want but TypeScript still complains because it has no knowledge of the Backbone variable that we're declaring. In other words, it doesn't provide the structure that the .d.ts file provides.

Most of our TypeScript solutions are actually working as they should despite our lack of correlation between .js files and their associated .d.ts files. My concern is: in the future will this discrepancy cause major problems in our application?

Right now, the best solutions I can think of are to:

  1. Get the .d.ts versions closest to our JS library versions and update them so that they are compatible with the latest TypeScript standards.
  2. Use the most recent .d.ts files and update them accordingly as we run into problems.

I'm not particularly thrilled about either solution, which is why I'm reaching out for another opinion. If anyone is still with me at this point (sorry for dragging this on), then I would appreciate your proposed solution.

Upvotes: 2

Views: 423

Answers (1)

Justin Beckwith
Justin Beckwith

Reputation: 7866

This can be a rough problem, and something our team certainly dealt with in the early days of TypeScript. We ended up starting with versions of the d.ts files on definitely typed, and adapting them to our needs. For a lot of the libraries, we found the typing wasn't using new language features, wasn't complete, or in some cases was flat out wrong. We've done our best to contribute back where it makes sense.

Given the two approaches you're considering, I'd go with #1. Keeping d.ts files up to date with the latest TypeScript standard was more of an issue when the language was evolving at a super fast pace. While I still expect a lot of new features to come in v2, v1 is fairly stable. During the run up to 1.0, there were changes in the language on what seemed like a monthly basis. I don't think we need to deal with that level of churn now :)

Upvotes: 1

Related Questions