Skip to content

Commit

Permalink
[ml] Optimize bundles, reduce page load async chunks by 74% (#179311)
Browse files Browse the repository at this point in the history
## Summary

The `AnomalyExplorerChartsService` was importing the
`SWIM_LANE_LABEL_WIDTH` numerical constant from
`swimlane_container.tsx`. As a result, the _entirety_ of that file was
being included in the async bundle.

`AnomalyExplorerChartsService` is loaded asynchronously on page load by
the embeddable. By relocating the constant to its own file-- as well as
other optimizations (see below)-- we reduce the async page load by 74%,
(in dev mode).

### Before - `351k`
<img width="1220" alt="Screenshot 2024-03-24 at 10 09 31 AM"
src="https://github.com/elastic/kibana/assets/297604/6cea41f5-fb10-41c3-8951-a4be897174f9">

### After - `93.4k`
<img width="1471" alt="Screenshot 2024-04-05 at 11 45 45 AM"
src="https://github.com/elastic/kibana/assets/297604/a07e7a19-c1af-4b45-a195-69730fc61b0c">

Unfortunately, this change led to a bloating of async modules, the cause
of which is still unclear. The application async chunk weighed in at 2.2
MB compressed! To get this PR to a shippable state, I refactored
additional code to bring down duplication and bloat.

The result is an `ml` experience that fetches small bundles on demand as
someone interacts with it:

![Apr-05-2024
11-51-18](https://github.com/elastic/kibana/assets/297604/75ba423e-7071-4867-ae4b-05308bf319f3)

More work can be done to continue to optimize the plugin, of course, but
this set of changes is an excellent start, (and case study on bundle
load).

### Other optimizations in this PR

- Registration of some `start` services are conditional, and contain
their own async calls. I've removed these from the register helper,
(which itself is a brute-force offload of code from the plugin, but is
still loaded every time), and loaded them async if the conditions apply.
- Routing in `ml` use factories to create individual routes. In a lot of
cases, the pages these routes displayed were not asynchronously loaded,
adding tremendous amounts of React code to the root application.
  - I made all pages loaded by routes async.
- In some cases, the components themselves were colocated with the route
factory. I moved those to their own files for async import.
- Similarly, some state managers were colocated. Those were moved to
their own files, as well.
- Moved flyouts, modals, expanding rows to `React.lazy` async modules,
(using `dynamic` from `@kbn/shared-ux-utility`.
- Refactored `export * from` directives from `public/shared.ts` to
accurately reflect what is being consumed outside the `ml` plugin, (and
also reduce the size of _that_ bundle.
- Refactored `lodash` imports to submodule imports to enable
tree-shaking, (if ever enabled in webpack).
- Moved `getMlGlobalServices` off of the `app.tsx` file for import by
others, (to avoid depending on the rest of the code there).
- Moved some types to `/common` to avoid importing code, (though,
admittedly, types are compiled away). But it felt cleaner to move them
out.

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
clintandrewhall and kibanamachine authored Apr 11, 2024
1 parent 7c6f4fe commit 67ab4f7
Show file tree
Hide file tree
Showing 74 changed files with 962 additions and 763 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import get from 'lodash/get';
import { get } from 'lodash';

import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';

Expand Down
2 changes: 1 addition & 1 deletion x-pack/packages/ml/response_stream/client/fetch_stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import startsWith from 'lodash/startsWith';
import { startsWith } from 'lodash';
import type { Reducer, ReducerAction } from 'react';

import type { HttpSetup, HttpFetchOptions } from '@kbn/core/public';
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/ml/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { getDefaultCapabilities as getDefaultMlCapabilities } from './types/capa
export { DATAFEED_STATE, JOB_STATE } from './constants/states';
export type { MlSummaryJob, SummaryJobState } from './types/anomaly_detection_jobs';
export { ML_ALERT_TYPES } from './constants/alerts';
export type { Job, Datafeed } from './types/anomaly_detection_jobs';
44 changes: 2 additions & 42 deletions x-pack/plugins/ml/public/application/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import './_index.scss';
import ReactDOM from 'react-dom';
import { pick } from 'lodash';

import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import type { AppMountParameters, CoreStart, HttpStart } from '@kbn/core/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
Expand All @@ -21,21 +19,15 @@ import { StorageContextProvider } from '@kbn/ml-local-storage';
import useLifecycles from 'react-use/lib/useLifecycles';
import useObservable from 'react-use/lib/useObservable';
import type { ExperimentalFeatures, MlFeatures } from '../../common/constants/app';
import { MlLicense } from '../../common/license';
import { MlCapabilitiesService } from './capabilities/check_capabilities';
import { ML_STORAGE_KEYS } from '../../common/types/storage';
import type { MlSetupDependencies, MlStartDependencies } from '../plugin';
import { clearCache, setDependencyCache } from './util/dependency_cache';
import { setLicenseCache } from './license';
import { mlUsageCollectionProvider } from './services/usage_collection';
import { MlRouter } from './routing';
import { mlApiServicesProvider } from './services/ml_api_service';
import { HttpService } from './services/http_service';
import type { PageDependencies } from './routing/router';
import { EnabledFeaturesContextProvider } from './contexts/ml';
import type { StartServices } from './contexts/kibana';
import { fieldFormatServiceFactory } from './services/field_format_service_factory';
import { indexServiceFactory } from './util/index_service';
import { getMlGlobalServices } from './util/get_services';

export type MlDependencies = Omit<
MlSetupDependencies,
Expand All @@ -54,38 +46,6 @@ interface AppProps {

const localStorage = new Storage(window.localStorage);

/**
* Provides global services available across the entire ML app.
*/
export function getMlGlobalServices(
httpStart: HttpStart,
dataViews: DataViewsContract,
usageCollection?: UsageCollectionSetup
) {
const httpService = new HttpService(httpStart);
const mlApiServices = mlApiServicesProvider(httpService);
// Note on the following services:
// - `mlIndexUtils` is just instantiated here to be passed on to `mlFieldFormatService`,
// but it's not being made available as part of global services. Since it's just
// some stateless utils `useMlIndexUtils()` should be used from within components.
// - `mlFieldFormatService` is a stateful legacy service that relied on "dependency cache",
// so because of its own state it needs to be made available as a global service.
// In the long run we should again try to get rid of it here and make it available via
// its own context or possibly without having a singleton like state at all, since the
// way this manages its own state right now doesn't consider React component lifecycles.
const mlIndexUtils = indexServiceFactory(dataViews);
const mlFieldFormatService = fieldFormatServiceFactory(mlApiServices, mlIndexUtils);

return {
httpService,
mlApiServices,
mlFieldFormatService,
mlUsageCollection: mlUsageCollectionProvider(usageCollection),
mlCapabilities: new MlCapabilitiesService(mlApiServices),
mlLicense: new MlLicense(),
};
}

export interface MlServicesContext {
mlServices: MlGlobalServices;
}
Expand Down
Loading

0 comments on commit 67ab4f7

Please sign in to comment.