diff --git a/.travis.yml b/.travis.yml index fde2abf9..c4cd5143 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,9 @@ language: node_js node_js: - lts/* - node +script: + - yarn test --code-coverage + - yarn build + +after_script: + - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 84b17a62..9ffbdf25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## v2.1.0 + +This release introduces the **minimum duration** option. It gives the possibility to force a minimum duration during which the spinner should be visible. +You can mix this parameter with the **debounce delay** option : + +```xml + + +``` + +``` +Debounce delay: 100ms +Min. duration time: 300ms +---0ms--------100ms------------180ms-----------400ms-- +----|----------|-----------------|---------------|---- +req starts spinner shows req ends spinner hides +``` + +``SpinnerVisibilityService#visibilityObservable`` and ``PendingInterceptorService#pendingRequestsStatus`` have been respectively deprecated in favor of ``visibilityObservable$`` and ``pendingRequestsStatus$`` (note the **$**). + ## v2.0.0 The module bundling now uses [ng-packagr](https://github.com/dherges/ng-packagr). diff --git a/README.md b/README.md index ec8dd824..45743c5c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # ng-http-loader [![Build Status](https://travis-ci.org/mpalourdio/ng-http-loader.svg?branch=master)](https://travis-ci.org/mpalourdio/ng-http-loader) +[![Coverage Status](https://coveralls.io/repos/github/mpalourdio/ng-http-loader/badge.svg?branch=master)](https://coveralls.io/github/mpalourdio/ng-http-loader?branch=master) [![npm](https://img.shields.io/npm/v/ng-http-loader.svg)](https://www.npmjs.com/package/ng-http-loader) [![npm](https://img.shields.io/npm/dm/ng-http-loader.svg)](https://www.npmjs.com/package/ng-http-loader) @@ -78,13 +79,13 @@ In your app.component.html, simply add : ## Customizing the spinner -You can customize the **background-color**, the **spinner type** and the **debounce delay** (ie. after how many milliseconds the spinner will be displayed, if needed): +You can customize the **background-color**, the **spinner type**, the **debounce delay** (ie. after how many milliseconds the spinner will be visible, if needed), the **minimum duration** (ie. how many milliseconds should the spinner be visible at least): ```xml + [debounceDelay]="100" + [minDuration]="300"> ``` diff --git a/package.json b/package.json index 7de13aa8..3c2882b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ng-http-loader", - "version": "2.0.0", + "version": "2.1.0", "scripts": { "ng": "ng", "build": "ng build", @@ -52,6 +52,7 @@ "@types/node": "~8.9.4", "codelyzer": "~4.2.1", "core-js": "^2.5.4", + "coveralls": "^3.0.1", "jasmine-core": "~2.99.1", "jasmine-spec-reporter": "~4.2.1", "karma": "~2.0.0", @@ -60,7 +61,7 @@ "karma-jasmine": "~1.1.1", "karma-jasmine-html-reporter": "^0.2.2", "karma-phantomjs-launcher": "^1.0.4", - "ng-packagr": "^3.0.0-rc.5", + "ng-packagr": "^3.0.0", "phantomjs-prebuilt": "^2.1.14", "rxjs": "^6.0.0", "ts-node": "~5.0.1", diff --git a/src/lib/components/spinner/spinner.component.html b/src/lib/components/spinner/spinner.component.html index 7019c6c3..d5c1e37b 100644 --- a/src/lib/components/spinner/spinner.component.html +++ b/src/lib/components/spinner/spinner.component.html @@ -3,42 +3,42 @@ diff --git a/src/lib/components/spinner/spinner.component.ts b/src/lib/components/spinner/spinner.component.ts index 26980c1e..59440610 100644 --- a/src/lib/components/spinner/spinner.component.ts +++ b/src/lib/components/spinner/spinner.component.ts @@ -8,8 +8,8 @@ */ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { merge, Observable, Subscription, timer } from 'rxjs'; -import { debounce } from 'rxjs/operators'; +import { EMPTY, merge, Observable, Subscription, timer } from 'rxjs'; +import { debounce, delayWhen } from 'rxjs/operators'; import { PendingInterceptorService } from '../../services/pending-interceptor.service'; import { SpinnerVisibilityService } from '../../services/spinner-visibility.service'; import { Spinkit } from '../../spinkits'; @@ -21,9 +21,10 @@ import { Spinkit } from '../../spinkits'; }) export class SpinnerComponent implements OnDestroy, OnInit { public isSpinnerVisible: boolean; + public spinkit = Spinkit; private subscriptions: Subscription; + private startTime: number; - public Spinkit = Spinkit; @Input() public backgroundColor: string; @Input() @@ -33,12 +34,17 @@ export class SpinnerComponent implements OnDestroy, OnInit { @Input() public debounceDelay = 0; @Input() + public minDuration = 0; + @Input() public entryComponent: any = null; constructor(private pendingInterceptorService: PendingInterceptorService, private spinnerVisibilityService: SpinnerVisibilityService) { this.subscriptions = merge( - this.pendingInterceptorService.pendingRequestsStatus.pipe(debounce(this.handleDebounce.bind(this))), - this.spinnerVisibilityService.visibilityObservable, + this.pendingInterceptorService.pendingRequestsStatus$.pipe( + debounce(this.handleDebounceDelay.bind(this)), + delayWhen(this.handleMinDuration.bind(this)) + ), + this.spinnerVisibilityService.visibilityObservable$, ) .subscribe(this.handleSpinnerVisibility().bind(this)); } @@ -71,11 +77,24 @@ export class SpinnerComponent implements OnDestroy, OnInit { return v => this.isSpinnerVisible = v; } - private handleDebounce(hasPendingRequests: boolean): Observable { - if (hasPendingRequests) { + private handleDebounceDelay(hasPendingRequests: boolean): Observable { + if (hasPendingRequests && !!this.debounceDelay) { return timer(this.debounceDelay); } - return timer(0); + return EMPTY; + } + + private handleMinDuration(hasPendingRequests: boolean): Observable { + if (hasPendingRequests || !this.minDuration) { + this.startTime = Date.now(); + + return timer(0); + } + + const timerObservable = timer(this.minDuration - (Date.now() - this.startTime)); + this.startTime = null; + + return timerObservable; } } diff --git a/src/lib/services/pending-interceptor.service.ts b/src/lib/services/pending-interceptor.service.ts index 8c44faac..b9ec81f5 100644 --- a/src/lib/services/pending-interceptor.service.ts +++ b/src/lib/services/pending-interceptor.service.ts @@ -21,10 +21,15 @@ export class PendingInterceptorService implements HttpInterceptor { private _filteredUrlPatterns: RegExp[] = []; private _forceByPass: boolean; + /** @deprecated Deprecated in favor of pendingRequestsStatus$ */ get pendingRequestsStatus(): Observable { return this._pendingRequestsStatus.asObservable(); } + get pendingRequestsStatus$(): Observable { + return this.pendingRequestsStatus; + } + get pendingRequests(): number { return this._pendingRequests; } diff --git a/src/lib/services/spinner-visibility.service.ts b/src/lib/services/spinner-visibility.service.ts index ffee64fe..b12e766b 100644 --- a/src/lib/services/spinner-visibility.service.ts +++ b/src/lib/services/spinner-visibility.service.ts @@ -20,10 +20,15 @@ export class SpinnerVisibilityService { constructor(private pendingInterceptorService: PendingInterceptorService) { } + /** @deprecated Deprecated in favor of visibilityObservable$ */ get visibilityObservable(): Observable { return this._visibilitySubject.asObservable(); } + get visibilityObservable$(): Observable { + return this.visibilityObservable; + } + public show(): void { this.pendingInterceptorService.forceByPass = true; this._visibilitySubject.next(true); diff --git a/src/test/components/spinner/spinner.component.spec.ts b/src/test/components/spinner/spinner.component.spec.ts index 70c7a272..636d1503 100644 --- a/src/test/components/spinner/spinner.component.spec.ts +++ b/src/test/components/spinner/spinner.component.spec.ts @@ -9,7 +9,7 @@ import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { async, ComponentFixture, discardPeriodicTasks, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { forkJoin, Observable } from 'rxjs'; import { SpinnerComponent } from '../../../lib/components/spinner/spinner.component'; @@ -222,11 +222,11 @@ describe('SpinnerComponent', () => { tick(999); expect(component.isSpinnerVisible).toBeFalsy(); - // the http request is pending for 2 seconds now - the spinner will be displayed + // the http request is pending for 2 seconds now - the spinner will be visible tick(1); expect(component.isSpinnerVisible).toBeTruthy(); - // the http request is pending for 5 seconds now - the spinner is still displayed + // the http request is pending for 5 seconds now - the spinner is still visible tick(3000); expect(component.isSpinnerVisible).toBeTruthy(); @@ -234,9 +234,6 @@ describe('SpinnerComponent', () => { httpMock.expectOne('/fake').flush({}); tick(); expect(component.isSpinnerVisible).toBeFalsy(); - - // https://github.com/angular/angular/issues/10127 - discardPeriodicTasks(); } ))); @@ -261,20 +258,20 @@ describe('SpinnerComponent', () => { tick(999); expect(component.isSpinnerVisible).toBeFalsy(); - // the http requests are pending for 2 seconds now - the spinner will be displayed + // the http requests are pending for 2 seconds now - the spinner will be visible tick(1); expect(component.isSpinnerVisible).toBeTruthy(); - // the http requests are pending for 5 seconds now - the spinner is still displayed + // the http requests are pending for 5 seconds now - the spinner is still visible tick(3000); expect(component.isSpinnerVisible).toBeTruthy(); - // the first http request is finally over, the spinner is still displayed + // the first http request is finally over, the spinner is still visible firstRequest.flush({}); tick(); expect(component.isSpinnerVisible).toBeTruthy(); - // the second request is pending for 8 seconds now - the spinner is still displayed + // the second request is pending for 8 seconds now - the spinner is still visible tick(3000); expect(component.isSpinnerVisible).toBeTruthy(); @@ -282,9 +279,6 @@ describe('SpinnerComponent', () => { secondRequest.flush({}); tick(); expect(component.isSpinnerVisible).toBeFalsy(); - - // https://github.com/angular/angular/issues/10127 - discardPeriodicTasks(); } ))); @@ -308,13 +302,14 @@ describe('SpinnerComponent', () => { http.get('/fake').subscribe(); tick(); expect(component.isSpinnerVisible).toBeTruthy(); + // the http request ends, but we want the spinner to be still visible httpMock.expectOne('/fake').flush({}); tick(); expect(component.isSpinnerVisible).toBeTruthy(); spinner.hide(); - // this time the spinner is not displayed anymore + // this time the spinner is not visible anymore expect(component.isSpinnerVisible).toBeFalsy(); // the bypassPendingInterceptorService should be reset for next http requests @@ -326,4 +321,151 @@ describe('SpinnerComponent', () => { expect(component.isSpinnerVisible).toBeFalsy(); } ))); + + it('should correctly handle the minimum spinner duration for a single http request', fakeAsync(inject( + [HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { + component.minDuration = 5000; + http.get('/fake').subscribe(); + + // the http request is pending for 1 second now + tick(1000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the http request is pending for 2 seconds now + tick(1000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the http request is finally over, the spinner is still visible + httpMock.expectOne('/fake').flush({}); + tick(); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the http request is over but the spinner is still visible after 3 seconds + tick(1000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the spinner is still visible after 4 seconds + tick(1000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the spinner is still visible after 4,999 seconds + tick(999); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the spinner is not visible anymore after 5 seconds + tick(1); + expect(component.isSpinnerVisible).toBeFalsy(); + } + ))); + + it('should correctly handle the minimum spinner duration for multiple http requests', fakeAsync(inject( + [HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { + component.minDuration = 5000; + + function runQuery(url: string): Observable { + return http.get(url); + } + + forkJoin([runQuery('/fake'), runQuery('/fake2')]).subscribe(); + + const firstRequest = httpMock.expectOne('/fake'); + const secondRequest = httpMock.expectOne('/fake2'); + + // the http requests are pending for 1 second now + tick(1000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the http requests are pending for 2 seconds now + tick(1000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the first http request is finally over, the spinner is still visible + firstRequest.flush({}); + tick(); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the second http request is still pending after 3 seconds + tick(1000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the second http request is still pending after 4 seconds + tick(1000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the second http request is finally over too, the spinner is still visible + secondRequest.flush({}); + tick(); + expect(component.isSpinnerVisible).toBeTruthy(); + + // After 5 seconds, the spinner is hidden + tick(1000); + expect(component.isSpinnerVisible).toBeFalsy(); + } + ))); + + it('should still display the spinner when the minimum duration is inferior to the http request duration', fakeAsync(inject( + [HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { + component.minDuration = 1000; + http.get('/fake').subscribe(); + + // the http request is pending for 1 second now + tick(1000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the http request is pending for 2 seconds now + tick(1000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the http request is finally over after 2 seconds, the spinner is hidden + httpMock.expectOne('/fake').flush({}); + tick(); + expect(component.isSpinnerVisible).toBeFalsy(); + } + ))); + + it('should be possible to set the minimum duration without side effect on manual show/hide', inject( + [SpinnerVisibilityService], (spinner: SpinnerVisibilityService) => { + component.minDuration = 10000; + spinner.show(); + expect(component.isSpinnerVisible).toBeTruthy(); + + spinner.hide(); + expect(component.isSpinnerVisible).toBeFalsy(); + } + )); + + it('should be possible to mix debounce delay and minimum duration', fakeAsync(inject( + [HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => { + // the spinner should not be visible the first second, then visible for 5 seconds + component.minDuration = 5000; + component.debounceDelay = 1000; + + http.get('/fake').subscribe(); + + // the http request is pending for 0,5 second now - spinner not visible because debounce + tick(500); + expect(component.isSpinnerVisible).toBeFalsy(); + + // the http request is pending for 1 second now - spinner visible + tick(500); + expect(component.isSpinnerVisible).toBeTruthy(); + + // the http request is finally over, the spinner is still visible + httpMock.expectOne('/fake').flush({}); + tick(); + expect(component.isSpinnerVisible).toBeTruthy(); + + // after 3 seconds, the spinner is still visible + tick(2000); + expect(component.isSpinnerVisible).toBeTruthy(); + + // after 5,999 seconds, the spinner is still visible + tick(2999); + expect(component.isSpinnerVisible).toBeTruthy(); + + // after 6 seconds (1s for debounce + 5s min. duration), the spinner is hidden + tick(1); + expect(component.isSpinnerVisible).toBeFalsy(); + } + ))); }); diff --git a/src/test/services/pending-interceptor.service.spec.ts b/src/test/services/pending-interceptor.service.spec.ts index f08903ca..e945287e 100644 --- a/src/test/services/pending-interceptor.service.spec.ts +++ b/src/test/services/pending-interceptor.service.spec.ts @@ -55,7 +55,7 @@ describe('PendingInterceptorService', () => { inject( [PendingInterceptorService, HttpClient, HttpTestingController], (service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => { - const pendingRequestsStatus = service.pendingRequestsStatus; + const pendingRequestsStatus = service.pendingRequestsStatus$; pendingRequestsStatus .subscribe( @@ -75,7 +75,7 @@ describe('PendingInterceptorService', () => { http.get('/fake').subscribe(); httpMock.expectOne('/fake'); - const pendingRequestsStatus = service.pendingRequestsStatus; + const pendingRequestsStatus = service.pendingRequestsStatus$; pendingRequestsStatus .subscribe( (next: boolean) => expect(next).toBeTruthy(), diff --git a/src/test/services/spinner-visibility.service.spec.ts b/src/test/services/spinner-visibility.service.spec.ts index d3610a65..6df0330c 100644 --- a/src/test/services/spinner-visibility.service.spec.ts +++ b/src/test/services/spinner-visibility.service.spec.ts @@ -23,12 +23,12 @@ describe('SpinnerVisibilityService', () => { })); it('should define a subject', inject([SpinnerVisibilityService], (service: SpinnerVisibilityService) => { - expect(service.visibilityObservable).toBeTruthy(); + expect(service.visibilityObservable$).toBeTruthy(); })); it('should pipe \'true\' when calling show()', inject([SpinnerVisibilityService], (spinner: SpinnerVisibilityService) => { spinner.show(); - spinner.visibilityObservable.subscribe(result => { + spinner.visibilityObservable$.subscribe(result => { expect(result).toBeTruthy(); }, error => { @@ -38,7 +38,7 @@ describe('SpinnerVisibilityService', () => { it('should pipe \'false\' when calling hide()', inject([SpinnerVisibilityService], (spinner: SpinnerVisibilityService) => { spinner.hide(); - spinner.visibilityObservable.subscribe(result => { + spinner.visibilityObservable$.subscribe(result => { expect(result).toBeFalsy(); }, error => { diff --git a/tsconfig.lib.json b/tsconfig.lib.json index d2f948b5..7c5da68f 100644 --- a/tsconfig.lib.json +++ b/tsconfig.lib.json @@ -9,7 +9,6 @@ "importHelpers": true, "noImplicitAny": true, "noImplicitReturns": true, - "removeComments": true, "types": [], "lib": [ "dom", diff --git a/yarn.lock b/yarn.lock index b09a3cff..0cbfd4dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -191,9 +191,9 @@ semver "^5.3.0" semver-intersect "^1.1.2" -"@types/estree@0.0.38": - version "0.0.38" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.38.tgz#c1be40aa933723c608820a99a373a16d215a1ca2" +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" "@types/jasmine@*", "@types/jasmine@~2.8.6": version "2.8.7" @@ -1510,6 +1510,16 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: parse-json "^2.2.0" require-from-string "^1.1.0" +coveralls@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.0.1.tgz#12e15914eaa29204e56869a5ece7b9e1492d2ae2" + dependencies: + js-yaml "^3.6.1" + lcov-parse "^0.0.10" + log-driver "^1.2.5" + minimist "^1.2.0" + request "^2.79.0" + cpx@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f" @@ -3629,7 +3639,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@3.x, js-yaml@^3.4.3, js-yaml@^3.7.0: +js-yaml@3.x, js-yaml@^3.4.3, js-yaml@^3.6.1, js-yaml@^3.7.0: version "3.11.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" dependencies: @@ -3820,6 +3830,10 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" +lcov-parse@^0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3" + leb@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/leb/-/leb-0.3.0.tgz#32bee9fad168328d6aea8522d833f4180eed1da3" @@ -3941,6 +3955,10 @@ lodash@^4.0.0, lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.3, l version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" +log-driver@^1.2.5: + version "1.2.7" + resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" + log-symbols@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -4354,9 +4372,9 @@ next-tick@1: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" -ng-packagr@^3.0.0-rc.5: - version "3.0.0-rc.5" - resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-3.0.0-rc.5.tgz#847aa336e87f4fc7162709c9ebb713988abc2c28" +ng-packagr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-3.0.0.tgz#2f59735fcf59352d81a26864970bccbece60402c" dependencies: "@ngtools/json-schema" "^1.1.0" autoprefixer "^8.0.0" @@ -4375,7 +4393,7 @@ ng-packagr@^3.0.0-rc.5: postcss-url "^7.3.0" read-pkg-up "^3.0.0" rimraf "^2.6.1" - rollup "^0.58.0" + rollup "^0.59.0" rollup-plugin-commonjs "^9.1.3" rollup-plugin-node-resolve "^3.0.0" rollup-plugin-sourcemaps "^0.4.2" @@ -5525,7 +5543,7 @@ request-progress@^2.0.1: dependencies: throttleit "^1.0.0" -request@2, request@^2.0.0, request@^2.74.0, request@^2.81.0, request@^2.83.0: +request@2, request@^2.0.0, request@^2.74.0, request@^2.79.0, request@^2.81.0, request@^2.83.0: version "2.87.0" resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" dependencies: @@ -5708,11 +5726,11 @@ rollup-pluginutils@^2.0.1: estree-walker "^0.5.2" micromatch "^2.3.11" -rollup@^0.58.0: - version "0.58.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.58.2.tgz#2feddea8c0c022f3e74b35c48e3c21b3433803ce" +rollup@^0.59.0: + version "0.59.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.59.3.tgz#15dae74cb1b6a6b39a63c7096c1d6f47d8f2a5bd" dependencies: - "@types/estree" "0.0.38" + "@types/estree" "0.0.39" "@types/node" "*" run-queue@^1.0.0, run-queue@^1.0.3: