Browse Source

build(webpack): Change webpack to have smaller chunks and make all entries async (#25566)

This changes our entrypoints to dynamically import the main bundles (i.e. the previous entrypoints). The goal here is to make the entrypoints as minimal as possible as we may need these to be uncached for splitting off frontend deploys.

The other change is removing the vendor chunk that we had, and instead use webpack defaults and let it decide how to chunk our build. This results in the client needing to request more + smaller chunks (instead of fewer + larger chunks). This costs a bit of network overhead, but a trade-off here is that the smaller chunks should have longer cache lives.

I'd like to push this out and evaluate how it affects our web vitals in prod and then make a decision on how we'd like to proceed.

Here's a light benchmarking of the perf changes, I used Chrome's dev tools to throttle a mix of the CPU and network bandwidth:

(tbh `DOMContentLoaded` doesn't seem like a very practical measurement here, but it's what was readily available in CDT)
![image](https://user-images.githubusercontent.com/79684/116126971-1e556300-a67c-11eb-948e-69e07ac3078b.png)

(private spreadsheet: https://docs.google.com/spreadsheets/d/1EN2jD0fqAJCASaAL1hAEBZJScTutmCZgCvDooamB3u4/)
Billy Vong 3 years ago
parent
commit
b4fef9f019

+ 0 - 1
src/sentry/templates/sentry/bases/react.html

@@ -1,2 +1 @@
 {% extends "sentry/base-react.html" %}
-{% load sentry_assets %}

+ 0 - 3
src/sentry/templates/sentry/layout.html

@@ -49,9 +49,6 @@
   {% manifest_asset_url "sentry" "runtime.js" as asset_url %}
   {% script src=asset_url %}{% endscript %}
 
-  {% manifest_asset_url "sentry" "vendor.js" as asset_url %}
-  {% script src=asset_url %}{% endscript %}
-
   {% block scripts_main_entrypoint %}
     {% manifest_asset_url "sentry" "app.js" as asset_url %}
     {% script src=asset_url data-entry="true" %}{% endscript %}

+ 31 - 0
static/app/bootstrap/index.tsx

@@ -0,0 +1,31 @@
+import {Config} from 'app/types';
+
+const BOOTSTRAP_URL = '/api/client-config/';
+
+const bootApplication = (data: Config) => {
+  window.csrfCookieName = data.csrfCookieName;
+
+  return data;
+};
+
+async function bootWithHydration() {
+  const response = await fetch(BOOTSTRAP_URL);
+  const data: Config = await response.json();
+
+  window.__initialData = data;
+
+  return bootApplication(data);
+}
+
+export async function bootstrap() {
+  const bootstrapData = window.__initialData;
+
+  // If __initialData is not already set on the window, we are likely running in
+  // pure SPA mode, meaning django is not serving our frontend application and we
+  // need to make an API request to hydrate the bootstrap data to boot the app.
+  if (bootstrapData === undefined) {
+    return await bootWithHydration();
+  }
+
+  return bootApplication(bootstrapData);
+}

+ 9 - 28
static/app/index.tsx

@@ -1,31 +1,12 @@
-import {Config} from 'app/types';
-
-const BOOTSTRAP_URL = '/api/client-config/';
-
-const bootApplication = (data: Config) => {
-  window.csrfCookieName = data.csrfCookieName;
-
-  // Once data hydration is done we can initialize the app
-  const {initializeMain} = require('./bootstrap/initializeMain');
+async function app() {
+  const [{bootstrap}, {initializeMain}] = await Promise.all([
+    import(/* webpackChunkName: "appBootstrap" */ 'app/bootstrap'),
+    import(
+      /* webpackChunkName: "appBootstrapInitializeMain" */ 'app/bootstrap/initializeMain'
+    ),
+  ]);
+  const data = await bootstrap();
   initializeMain(data);
-};
-
-async function bootWithHydration() {
-  const response = await fetch(BOOTSTRAP_URL);
-  const data: Config = await response.json();
-
-  window.__initialData = data;
-
-  bootApplication(data);
 }
 
-const bootstrapData = window.__initialData;
-
-// If __initialData is not already set on the window, we are likely running in
-// pure SPA mode, meaning django is not serving our frontend application and we
-// need to make an API request to hydrate the bootstrap data to boot the app.
-if (bootstrapData === undefined) {
-  bootWithHydration();
-} else {
-  bootApplication(bootstrapData);
-}
+app();

+ 4 - 4
static/app/views/integrationPipeline/index.tsx

@@ -1,5 +1,5 @@
-import 'focus-visible';
+async function integrationPipeline() {
+  return await import(/* webpackChunkName: "integrationPipelineInit" */ './init');
+}
 
-import {initializePipelineView} from 'app/bootstrap/initializePipelineView';
-
-initializePipelineView(window.__initialData);
+integrationPipeline();

+ 5 - 0
static/app/views/integrationPipeline/init.tsx

@@ -0,0 +1,5 @@
+import 'focus-visible';
+
+import {initializePipelineView} from 'app/bootstrap/initializePipelineView';
+
+initializePipelineView(window.__initialData);

+ 6 - 23
webpack.config.js

@@ -170,7 +170,7 @@ supportedLocales
       );
 
     localeChunkGroups[group] = {
-      chunks: 'initial',
+      chunks: 'async',
       name: group,
       test: groupTest,
       enforce: true,
@@ -195,25 +195,6 @@ const localeRestrictionPlugins = [
   ),
 ];
 
-/**
- * Explicit codesplitting cache groups
- */
-const cacheGroups = {
-  defaultVendors: {
-    name: 'vendor',
-    // This `platformicons` check is required otherwise it will get put into this chunk instead
-    // of `sentry.css` bundle
-    // TODO(platformicons): Simplify this if we move platformicons into repo
-    test: module =>
-      !/platformicons/.test(module.resource) &&
-      /[\\/]node_modules[\\/]/.test(module.resource),
-    priority: -10,
-    enforce: true,
-    chunks: 'initial',
-  },
-  ...localeChunkGroups,
-};
-
 const babelOptions = {...babelConfig, cacheDirectory: true};
 const babelLoaderConfig = {
   loader: 'babel-loader',
@@ -413,9 +394,11 @@ let appConfig = {
       // Which means the app will not load because we'd need these additional chunks to be loaded in our
       // django template.
       chunks: 'async',
-      maxInitialRequests: 5,
-      maxAsyncRequests: 7,
-      cacheGroups,
+      maxInitialRequests: 10, // (default: 30)
+      maxAsyncRequests: 10, // (default: 30)
+      cacheGroups: {
+        ...localeChunkGroups,
+      },
     },
 
     // This only runs in production mode