Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why is file size so big? #3971

Closed
bcherny opened this issue Jul 29, 2018 · 8 comments
Closed

Why is file size so big? #3971

bcherny opened this issue Jul 29, 2018 · 8 comments

Comments

@bcherny
Copy link
Contributor

bcherny commented Jul 29, 2018

I'm using RxJS6 in Undux, via typed-rx-emitter. I use it by importing Observable, and calling Observable.create. I don't use any other Rx methods.

Bundling Undux (+typed-rx-emitter, +rxjs) in as part of undux-todomvc, I see all sorts of things included in the bundle that I'm not using. Eg:

  • CombineLatestOperator
  • MergeMapOperator
  • MapOperator
  • RaceOperator
  • ZipOperator
  • AsyncSubject
  • BehaviorSubject
  • Subject
  • ReplayEvent
  • AsapScheduler
  • VirtualTimeScheduler
  • ForkJoinSubscriber
  • ZipBufferIterator

All in all, that's ~80kb (unmangled). Even after running the code through Uglify's tree shaker, these are included. Are they core parts of Rx.Observable.create? Is there a way to remove them? Am I doing something wrong when bundling?

Repro:

Generated bundle.js (after Uglify)

Uglify options:

new UglifyJsPlugin({
  cache: true,
  parallel: true,
  sourceMap: true,
  uglifyOptions: {
    keep_classnames: true,
    keep_fnames: true,
    mangle: false
  }
})
@kwonoj
Copy link
Member

kwonoj commented Jul 29, 2018

You are importing everything in rxjs surface api via https://github.com/bcherny/undux/blob/master/src/index.ts#L1, If codes are targeting v5 import each individual via corresponding subimport, if it's v6 follow guide for optimization. https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md#build-and-treeshaking

@kwonoj kwonoj closed this as completed Jul 29, 2018
@bcherny
Copy link
Contributor Author

bcherny commented Jul 29, 2018

You are importing everything in rxjs

No, I'm only importing the type. It's compiled away by TSC before it reaches Webpack. Confirmed by avoiding the wildcard import.

if it's v6 follow guide for optimization

Those instructions only cover operators and don't talk about Observable.create. No change in file size (code).

@kwonoj
Copy link
Member

kwonoj commented Jul 29, 2018

You have incorrect understanding of subpath import.

import {Observable} from 'rxjs';

is nothing more than

import * as rx from 'rxjs';
const ({Observable} = rx);

ends up importing all surface as same as before.

Try create simple test.ts as below

import { Observable } from 'rxjs';

console.log(Observable);

and transpile it: you'll get

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var rxjs_1 = require("rxjs");
console.log(rxjs_1.Observable);
//# sourceMappingURL=index.js.map

CJS will import whole rxjs still.

contray, if you import subpath

import { Observable } from 'rxjs/Observable';

console.log(Observable);

transpiliation limits import path

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Observable_1 = require("rxjs/Observable");
console.log(Observable_1.Observable);
//# sourceMappingURL=index.js.map

@kwonoj
Copy link
Member

kwonoj commented Jul 29, 2018

in short, impot path is bit different between v5 and v6, but do not import everything is key.

@kwonoj
Copy link
Member

kwonoj commented Jul 29, 2018

No, I'm only importing the type. It's compiled away by TSC

also, this is very true and if you only use types, it should have zero actual codes. But also, you have specified package as peerdep - how do you actually confirm it's type only, especially you mentioned you do call Observable.create? it's runtime call, so there must be imported code somewhere.

@bcherny
Copy link
Contributor Author

bcherny commented Jul 29, 2018

@kwonoj Without rxjs-compat, import {Observable} from 'rxjs/Observable' doesn't seem to point to anything with RxJS6. Also, it seems to be at odds with the docs:

import { Observable, Subject, ReplaySubject, from, of, range } from 'rxjs';

https://github.com/ReactiveX/rxjs#es6-via-npm

And

Import paths

If you're a TypeScript developer, it's recommended that you use rxjs-tslint to refactor your import paths.
For JavaScript developers, the general rule is as follows:

  1. rxjs: Creation methods, types, schedulers and utilities
    import { Observable, Subject, asapScheduler, pipe, of, from, interval, merge, fromEvent, SubscriptionLike, PartialObserver } from 'rxjs';

https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/migration.md#import-paths

Also this?

import {Observable} from '@reactivex/rxjs/es6/Observable.js'

http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html

And to be clear, my actual import is in typed-rx-emitter:

import { Observable, Observer } from 'rxjs'

https://github.com/bcherny/typed-rx-emitter/blob/0b94942/index.ts#L1

@kwonoj
Copy link
Member

kwonoj commented Jul 29, 2018

above snippet written through v5, as your package peerdep specifies rx >=5 is allowed.

As mentioned impot path is bit different between v5 and v6,, per version it may vary and migration documentation describes way to enable treeshaking for those. If you think there is an issue with rx, please create repo with webpack config / import code allows to reproduces in single repo, instead of multiple packages involved.

@bcherny
Copy link
Contributor Author

bcherny commented Jul 29, 2018

Figured out my issue - in TS, you have to compile to es2015 modules, not commonjs. Here's the repro.

tl;dr-

With "module": "commonjs" vs "module": "es2015" in tsconfig.json:

"module": "commonjs" "module": "es2015"
rxjs 79.6 KiB 14.7 KiB
rxjs-compat 80 KiB 80.1 KiB

That means if my package A depends on rxjs, and some package Z indirectly depends on A (maybe A -> B -> C -> Z), then every intermediate package (A, B, and C) has to ship with ES2015 exports, otherwise it will unintentionally bloat Z's file size.

That seems wrong. Since file size is Z's concern, we shouldn't expect A, B, and C to make sure they ship special ES2015 builds specifically for consumers like Z. Shipping both a CommonJS and an ES2015 build in a single NPM package is still super unusual, and it seems bad to put the burden on A, B, and C to do it.

An alternative solution is to fix RxJS6's re-exports in compat mode, so that even if you or one of your dependencies compiled rxjs to CommonJS, you can still tree-shake. I put up a PR to make this change here: #3974.

The results seem to be clear:

Before patch

"module": "commonjs" "module": "es2015"
rxjs 79.6 KiB 14.7 KiB
rxjs-compat 80 KiB 80.1 KiB

After patch

"module": "commonjs" "module": "es2015"
rxjs 79.6 KiB 14.7 KiB
rxjs-compat 18.6 KiB 18.7 KiB

That said, I'm not familiar with RxJS's codebase, and this kind of module and bundling stuff can be tricky. Please let me know if I missed something.

@lock lock bot locked as resolved and limited conversation to collaborators Aug 28, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants