nickarthur
4/29/2018 - 3:50 AM

TypeScript + RxJS + Closure Compiler setup

TypeScript + RxJS + Closure Compiler setup

I'm just going to make a note of this for Future Paul, because I spent way too long trying to get this working. If anyone has a better version of this workflow let me know.

Here's the rough idea:

  1. Have some TypeScript
  2. With some RxJS. Only include the bits of RxJS that we actually need because bundle sizes.
  3. Combine to a bundle using Closure Compiler (because it's super good at crushing down JS).

Because Future Paul will want it, here's the installs needed:

npm i google-closure-compiler rollup-plugin-commonjs rollup-plugin-node-resolve rxjs

# You might not want global, I usually do.
npm i -g typescript rollup

Take the following code:

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/filter';

class Sample {
  static main () {
    // Create a number every second, but filter to just even numbers...
    const even = Observable.interval(1000)
        .filter(data => data % 2 === 0);

    // Now do the same again, but for odd numbers.
    const odd = Observable.interval(1000)
        .filter(data => data % 2 === 1)

    Sample.combineNumbers(even, odd)
        .subscribe(data => console.log('Number', data));
  }

  static combineNumbers(a:Observable<number>, b:Observable<number>):Observable<number> {
    return Observable.merge(a, b);
  }
}

Sample.main();

Firstly, lol at the naming of the functions etc... I'm one step away from "Enterprise" coding here, folks. Send help.

Anyway, it's a bit of TypeScript that creates a couple of Observables and merges them to a third, final Observable and logs out its values.

TypeScript => JavaScript

You need a tsconfig.json file in the root.

{
    "compilerOptions": {
        "target": "es6",
        "module": "commonjs"
    }
}

Next up, compile the TypeScript.

tsc -p .

Which will output JavaScript that looks like this:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Observable_1 = require("rxjs/Observable");
require("rxjs/add/observable/interval");
require("rxjs/add/observable/merge");
require("rxjs/add/operator/filter");
class Sample {
    static main() {
        // Create a number every second, but filter to just even numbers...
        const even = Observable_1.Observable.interval(1000)
            .filter(data => data % 2 === 0);
        // Now do the same again, but for odd numbers.
        const odd = Observable_1.Observable.interval(1000)
            .filter(data => data % 2 === 1);
        Sample.combineNumbers(even, odd)
            .subscribe(data => console.log('Number', data));
    }
    static combineNumbers(a, b) {
        return Observable_1.Observable.merge(a, b);
    }
}
Sample.main();

Resolving the requires

Those requires need to be resolved. For what it's worth I tried using Tsickle rather than tsc directly, and that generates similar output save that the requires are goog.require calls instead. But, because Closure Compiler doesn't like the goog.require calls against non-Closure code, I had to abandon that.

Instead we can run rollup, for which we first need a rollup.config.js:

import cjs from 'rollup-plugin-commonjs';
import nodeResolve from 'rollup-plugin-node-resolve';

export default {
  entry: 'src/app.js',
  dest: 'src/bundle.js',
  format: 'iife',
  moduleName: 'Sample',
  plugins: [
    nodeResolve({jsnext: true, main: true}),
    cjs()
  ]
};

And now call rollup with the config flag.

rollup -c

Closure Compile

Now we have a bundle, but it has comments and all sorts of unnecessary gubbins in there (though functionally it should just be the stuff we need). Let's hand it off to Closure Compiler to have it squish the output:

java -jar ./node_modules/google-closure-compiler/compiler.jar --js src/bundle.js --language_in=ECMASCRIPT6 --language_out=ES5_STRICT --compilation_level SIMPLE_OPTIMIZATIONS | tee > dist/app.js

It would be nice to have ADVANCED_OPTIMIZATIONS enabled here because huge size reductions FTW, but because the RxJS isn't a simple match with Closure, there's no easy option here AFAICT.

With all that done, it should now at least build and spit out some JS that works.