Search code examples
rxjssystemjstypescript2.0

Error constructing an rxjs/Observable


Why does the following TypeScript code compile, but systemjs fails to load the dependencies correctly at runtime?

import { Observable } from 'rxjs';
let temp123 = new Observable<String>();

However, this works:

import { Observable } from 'rxjs/Observable';
let temp123 = new Observable<String>();

Specifically, the first code results in a .js file that contains the code:

var Observable_1 = require('rxjs');
var temp123 = new Observable_1.Observable();

but the second code generates this:

var Observable_1 = require('rxjs/Observable');
var temp123 = new Observable_1.Observable();

the line require('rxjs') fails with a 404 error because there is no file there. Why is the typescript compiler able to resolve this, but systemjs cannot load it at runtime?

Also noteworthy: This problem only happens if I do certain things with the Observable. For example, the following code works:

import { Observable } from 'rxjs';
let temp123: Observable<String> = null;
let xyz = temp123.first();

I can use the Observable, and call methods on it, without the TypeScript compiler generated a require('rxjs'). But I can't construct one, and I can't extend it either.

Versions:TypeScript 2.0.3, Systemjs 0.19.27, rxjs 5.0.0-beta.12


Solution

  • Why is the typescript compiler able to resolve this, but systemjs cannot load it at runtime?

    That's the way it works:

    when you write import { Observable } from 'rxjs'; typescript finds rxjs folder in node_modules with package.json in it, which has

     "typings": "Rx.d.ts"
    

    that's type declarations file for rxjs, and that file contains

      export { Observable } from './Observable';
    

    which makes typescript to find another type declaration file in the same folder, Observable.d.ts, which has exported declaration for Observable class.

    That's enough for your code to compile without errors.

    If your code does not actually try to use Observable as a value, it will work, because typescript does unused reference elision - if Observable is used for type checking only, as in your second example, there will be no require('rxjs') call in generated javascrpt.

    Now, SystemJS.

    SystemJS does not have any default location to look for modules - it does not even recognise node_modules convention about package.json file with main property.

    So, most likely, SystemJS in your example is configured like this:

        SystemJS.config({
            paths: {'npm:': 'node_modules/'},
            map: {'rxjs': 'npm:rxjs'},
            packages: {
                rxjs: {
                }
            }
        });
    

    So, the module rxjs/Observable imported by this line

    import { Observable } from 'rxjs/Observable';
    

    is mapped to

    node_modules/rxjs/Observable.js
    

    because rxjs prefix matches map entry which together with paths maps it to node_modules/rxjs

    Observable part comes through as is

    .js extension is added because rxjs matches with rxjs package in systemjs config, and for any module that belongs to a package, SystemJS adds .js extension automatically unless defaultExtension is set to something else in that package config.

    And it works, because the file node_modules/rxjs/Observable.js exists.

    And that import works with typescript too, because node_modules/rxjs/Observable.d.ts exists too.

    Finally, this does not work at runtime

    import { Observable } from 'rxjs';
    

    because it's mapped to node_modules/rxjs url, and there is no actual file there.

    You can fix it by using main property in SystemJS package config:

            packages: {
                rxjs: {
                    main: 'Rx.js'
                }
            }
    

    Now it's mapped to node_modules/rxjs/Rx.js, and that file actually exists and exports something named Observable, so it should work.

    Checked with SystemJS 0.19.43, rxjs 5.0.3, typescript 2.1.5.