diff --git a/.github/workflows/pr-bundlesize-compare.yml b/.github/workflows/pr-bundlesize-compare.yml index 576bfefc8996..c9fef0021a98 100644 --- a/.github/workflows/pr-bundlesize-compare.yml +++ b/.github/workflows/pr-bundlesize-compare.yml @@ -35,7 +35,7 @@ jobs: - name: Build run: yarn build:client env: - ANALYZE_BUNDLE: true + ANALYZE_BUNDLE_PR: true - name: Upload stats.json uses: actions/upload-artifact@v4 @@ -75,7 +75,7 @@ jobs: - name: Build run: yarn build:client env: - ANALYZE_BUNDLE: true + ANALYZE_BUNDLE_PR: true - name: Upload stats.json uses: actions/upload-artifact@v4 diff --git a/client/config/webpack.config.js b/client/config/webpack.config.js index f839f3762044..92906a67a3b3 100644 --- a/client/config/webpack.config.js +++ b/client/config/webpack.config.js @@ -22,6 +22,10 @@ import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; // Source maps are resource heavy and can cause out of memory issue for large source files. const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false"; +// to compare file sizes in PRs we need them to not include hashes +const analyzeBundlePR = process.env.ANALYZE_BUNDLE_PR; +const analyzeBundle = analyzeBundlePR || process.env.ANALYZE_BUNDLE; + // This is the production and development configuration. // It is focused on developer experience, fast rebuilds, and a minimal bundle. function config(webpackEnv) { @@ -119,15 +123,20 @@ function config(webpackEnv) { // Add /* filename */ comments to generated require()s in the output. pathinfo: isEnvDevelopment, // There will be one main bundle, and one file per asynchronous chunk. - // In development, it does not produce real files. - filename: isEnvProduction - ? "static/js/[name].[contenthash:8].js" - : isEnvDevelopment && "static/js/bundle.js", + filename: analyzeBundlePR + ? "static/js/[name].js" + : isEnvProduction + ? "static/js/[name].[contenthash:8].js" + : isEnvDevelopment && "static/js/bundle.js", // There are also additional JS chunk files if you use code splitting. - chunkFilename: isEnvProduction - ? "static/js/[name].[contenthash:8].chunk.js" - : isEnvDevelopment && "static/js/[name].chunk.js", - assetModuleFilename: "static/media/[name].[hash][ext]", + chunkFilename: analyzeBundlePR + ? "static/js/[name].chunk.js" + : isEnvProduction + ? "static/js/[name].[contenthash:8].chunk.js" + : isEnvDevelopment && "static/js/[name].chunk.js", + assetModuleFilename: analyzeBundlePR + ? "static/media/[file]" + : "static/media/[name].[hash][ext]", publicPath: "/", // Point sourcemap entries to original disk location (format as URL on Windows) devtoolModuleFilenameTemplate: isEnvProduction @@ -252,7 +261,9 @@ function config(webpackEnv) { { loader: resolve.sync("file-loader"), options: { - name: "static/media/[name].[hash].[ext]", + name: analyzeBundlePR + ? "static/media/[path][name].[ext]" + : "static/media/[name].[hash].[ext]", }, }, ], @@ -429,8 +440,12 @@ function config(webpackEnv) { new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional - filename: "static/css/[name].[contenthash:8].css", - chunkFilename: "static/css/[name].[contenthash:8].chunk.css", + filename: analyzeBundlePR + ? "static/css/[name].css" + : "static/css/[name].[contenthash:8].css", + chunkFilename: analyzeBundlePR + ? "static/css/[name].chunk.css" + : "static/css/[name].[contenthash:8].chunk.css", }), // Generate an asset manifest file with the following content: // - "files" key: Mapping of all asset filenames to their corresponding @@ -499,7 +514,7 @@ function config(webpackEnv) { extensions: ["js", "mjs", "jsx", "ts", "tsx"], }), isEnvProduction && - process.env.ANALYZE_BUNDLE && + analyzeBundle && new BundleAnalyzerPlugin({ analyzerMode: "disabled", generateStatsFile: true, diff --git a/client/src/app.tsx b/client/src/app.tsx index a64b26a9e056..4cfed0e7ff22 100644 --- a/client/src/app.tsx +++ b/client/src/app.tsx @@ -37,12 +37,24 @@ import { Newsletter } from "./newsletter"; import { Curriculum } from "./curriculum"; import { useGA } from "./ga-context"; -const AllFlaws = React.lazy(() => import("./flaws")); -const Translations = React.lazy(() => import("./translations")); -const WritersHomepage = React.lazy(() => import("./writers-homepage")); -const Sitemap = React.lazy(() => import("./sitemap")); -const Playground = React.lazy(() => import("./playground")); -const Observatory = React.lazy(() => import("./observatory")); +const AllFlaws = React.lazy( + () => import(/* webpackChunkName: "flaws" */ "./flaws") +); +const Translations = React.lazy( + () => import(/* webpackChunkName: "translations" */ "./translations") +); +const WritersHomepage = React.lazy( + () => import(/* webpackChunkName: "writers-homepage" */ "./writers-homepage") +); +const Sitemap = React.lazy( + () => import(/* webpackChunkName: "sitemap" */ "./sitemap") +); +const Playground = React.lazy( + () => import(/* webpackChunkName: "playground" */ "./playground") +); +const Observatory = React.lazy( + () => import(/* webpackChunkName: "observatory" */ "./observatory") +); function Layout({ pageType, children }) { const { pathname } = useLocation(); diff --git a/client/src/document/code/ai-explain.ts b/client/src/document/code/ai-explain.ts index 658d8eff048e..5d6f76caf409 100644 --- a/client/src/document/code/ai-explain.ts +++ b/client/src/document/code/ai-explain.ts @@ -160,7 +160,9 @@ export function addExplainButton( answers.set(highlighted, container); - const { render } = await import("./render-md"); + const { render } = await import( + /* webpackChunkName: "ai-explain-md" */ "./render-md" + ); explain( { diff --git a/client/src/document/index.tsx b/client/src/document/index.tsx index f67715bfef11..1fd5d8d5421f 100644 --- a/client/src/document/index.tsx +++ b/client/src/document/index.tsx @@ -46,8 +46,13 @@ import { CLIENT_SIDE_NAVIGATION } from "../telemetry/constants"; // import { useUIStatus } from "../ui-context"; // Lazy sub-components -const Toolbar = React.lazy(() => import("./toolbar")); -const MathMLPolyfillMaybe = React.lazy(() => import("./mathml-polyfill")); +const Toolbar = React.lazy( + () => import(/* webpackChunkName: "toolbar" */ "./toolbar") +); +const MathMLPolyfillMaybe = React.lazy( + () => + import(/* webpackChunkName: "mathml-polyfill-maybe" */ "./mathml-polyfill") +); export class HTTPError extends Error { public readonly status: number; diff --git a/client/src/document/mathml-polyfill/index.tsx b/client/src/document/mathml-polyfill/index.tsx index efdb16eb155d..66cd0450030d 100644 --- a/client/src/document/mathml-polyfill/index.tsx +++ b/client/src/document/mathml-polyfill/index.tsx @@ -2,7 +2,9 @@ import React from "react"; import "./mathml-font.scss"; -const MathMLPolyfill = React.lazy(() => import("./polyfill")); +const MathMLPolyfill = React.lazy( + () => import(/* webpackChunkName: "mathml-polyfill" */ "./polyfill") +); // This component gets rendered if the document has MathML in it. // But that doesn't mean we necessarily need the CSS polyfill. diff --git a/client/src/index.tsx b/client/src/index.tsx index b9cd51c2df86..3405be061cae 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -52,5 +52,7 @@ if (container.firstElementChild) { // Initialize mdnWorker if there's a service worker already. if (navigator?.serviceWorker?.controller && !window.mdnWorker) { - import("./settings/mdn-worker").then(({ getMDNWorker }) => getMDNWorker()); + import(/* webpackChunkName: "mdn-worker" */ "./settings/mdn-worker").then( + ({ getMDNWorker }) => getMDNWorker() + ); } diff --git a/client/src/page-not-found/index.tsx b/client/src/page-not-found/index.tsx index 46011ea4d253..d643978270a4 100644 --- a/client/src/page-not-found/index.tsx +++ b/client/src/page-not-found/index.tsx @@ -4,7 +4,9 @@ import { useLocation } from "react-router-dom"; import { MainContentContainer } from "../ui/atoms/page-content"; import "./index.scss"; -const FallbackLink = React.lazy(() => import("./fallback-link")); +const FallbackLink = React.lazy( + () => import(/* webpackChunkName: "fallback-link" */ "./fallback-link") +); // NOTE! To hack on this component, you have to use a trick to even get to this // unless you use the Express server on localhost:5042. diff --git a/client/src/plus/index.tsx b/client/src/plus/index.tsx index 57b9dfcbf9cf..72988d733c54 100644 --- a/client/src/plus/index.tsx +++ b/client/src/plus/index.tsx @@ -14,9 +14,15 @@ import { DocParent } from "../../../libs/types/document"; import "./index.scss"; import OfferOverview from "./offer-overview"; -const AiHelp = React.lazy(() => import("./ai-help")); -const Collections = React.lazy(() => import("./collections")); -const Updates = React.lazy(() => import("./updates")); +const AiHelp = React.lazy( + () => import(/* webpackChunkName: "ai-help" */ "./ai-help") +); +const Collections = React.lazy( + () => import(/* webpackChunkName: "collections" */ "./collections") +); +const Updates = React.lazy( + () => import(/* webpackChunkName: "updates" */ "./updates") +); interface LayoutProps { withoutContainer?: boolean; diff --git a/client/src/plus/offer-overview/offer-overview-subscribe/index.tsx b/client/src/plus/offer-overview/offer-overview-subscribe/index.tsx index d391d1f4af02..02ec5cfb6660 100644 --- a/client/src/plus/offer-overview/offer-overview-subscribe/index.tsx +++ b/client/src/plus/offer-overview/offer-overview-subscribe/index.tsx @@ -17,7 +17,9 @@ import { OFFER_OVERVIEW_CLICK } from "../../../telemetry/constants"; import LogInLink from "../../../ui/atoms/login-link"; import React from "react"; -const Stripe = React.lazy(() => import("./stripe")); +const Stripe = React.lazy( + () => import(/* webpackChunkName: "stripe" */ "./stripe") +); export enum Period { Month, diff --git a/client/src/settings/index.tsx b/client/src/settings/index.tsx index dfe55eb52082..69e82654fadb 100644 --- a/client/src/settings/index.tsx +++ b/client/src/settings/index.tsx @@ -9,7 +9,9 @@ import Newsletter from "./newsletter"; import { ManageAIHelp } from "./ai-help"; import { useScrollToAnchor } from "../hooks"; -const OfflineSettings = React.lazy(() => import("./offline-settings")); +const OfflineSettings = React.lazy( + () => import(/* webpackChunkName: "offline-settings" */ "./offline-settings") +); export function Settings() { const pageTitle = "Settings"; diff --git a/client/src/site-search/index.tsx b/client/src/site-search/index.tsx index 3d32b29653d9..43f13601f276 100644 --- a/client/src/site-search/index.tsx +++ b/client/src/site-search/index.tsx @@ -9,8 +9,12 @@ import "./index.scss"; import { SidePlacement } from "../ui/organisms/placement"; import { CLIENT_SIDE_NAVIGATION } from "../telemetry/constants"; -const SiteSearchForm = React.lazy(() => import("./form")); -const SearchResults = React.lazy(() => import("./search-results")); +const SiteSearchForm = React.lazy( + () => import(/* webpackChunkName: "search-form" */ "./form") +); +const SearchResults = React.lazy( + () => import(/* webpackChunkName: "search-results" */ "./search-results") +); export function SiteSearch() { const isServer = useIsServer(); diff --git a/client/src/user-context.tsx b/client/src/user-context.tsx index d59b371995c2..59768d55b7d3 100644 --- a/client/src/user-context.tsx +++ b/client/src/user-context.tsx @@ -248,7 +248,9 @@ export function UserDataProvider(props: { children: React.ReactNode }) { // Let's initialize the MDN Worker if applicable. if (!window.mdnWorker && data?.offlineSettings?.offline) { - import("./settings/mdn-worker").then(({ getMDNWorker }) => { + import( + /* webpackChunkName: "mdn-worker" */ "./settings/mdn-worker" + ).then(({ getMDNWorker }) => { const mdnWorker = getMDNWorker(); if (data?.isSubscriber === false) { mdnWorker.clearOfflineSettings(); diff --git a/client/src/writers-homepage/index.tsx b/client/src/writers-homepage/index.tsx index 9ee57cc5232b..9a39a76d6e36 100644 --- a/client/src/writers-homepage/index.tsx +++ b/client/src/writers-homepage/index.tsx @@ -6,7 +6,9 @@ import { useIsServer, useLocale } from "../hooks"; import "./index.scss"; // Lazy sub-components -const ViewedDocuments = React.lazy(() => import("./viewed-documents")); +const ViewedDocuments = React.lazy( + () => import(/* webpackChunkName: "viewed-documents" */ "./viewed-documents") +); export default function WritersHomepage() { const isServer = useIsServer();