13

I'm trying to work out the best way to split my application into a few CommonJS modules that can be consumed by other applications.

I have 5 TS classes, and I'd like to bundle them as a single CommonJS module. I then intend to publish this module to a private NPM repo, so it can be consumed by other applications. Ideally I'd like to package the relevant *.d.ts definition files with it.

What's the best way to do this? I'm using external TS modules, but these produce a separate CommonJS module per TS class.

LJW
  • 2,378
  • 1
  • 21
  • 35

2 Answers2

18

As far as i know typescript doesn't support combining external modules yet. From their wiki on codeplex:

TypeScript has a one-to-one correspondence between external module source files and their emitted JS files. One effect of this is that it's not possible to use the --out compiler switch to concatenate multiple external module source files into a single JavaScript file.

However, you can do a trick by using internal modules in typescript, since the tsc compiler has the ability to compile them into a single file, and then you can just add one more file with a module.exports directive for the whole namespace to make it a CommonJS module.

Here is a step by step example. Let's say you have the following internal modules split into three files:

Validation.ts:

module Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}

ZipCodeValidator.ts

/// <reference path="Validation.ts" />
module Validation {
    var numberRegexp = /^[0-9]+$/;
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

LettersOnlyValidator.ts

/// <reference path="Validation.ts" />
module Validation {
    var lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}

If you compile these with with the --out parameter in the tsc compiler you can combine them into a single file. However, that doesn't make them a CommonJS module. To export them you use a trick to add one more ts file called ValidationExport.ts containing the export directive for the namespace:

var module: any = <any>module;
module.exports = Validation;

And then you can run the tsc command to compile everything to a single file called "validationmodule.js":

tsc --out validationmodule.js Validation.ts ZipCodeValidator.ts LettersOnlyValidator.ts ValidationExport.ts

The output is a CommonJS module you can use in Node.js:

var Validation = require("./validationmodule");

var zipCodeValidator = new Validation.ZipCodeValidator();
var lettersOnylValidator = new Validation.LettersOnlyValidator();

console.log(zipCodeValidator.isAcceptable("16211"));
console.log(lettersOnylValidator.isAcceptable("5555"));
Faris Zacina
  • 14,056
  • 7
  • 62
  • 75
  • Thanks, great response. I guess this then leads me to the question: what's the drawback of using internal modules? I stopped using them a while back as managing script dependencies was a faff. – LJW Oct 29 '14 at 19:24
  • Internal modules are just namespaces. They shouldn't pose any limitation in your code. In my example above you have script dependencies and it works fine. If you have a more complex dependency scenario that didn't work you can post it as a question ;) – Faris Zacina Oct 29 '14 at 19:26
  • I'm having trouble implementing this solution. Specifically, when I run the line `tsc --out validationmodule.js Validation.ts [...] ValidationExport.ts`, tsc raises the error `TS2304: Cannot find name 'module'.` when it attempts to process `ValidationExport.ts` – Jthorpe Mar 22 '15 at 18:59
  • @Jthorpe i have updated my answer with a ValidationExport.ts code that should make the compiler happy. – Faris Zacina Mar 22 '15 at 20:32
  • 1
    Thanks! I was just about to post a similar fix using this declaration `declare var module;` but you beat me to the punch by a few seconds... – Jthorpe Mar 22 '15 at 20:35
  • I like the simple trick, thanks a lot. I even loaded my js file in a browser. There I got an "Uncaught TypeError: Cannot set property 'exports' of undefined", so I changed my line to: `var module: any = module || {};` and seems to work everywhere. – Dan Marshall May 15 '15 at 04:03
  • 1
    You sir, have made my day with this post. I've been trying solutions that only partially resolve my issues, but this idea has done it 100%. Thank you! :* – Matt Sep 03 '16 at 22:44
1

Having a separate CommonJS module per file is completely appropriate. All the require calls in TypeScript will translate to CommonJS require calls in JavaScript, and the .d.ts files will be picked up in the process. (If you're doing something silly like requireing classes outside your source directory... stop.)

You would only need to consider a packaging step if you intended to use this NPM package in other applications, in which case look into Browserify

nexus
  • 510
  • 3
  • 14