Importing lettable RxJS operators

Alexander Abakumov

RxJS 5.5 makes a big breaking change and introduces lettable operators to replace basically all operators (called "patch" operators) we used to use before.

That article contains a note:

Lettable operators can now be imported from rxjs/operators, but doing so without changing your build process will often result in a larger application bundle. This is because by default rxjs/operators will resolve to the CommonJS output of rxjs.

This statement is easy to proof on the practice with the brand new AngularCLI-generated app.

When we have an application that doesn't import anything from RxJS:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from "@angular/common/http";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  public title = 'app';

  constructor(private readonly http: HttpClient) {
  }

  public ngOnInit(): void {
    this.http.get('https://api.github.com/users')
      .subscribe(response => {
        console.log(response);
      });
  }
}

We can see the following:

ng build --prod
chunk {0} polyfills.e1f97a0070e18e96a6be.bundle.js (polyfills) 61.4 kB {4} [initial] [rendered]
chunk {1} main.b2b5d212102ca9d103e8.bundle.js (main) 4.92 kB {3} [initial] [rendered]
chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 0 bytes {4} [initial] [rendered]
chunk {3} vendor.4b7be3dbe842aec3f0ab.bundle.js (vendor) 236 kB [initial] [rendered]
chunk {4} inline.387c7023e5627ac04221.bundle.js (inline) 1.45 kB [entry] [rendered]

When we import a RxJS operator the "old" way and use it:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import "rxjs/add/operator/map";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  public title = 'app';

  constructor(private readonly http: HttpClient) {
  }

  public ngOnInit(): void {
    this.http.get('https://api.github.com/users')
      .map((u: any) => 1)
      .subscribe(response => {
        console.log(response);
      });
  }
}

We can see no gain in size of bundles:

chunk {0} polyfills.e1f97a0070e18e96a6be.bundle.js (polyfills) 61.4 kB {4} [initial] [rendered]
chunk {1} main.229ad10195bbb426b3e8.bundle.js (main) 4.96 kB {3} [initial] [rendered]
chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 0 bytes {4} [initial] [rendered]
chunk {3} vendor.933334fc50e7008778fe.bundle.js (vendor) 236 kB [initial] [rendered]
chunk {4} inline.6a52179d8b19cd3cc179.bundle.js (inline) 1.45 kB [entry] [rendered]

When we try to import and use the lettable operator instead as recommended but without modifying the build process:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { map } from "rxjs/operators";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  public title = 'app';

  constructor(private readonly http: HttpClient) {
  }

  public ngOnInit(): void {
    this.http.get('https://api.github.com/users').pipe(
        map((u: any) => 1))
      .subscribe(response => {
        console.log(response);
      });
  }
}

We see that the vendor bundle is 108 kB bigger which tells us that RxJS hasn't been tree-shaked:

chunk {0} polyfills.e1f97a0070e18e96a6be.bundle.js (polyfills) 61.4 kB {4} [initial] [rendered]
chunk {1} main.450c741a106157402dcd.bundle.js (main) 4.97 kB {3} [initial] [rendered]
chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 0 bytes {4} [initial] [rendered]
chunk {3} vendor.3f53f0e2283f4c44ec38.bundle.js (vendor) 344 kB [initial] [rendered]
chunk {4} inline.2d973ef5a10aa806b082.bundle.js (inline) 1.45 kB [entry] [rendered]

When I try to import the lettable operator as recommended in the No Control over Build Process section of the article:

import { map } from "rxjs/operators/map";

I'm getting a build error:

./src/app/app.component.ts
Module not found: Error: Can't resolve 'rxjs/operators/map' in 'c:\Projects\Angular\src\app'
 @ ./src/app/app.component.ts 14:0-41
 @ ./src/app/app.module.ts
 @ ./src/main.ts
 @ multi webpack-dev-server/client?http://localhost:4200 ./src/main.ts
  1. What I'm doing wrong?
  2. How can we import new RxJS lettable operators in an Angular CLI app so that RxJS would still be tree-shaked?

UPDATE: Package versions (basically all are latest "wanted" versions of an AngularCLI app at the moment):

rxjs: 5.5.0
@angular/cli: 1.4.9
node: 8.6.0
os: win32 x64
@angular/animations: 4.4.6
@angular/common: 4.4.6
@angular/compiler: 4.4.6
@angular/core: 4.4.6
@angular/forms: 4.4.6
@angular/http: 4.4.6
@angular/platform-browser: 4.4.6
@angular/platform-browser-dynamic: 4.4.6
@angular/router: 4.4.6
@angular/cli: 1.4.9
@angular/compiler-cli: 4.4.6
@angular/language-service: 4.4.6
typescript: 2.3.4
Richard Matsen

For completeness, @angular/[email protected] adds the following in models/webpack-configs/common.js

// Read the tsconfig to determine if we should prefer ES2015 modules.
// Load rxjs path aliases.
// https://github.com/ReactiveX/rxjs/blob/master/doc/lettable-operators.md#build-and-treeshaking
let alias = {};
try {
    const rxjsPathMappingImport = 'rxjs/_esm5/path-mapping';
    const rxPaths = require_project_module_1.requireProjectModule(projectRoot, rxjsPathMappingImport);
    alias = rxPaths(nodeModules);
}
catch (e) { }

    resolve: {
        extensions: ['.ts', '.js'],
        modules: ['node_modules', nodeModules],
        symlinks: !buildOptions.preserveSymlinks,
        alias
    },

and rxjs/_esm5/path-mapping.js has these two entries

"rxjs/operators": path.resolve(PATH_REPLACEMENT, "rxjs/_esm5/operators/index.js"),
...
"rxjs/operators/map": path.resolve(PATH_REPLACEMENT, "rxjs/_esm5/operators/map.js"),

The essential bit of the error message is

aliased with mapping 'rxjs/operators': 
  'C:\Dev\Projects\rx55\node_modules\rxjs\_esm5\operators\index.js' to 
  'C:\Dev\Projects\rx55\node_modules\rxjs\_esm5\operators\index.js/map'
...
C:\Dev\Projects\rx55\node_modules\rxjs\_esm5\operators\index.js\map doesn't exist

so the first mapping is interfering with the second.

By reversing the order of the mappings the build works, so to my mind the fault is with rxjs v5.5.

That said, Alexander's work-around is the path to take until the fix.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related