Search code examples
javascriptmodulerequirejstypescriptamd

Making AMD Optional with typescript


I've created a smallish library in typescript, that I want to be able to use with a lot of my projects, some of the projects use requirejs, and others don't.

I've seen other scripts do this, where it uses define and checks for AMD, and if they're not there it attaches the object to the window object or something.

I'm wondering what is the best way to do this? And if possible any shortcuts or w/e for doing it in typescript.

Here is an example module

export module Utilities {
//This is used to grab query string values from a javascript file
//pass the filename (without .js) into the constructor, then use
//GetValue(name) to find the query string values
export class QueryStringHelper {
    names: string[] = [];
    values: string[] = [];
    constructor(public fileName: string) {
        this.getQueryStringNameAndValues();
    }
   // GetValue(queryStringName: string) => number;

    GetValue(queryStringName: string) {
        var i = this.names.indexOf(queryStringName);
        if (i == -1)
            return undefined;
        else {
            if (this.values.length > i)
                return this.values[i];
        }
    }
    getQueryStringNameAndValues() {
        var doc: HTMLDocument = document;
        var scriptQuery: string = '';

        // Look for the <script> node that loads this script to get its parameters.
        // This starts looking at the end instead of just considering the last
        // because deferred and async scripts run out of order.
        // If the script is loaded twice, then this will run in reverse order.
        // take from Google Prettify
        for (var scripts = doc.scripts, i = scripts.length; --i >= 0;) {
            var script = <HTMLScriptElement>scripts[i];
            var match = script.src.match("^[^?#]*\\/" + this.fileName + "\\.js(\\?[^#]*)?(?:#.*)?$");
            if (match) {
               scriptQuery = match[1] || '';
                // Remove the script from the DOM so that multiple runs at least run
                // multiple times even if parameter sets are interpreted in reverse
                // order.
                script.parentNode.removeChild(script);
                break;
            }
        }

        var that = this;
        scriptQuery.replace(
            /[?&]([^&=]+)=([^&]+)/g,
            function (_, name, value) {
                value = decodeURIComponent(value);
                name = decodeURIComponent(name);
                that.names.push(name);
                that.values.push(value);
                return "";
            });
        }
    }
}

Of course this works when I use requireJS, and load it how it explains on the website, but if I try loading this without requireJS, I get the obvious define is not defined exception.

How would I go about making it..."optional".

I'm sure this has been answered before but I couldn't figure out what to search for (and had trouble naming the question..feel free to edit)

EDIT EXAMPLE

So I know this way works, but is this a proper way to do what i'm looking for? What problems will I have in the future if I do it this way?

//i guess by default this is attached to the window object
//so it's automatically available to anything that just includes it
class TestClass {
    constructor(public testValue: string) {
    }

    getTestValue(): string {
    return this.testValue;
    }
}
//attempt to support amd
if (typeof window['define'] === "function" && window['define']['amd']) {
    window['define']("TestClass", [], function () {
        return TestClass;
    });
}

And that produces this javascript

var TestClass = (function () {
    function TestClass(testValue) {
        this.testValue = testValue;
    }
    TestClass.prototype.getTestValue = function () {
        return this.testValue;
    };  
    return TestClass;
})();

if (typeof window['define'] === "function" && window['define']['amd']) {
    window['define']("TestClass", [], function () {
        return TestClass;
    });
}

Solution

  • This isn't a pattern TypeScript supports. The reason is that this technique only works for modules that don't have any module dependencies themselves. You can still use TypeScript for the module, but you'll have to call define yourself instead of using the top-level export keyword.