From e653aaa66a3dbffeb1fc82009fa08def715006cd Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 2 Jan 2020 14:04:31 +0300 Subject: [PATCH] Clean up generic hooks, use react-use instead (#53822) As we recently added react-use as a dependency, makes sense to clean up those generic hooks from Kibana repo. Removed custom hooks from kibana_react and other places: useObservable useUnmount useShallowCompareEffect react-use should be used instead: import useObservable from 'react-use/lib/useObservable' --- package.json | 2 +- .../editors/default/components/agg_params.tsx | 6 +- .../public/expression_renderer.tsx | 2 +- src/plugins/kibana_react/public/index.ts | 2 +- .../ui_settings/use_ui_setting.test.tsx | 4 +- .../public/ui_settings/use_ui_setting.ts | 2 +- src/plugins/kibana_react/public/util/index.ts | 3 - .../public/util/use_observable.test.tsx | 54 ------------ .../public/util/use_observable.ts | 34 -------- .../util/use_shallow_compare_effect.test.ts | 86 ------------------- .../public/util/use_shallow_compare_effect.ts | 80 ----------------- .../kibana_react/public/util/use_unmount.ts | 24 ------ .../public/context/LicenseContext/index.tsx | 2 +- .../public/utils/use_kibana_ui_setting.ts | 2 +- .../infra/public/utils/use_observable.ts | 21 ----- .../chart_tooltip/chart_tooltip.tsx | 5 +- yarn.lock | 20 ++++- 17 files changed, 27 insertions(+), 322 deletions(-) delete mode 100644 src/plugins/kibana_react/public/util/use_observable.test.tsx delete mode 100644 src/plugins/kibana_react/public/util/use_observable.ts delete mode 100644 src/plugins/kibana_react/public/util/use_shallow_compare_effect.test.ts delete mode 100644 src/plugins/kibana_react/public/util/use_shallow_compare_effect.ts delete mode 100644 src/plugins/kibana_react/public/util/use_unmount.ts delete mode 100644 x-pack/legacy/plugins/infra/public/utils/use_observable.ts diff --git a/package.json b/package.json index 651ffb60d7b88..a0f5dd3af14c0 100644 --- a/package.json +++ b/package.json @@ -234,7 +234,7 @@ "react-resize-detector": "^4.2.0", "react-router-dom": "^5.1.2", "react-sizeme": "^2.3.6", - "react-use": "^13.10.2", + "react-use": "^13.13.0", "reactcss": "1.2.3", "redux": "4.0.0", "redux-actions": "2.2.1", diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx index 4f4c0bda6520a..1515ed86db070 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx @@ -20,10 +20,11 @@ import React, { useReducer, useEffect, useMemo } from 'react'; import { EuiForm, EuiAccordion, EuiSpacer, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import useUnmount from 'react-use/lib/useUnmount'; import { VisState } from 'ui/vis'; -import { aggTypes, AggType, AggParam, AggConfig } from 'ui/agg_types/'; import { IndexPattern } from 'ui/index_patterns'; +import { aggTypes, AggType, AggParam, AggConfig } from 'ui/agg_types/'; import { DefaultEditorAggSelect } from './agg_select'; import { DefaultEditorAggParam } from './agg_param'; @@ -44,9 +45,6 @@ import { } from './agg_params_state'; import { editorConfigProviders } from '../../config/editor_config_providers'; import { FixedParam, TimeIntervalParam, EditorParamConfig } from '../../config/types'; -// TODO: Below import is temporary, use `react-use` lib instead. -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { useUnmount } from '../../../../../../../plugins/kibana_react/public/util/use_unmount'; import { AggGroupNames } from '../agg_groups'; import { OnAggParamsChange } from './agg_common_props'; diff --git a/src/plugins/expressions/public/expression_renderer.tsx b/src/plugins/expressions/public/expression_renderer.tsx index 3989f4ed7d698..5c04d8405479f 100644 --- a/src/plugins/expressions/public/expression_renderer.tsx +++ b/src/plugins/expressions/public/expression_renderer.tsx @@ -22,9 +22,9 @@ import React from 'react'; import classNames from 'classnames'; import { Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; +import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect'; import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { useShallowCompareEffect } from '../../kibana_react/public'; import { IExpressionLoaderParams, IInterpreterRenderHandlers, RenderError } from './types'; import { ExpressionAST } from '../common/types'; import { ExpressionLoader } from './loader'; diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 258b0e94ef955..10b7dd2b4da44 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -25,4 +25,4 @@ export * from './overlays'; export * from './ui_settings'; export * from './field_icon'; export * from './table_list_view'; -export { toMountPoint, useObservable, useShallowCompareEffect } from './util'; +export { toMountPoint } from './util'; diff --git a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx index 0879b0cb3f36a..db6d92a12841a 100644 --- a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx +++ b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx @@ -24,10 +24,10 @@ import { useUiSetting$ } from './use_ui_setting'; import { createKibanaReactContext } from '../context'; import { KibanaServices } from '../context/types'; import { Subject } from 'rxjs'; -import { useObservable } from '../util/use_observable'; import { coreMock } from '../../../../core/public/mocks'; +import useObservable from 'react-use/lib/useObservable'; -jest.mock('../util/use_observable'); +jest.mock('react-use/lib/useObservable'); const useObservableSpy = (useObservable as any) as jest.SpyInstance; useObservableSpy.mockImplementation((observable, def) => def); diff --git a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts index 295515bfa51af..a8bc01bb8d2c4 100644 --- a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts +++ b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts @@ -18,8 +18,8 @@ */ import { useCallback, useMemo } from 'react'; +import useObservable from 'react-use/lib/useObservable'; import { useKibana } from '../context'; -import { useObservable } from '../util/use_observable'; /** * Returns the current UI-settings value. diff --git a/src/plugins/kibana_react/public/util/index.ts b/src/plugins/kibana_react/public/util/index.ts index 4f64d6c9c81ab..71a281dbdaad3 100644 --- a/src/plugins/kibana_react/public/util/index.ts +++ b/src/plugins/kibana_react/public/util/index.ts @@ -17,7 +17,4 @@ * under the License. */ -export * from './use_observable'; -export * from './use_unmount'; export * from './react_mount'; -export * from './use_shallow_compare_effect'; diff --git a/src/plugins/kibana_react/public/util/use_observable.test.tsx b/src/plugins/kibana_react/public/util/use_observable.test.tsx deleted file mode 100644 index 2dfbea06ecbb3..0000000000000 --- a/src/plugins/kibana_react/public/util/use_observable.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { renderHook, act } from '@testing-library/react-hooks'; -import { Subject } from 'rxjs'; -import { useObservable } from './use_observable'; - -test('default initial value is undefined', () => { - const subject$ = new Subject(); - const { result } = renderHook(() => useObservable(subject$)); - - expect(result.current).toBe(undefined); -}); - -test('can specify initial value', () => { - const subject$ = new Subject(); - const { result } = renderHook(() => useObservable(subject$, 123)); - - expect(result.current).toBe(123); -}); - -test('returns the latest value of observables', () => { - const subject$ = new Subject(); - const { result } = renderHook(() => useObservable(subject$, 123)); - - act(() => { - subject$.next(125); - }); - expect(result.current).toBe(125); - - act(() => { - subject$.next(300); - subject$.next(400); - }); - expect(result.current).toBe(400); -}); - -xtest('subscribes to observable only once', () => {}); diff --git a/src/plugins/kibana_react/public/util/use_observable.ts b/src/plugins/kibana_react/public/util/use_observable.ts deleted file mode 100644 index 6a7ce1f5290d2..0000000000000 --- a/src/plugins/kibana_react/public/util/use_observable.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { useLayoutEffect, useState } from 'react'; -import { Observable } from 'rxjs'; - -export function useObservable(observable$: Observable): T | undefined; -export function useObservable(observable$: Observable, initialValue: T): T; -export function useObservable(observable$: Observable, initialValue?: T): T | undefined { - const [value, update] = useState(initialValue); - - useLayoutEffect(() => { - const s = observable$.subscribe(update); - return () => s.unsubscribe(); - }, [observable$]); - - return value; -} diff --git a/src/plugins/kibana_react/public/util/use_shallow_compare_effect.test.ts b/src/plugins/kibana_react/public/util/use_shallow_compare_effect.test.ts deleted file mode 100644 index 810c727fcdb0b..0000000000000 --- a/src/plugins/kibana_react/public/util/use_shallow_compare_effect.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { renderHook } from '@testing-library/react-hooks'; -import { useShallowCompareEffect } from './use_shallow_compare_effect'; - -describe('useShallowCompareEffect', () => { - test("doesn't run effect on shallow change", () => { - const callback = jest.fn(); - let deps = [1, { a: 'b' }, true]; - const { rerender } = renderHook(() => useShallowCompareEffect(callback, deps)); - - expect(callback).toHaveBeenCalledTimes(1); - callback.mockClear(); - - // no change - rerender(); - expect(callback).toHaveBeenCalledTimes(0); - callback.mockClear(); - - // no-change (new object with same properties) - deps = [1, { a: 'b' }, true]; - rerender(); - expect(callback).toHaveBeenCalledTimes(0); - callback.mockClear(); - - // change (new primitive value) - deps = [2, { a: 'b' }, true]; - rerender(); - expect(callback).toHaveBeenCalledTimes(1); - callback.mockClear(); - - // no-change - rerender(); - expect(callback).toHaveBeenCalledTimes(0); - callback.mockClear(); - - // change (new primitive value) - deps = [1, { a: 'b' }, false]; - rerender(); - expect(callback).toHaveBeenCalledTimes(1); - callback.mockClear(); - - // change (new properties on object) - deps = [1, { a: 'c' }, false]; - rerender(); - expect(callback).toHaveBeenCalledTimes(1); - callback.mockClear(); - }); - - test('runs effect on deep change', () => { - const callback = jest.fn(); - let deps = [1, { a: { b: 'c' } }, true]; - const { rerender } = renderHook(() => useShallowCompareEffect(callback, deps)); - - expect(callback).toHaveBeenCalledTimes(1); - callback.mockClear(); - - // no change - rerender(); - expect(callback).toHaveBeenCalledTimes(0); - callback.mockClear(); - - // change (new nested object ) - deps = [1, { a: { b: 'c' } }, true]; - rerender(); - expect(callback).toHaveBeenCalledTimes(1); - callback.mockClear(); - }); -}); diff --git a/src/plugins/kibana_react/public/util/use_shallow_compare_effect.ts b/src/plugins/kibana_react/public/util/use_shallow_compare_effect.ts deleted file mode 100644 index dfba7b907f5fb..0000000000000 --- a/src/plugins/kibana_react/public/util/use_shallow_compare_effect.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { useEffect, useRef } from 'react'; - -/** - * Similar to https://github.com/kentcdodds/use-deep-compare-effect - * but uses shallow compare instead of deep - */ -export function useShallowCompareEffect( - callback: React.EffectCallback, - deps: React.DependencyList -) { - useEffect(callback, useShallowCompareMemoize(deps)); -} -function useShallowCompareMemoize(deps: React.DependencyList) { - const ref = useRef(undefined); - - if (!ref.current || deps.some((dep, index) => !shallowEqual(dep, ref.current![index]))) { - ref.current = deps; - } - - return ref.current; -} -// https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js -function shallowEqual(objA: any, objB: any): boolean { - if (is(objA, objB)) { - return true; - } - - if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { - return false; - } - - const keysA = Object.keys(objA); - const keysB = Object.keys(objB); - - if (keysA.length !== keysB.length) { - return false; - } - - // Test for A's keys different from B. - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i]) || - !is(objA[keysA[i]], objB[keysA[i]]) - ) { - return false; - } - } - - return true; -} - -/** - * IE11 does not support Object.is - */ -function is(x: any, y: any): boolean { - if (x === y) { - return x !== 0 || y !== 0 || 1 / x === 1 / y; - } else { - return x !== x && y !== y; - } -} diff --git a/src/plugins/kibana_react/public/util/use_unmount.ts b/src/plugins/kibana_react/public/util/use_unmount.ts deleted file mode 100644 index 009bf8c4caa1c..0000000000000 --- a/src/plugins/kibana_react/public/util/use_unmount.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { useEffect } from 'react'; - -export function useUnmount(fn: () => void): void { - useEffect(() => fn, []); -} diff --git a/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx b/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx index 714e4c8985678..8cdb7f050027d 100644 --- a/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx +++ b/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { useObservable } from '../../../../../../../src/plugins/kibana_react/public'; +import useObservable from 'react-use/lib/useObservable'; import { ILicense } from '../../../../../../plugins/licensing/public'; import { useApmPluginContext } from '../../hooks/useApmPluginContext'; import { InvalidLicenseNotification } from './InvalidLicenseNotification'; diff --git a/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts b/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts index 1b08fb4231243..ce39a31c0fc3f 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts @@ -7,7 +7,7 @@ import { useCallback, useMemo } from 'react'; import { npSetup } from 'ui/new_platform'; -import { useObservable } from './use_observable'; +import useObservable from 'react-use/lib/useObservable'; /** * This hook behaves like a `useState` hook in that it provides a requested diff --git a/x-pack/legacy/plugins/infra/public/utils/use_observable.ts b/x-pack/legacy/plugins/infra/public/utils/use_observable.ts deleted file mode 100644 index 536b6b2723ae0..0000000000000 --- a/x-pack/legacy/plugins/infra/public/utils/use_observable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useEffect, useState } from 'react'; -import { Observable } from 'rxjs'; - -export function useObservable(observable$: Observable): T | undefined; -export function useObservable(observable$: Observable, initialValue: T): T; -export function useObservable(observable$: Observable, initialValue?: T): T | undefined { - const [value, update] = useState(initialValue); - - useEffect(() => { - const s = observable$.subscribe(update); - return () => s.unsubscribe(); - }, [observable$]); - - return value; -} diff --git a/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx b/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx index 42a3e97509452..aa28831e8d807 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx @@ -7,10 +7,7 @@ import classNames from 'classnames'; import React, { useRef, FC } from 'react'; import { TooltipValueFormatter } from '@elastic/charts'; - -// TODO: Below import is temporary, use `react-use` lib instead. -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { useObservable } from '../../../../../../../../src/plugins/kibana_react/public/util/use_observable'; +import useObservable from 'react-use/lib/useObservable'; import { chartTooltip$, ChartTooltipValue } from './chart_tooltip_service'; diff --git a/yarn.lock b/yarn.lock index 7528db731e587..6b096306b78d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4695,6 +4695,11 @@ dependencies: tslib "^1.9.3" +"@xobotyi/scrollbar-width@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.5.0.tgz#488210bff634548040dc22a72f62722a85b134e1" + integrity sha512-BK+HR1D00F2xh7n4+5en8/dMkG13uvIXLmEbsjtc1702b7+VwXkvlBDKoRPJMbkRN5hD7VqWa3nS9fNT8JG3CA== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -12458,6 +12463,11 @@ fast-safe-stringify@^2.0.7: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fast-shallow-equal@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-0.1.1.tgz#44d01324d7fd31e00a67bb02b9396e283d526c22" + integrity sha512-XVP6nhaXLYOH6JZCWBcNaeEer9GJ5/8cJWUP+OLmgwWgEkJp5Kpl/fdpJ01zl0mpLxrk7f5J3hIv+GmjTCi7Mg== + fast-stream-to-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-stream-to-buffer/-/fast-stream-to-buffer-1.0.0.tgz#793340cc753e7ec9c7fb6d57a53a0b911cb0f588" @@ -23779,12 +23789,14 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react-use@^13.10.2: - version "13.10.2" - resolved "https://registry.yarnpkg.com/react-use/-/react-use-13.10.2.tgz#4250d258ca9068662943299c01794a136408c8e9" - integrity sha512-z3VFSiPHW6arViGVnajO7YKY5OD+Z9LWcImoJdYHkau23cLSoTctxM3XENLpGxjhJlHaYiQZ6pPgq7pwGTqSZA== +react-use@^13.13.0: + version "13.13.0" + resolved "https://registry.yarnpkg.com/react-use/-/react-use-13.13.0.tgz#5d133c4d4d8d3f21f6ccf4ccbe54fbcd6fdafb36" + integrity sha512-J3/h5wvL6vXmecAvEnninCC3DviLMRWcQrEnouTliwws1b376DQKEgIFuTXlF8c3SKpXBQJdDDm1RpluokW6ag== dependencies: + "@xobotyi/scrollbar-width" "1.5.0" copy-to-clipboard "^3.2.0" + fast-shallow-equal "^0.1.1" nano-css "^5.2.1" react-fast-compare "^2.0.4" resize-observer-polyfill "^1.5.1"