diff --git a/.gitignore b/.gitignore index 7222583..cfe43d2 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ yarn-error.log* # Sourcemaps from pre-built utility bundles (issue #57): don't recommit them static/utility-apps/**/*.map + +# Generated by ANALYZE=1 builds +bundle-report.html diff --git a/dev-plans/bundle-audit-2026-07.md b/dev-plans/bundle-audit-2026-07.md new file mode 100644 index 0000000..c26b2c7 --- /dev/null +++ b/dev-plans/bundle-audit-2026-07.md @@ -0,0 +1,60 @@ +# Bundle Audit — July 2026 (issue #99) + +Run `ANALYZE=1 npm run build` to regenerate the interactive report at +`build/bundle-report.html` (webpack-bundle-analyzer, client bundle only). + +## Headline result + +The micro-app migrations (#93/#94/#95) already achieved the audit's main goal: +**no WASM, 3D, or document library ships on the critical path.** Every page +loads the same two files first: + +| File | Parsed size | Contents | +|---|---|---| +| `main..js` | 633 KB | react-dom (174 KB), prism-react-renderer + prismjs (109 KB), Docusaurus theme/runtime (~140 KB), lunr + language stemmers (71 KB, offline search), mark.js (17 KB), site code incl. the `en` dictionary (33 KB) | +| `runtime~main..js` | 9 KB | webpack runtime | + +Everything in `main` is framework or deliberately-global functionality +(search, syntax highlighting, theme). There is no obvious low-effort cut left: +the next meaningful step would be swapping search or highlighting strategies, +which trades UX for bytes. + +## Heavy libraries — all verified lazy + +| Chunk | Size | Contents | Loaded on | +|---|---|---|---| +| `7084.*` | 3.0 MB | rapier physics (2.2 MB) + three.js (698 KB) + react-three-fiber | Eco Sort game page only | +| `5741.*` | 743 KB | @mdx-js/mdx compiler + acorn + framer-motion | MDX post editor only | +| `3512.*` | 391 KB | jspdf | PDF-export tools only | +| `5527.*` | 262 KB | emoji-picker-react | comment editor interaction | +| locale chunks | ~15 KB each | `ru/ua/de/es/et` dictionaries | on language switch (only `en` is in `main`) | + +Supabase is dynamically imported by the lazy client facade +(`src/lib/supabaseClient.ts`) and does not appear in `main`. + +## Changes made in this audit + +1. **Removed dead dependencies `replicad` and `replicad-opencascadejs`** — + zero imports remained in `src/` after the CAD tools moved to standalone + apps. Shrinks installs/CI by ~15 MB (the OpenCascade WASM binary alone is + ~11 MB in node_modules); runtime bundles were never affected because + webpack only bundles imported modules. +2. **Removed the replicad WASM webpack config** (`asyncWebAssembly`, + `__filename` mocks, `fs/path/crypto` fallbacks) — nothing in the site + bundle imports WASM anymore. Verified by a clean production build. +3. **Committed the analyzer tooling** (`ANALYZE=1` flag in the webpack + plugin) so future audits are a one-liner. + +## Acceptance criteria mapping + +- *Bundle analysis report committed or documented* — this file + reproducible + `ANALYZE=1` build. +- *No WASM/3D library loaded on the landing or docs pages* — verified: the + only render-path JS is `main` + `runtime` (table above); three/rapier live + exclusively in the Eco Sort chunk. +- *Measurable reduction in first-load JS for non-tool pages* — the big + reduction was delivered by the migrations this audit verified (the last + in-bundle CAD tool previously pulled replicad/OpenCascade context into + shared chunks); this pass removes the leftover dependency and config debt + and establishes the measurement baseline (642 KB total first-load) to hold + the line against regressions. diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 9ab2924..5a88aca 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -4,6 +4,7 @@ dotenv.config({path: './.env.local'}); import {themes as prismThemes} from 'prism-react-renderer'; import type {Config} from '@docusaurus/types'; import type * as Preset from '@docusaurus/preset-classic'; +import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'; const config: Config = { title: 'CAD AutoScript', @@ -49,29 +50,26 @@ const config: Config = { }, clientModules: ['./src/clientModules/errorReporting.ts'], plugins: [ - async function myPlugin(_context, _options) { + async function siteWebpackPlugin(_context, _options) { return { - name: 'docusaurus-replicad-config', - configureWebpack(_config, _isServer) { + name: 'site-webpack-config', + configureWebpack(_config, isServer) { + // ANALYZE=1 npm run build → writes build/bundle-report.html (client bundle). + // The replicad/OpenCascade WASM webpack config that used to live here + // was removed with the last in-bundle CAD tool (issues #93/#94/#99): + // every WASM consumer now runs as a standalone utility app. + const analyzerPlugins = + process.env.ANALYZE && !isServer + ? [ + new BundleAnalyzerPlugin({ + analyzerMode: 'static', + reportFilename: '../bundle-report.html', + openAnalyzer: false, + }), + ] + : []; return { - // replicad-opencascadejs requires async-WebAssembly streaming. - experiments: { - asyncWebAssembly: true, - }, - // Vendor wasm bindings reference __filename / __dirname. webpack 5 - // defaults to 'warn-mock', which mocks them AND emits a warning per - // locale build. 'mock' keeps the same runtime behaviour silently. - node: { - __filename: 'mock', - __dirname: 'mock', - }, - resolve: { - fallback: { - crypto: false, - fs: false, - path: false, - }, - }, + plugins: analyzerPlugins, }; }, }; diff --git a/package.json b/package.json index 0fdf9a8..1c57471 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,6 @@ "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", "remark-mdx-frontmatter": "^5.2.0", - "replicad": "^0.20.5", - "replicad-opencascadejs": "^0.20.2", "three": "^0.182.0", "three-stdlib": "2.36.1", "uuid": "^13.0.1", @@ -86,7 +84,8 @@ "sharp": "^0.34.5", "tailwindcss": "^4.2.1", "typescript": "~5.6.2", - "typescript-eslint": "^8.64.0" + "typescript-eslint": "^8.64.0", + "webpack-bundle-analyzer": "^5.3.1" }, "browserslist": { "production": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bec788d..9217879 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -141,12 +141,6 @@ importers: remark-mdx-frontmatter: specifier: ^5.2.0 version: 5.2.0 - replicad: - specifier: ^0.20.5 - version: 0.20.5 - replicad-opencascadejs: - specifier: ^0.20.2 - version: 0.20.2 three: specifier: ^0.182.0 version: 0.182.0 @@ -217,6 +211,9 @@ importers: typescript-eslint: specifier: ^8.64.0 version: 8.64.0(eslint@10.7.0(jiti@2.6.1))(typescript@5.6.3) + webpack-bundle-analyzer: + specifier: ^5.3.1 + version: 5.3.1 packages: @@ -1337,6 +1334,10 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} + '@discoveryjs/json-ext@0.6.3': + resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} + engines: {node: '>=14.17.0'} + '@docsearch/core@4.6.3': resolution: {integrity: sha512-rUOujwIpxJRgD7+kicVsI3D5sqBvdiRTquzWBpTEXZs8ZXfGbfzpus5HqumaNYTppN2HvH8E2yNuRwYdHJeOlA==} peerDependencies: @@ -3089,9 +3090,6 @@ packages: '@types/offscreencanvas@2019.7.3': resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} - '@types/opentype.js@1.3.4': - resolution: {integrity: sha512-6fbXi67I07ugNM+FExwJnfuui2hD7hraD6nqjr3UnqsbBpxSkrtmO6tBubPdNAjqRT9TVkquVkNS9IkgTtq6/w==} - '@types/pako@2.0.4': resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} @@ -3838,6 +3836,10 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4568,12 +4570,6 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatbush@4.5.0: - resolution: {integrity: sha512-K7JSilGr4lySRLdJqKY45fu0m/dIs6YAAu/ESqdMsnW3pI0m3gpa6oRc6NDXW161Ov9+rIQjsuyOt5ObdIfgwg==} - - flatqueue@3.0.0: - resolution: {integrity: sha512-y1deYaVt+lIc/d2uIcWDNd0CrdQTO5xoCjeFdhX0kSXvm2Acm0o+3bAOiYklTEoRyzwio3sv3/IiBZdusbAe2Q==} - flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} @@ -4810,6 +4806,9 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + html-minifier-terser@6.1.0: resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} engines: {node: '>=12'} @@ -5987,11 +5986,6 @@ packages: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true - opentype.js@1.3.4: - resolution: {integrity: sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==} - engines: {node: '>= 8.0.0'} - hasBin: true - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -6810,12 +6804,6 @@ packages: renderkid@3.0.0: resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} - replicad-opencascadejs@0.20.2: - resolution: {integrity: sha512-scws/tSYeO3CxwRBVK264QbHcGZXQl1mNmRewczSO/Ac9BLDNQ6RAMk5NeB8jK7YQlRU04kwf+Ge0ydFRUO6ig==} - - replicad@0.20.5: - resolution: {integrity: sha512-6aDfqwo9yWWJc5w0IOzvbVTij9o/Edh1Qm619bxAcqhNvXB7m0e7JNlEkhXIJFyRACJjQR0KHD5b3DXtvKDkCQ==} - require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -7011,6 +6999,10 @@ packages: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -7109,9 +7101,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string.prototype.codepointat@0.2.1: - resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} - string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -7304,9 +7293,6 @@ packages: resolution: {integrity: sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==} engines: {node: '>=10'} - tiny-inflate@1.0.3: - resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -7592,6 +7578,11 @@ packages: engines: {node: '>= 10.13.0'} hasBin: true + webpack-bundle-analyzer@5.3.1: + resolution: {integrity: sha512-wP2EusncRGL1tZyMHC/umLkjPdYMkTL9nPEKh8G8dkYCJ9TyF6xnXFqjhdqmv5J900irN/g0P5jMvLT22krEXQ==} + engines: {node: '>= 20.9.0'} + hasBin: true + webpack-dev-middleware@7.4.5: resolution: {integrity: sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==} engines: {node: '>= 18.12.0'} @@ -9261,6 +9252,8 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} + '@discoveryjs/json-ext@0.6.3': {} + '@docsearch/core@4.6.3(@types/react@19.2.13)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': optionalDependencies: '@types/react': 19.2.13 @@ -12054,8 +12047,6 @@ snapshots: '@types/offscreencanvas@2019.7.3': {} - '@types/opentype.js@1.3.4': {} - '@types/pako@2.0.4': {} '@types/phoenix@1.6.6': {} @@ -12917,6 +12908,8 @@ snapshots: commander@11.1.0: {} + commander@14.0.3: {} + commander@2.20.3: {} commander@5.1.0: {} @@ -13738,12 +13731,6 @@ snapshots: flat@5.0.2: {} - flatbush@4.5.0: - dependencies: - flatqueue: 3.0.0 - - flatqueue@3.0.0: {} - flatted@3.4.2: {} follow-redirects@1.16.0(debug@4.4.3): @@ -14058,6 +14045,8 @@ snapshots: html-escaper@2.0.2: {} + html-escaper@3.0.3: {} + html-minifier-terser@6.1.0: dependencies: camel-case: 4.1.2 @@ -15379,11 +15368,6 @@ snapshots: opener@1.5.2: {} - opentype.js@1.3.4: - dependencies: - string.prototype.codepointat: 0.2.1 - tiny-inflate: 1.0.3 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -16417,14 +16401,6 @@ snapshots: lodash: 4.18.1 strip-ansi: 6.0.1 - replicad-opencascadejs@0.20.2: {} - - replicad@0.20.5: - dependencies: - '@types/opentype.js': 1.3.4 - flatbush: 4.5.0 - opentype.js: 1.3.4 - require-from-string@2.0.2: {} require-like@0.1.2: {} @@ -16666,6 +16642,12 @@ snapshots: mrmime: 2.0.1 totalist: 3.0.1 + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + sisteransi@1.0.5: {} sitemap@7.1.3: @@ -16764,8 +16746,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.2.0 - string.prototype.codepointat@0.2.1: {} - string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -16927,8 +16907,6 @@ snapshots: dependencies: convert-hrtime: 3.0.0 - tiny-inflate@1.0.3: {} - tiny-invariant@1.3.3: {} tiny-warning@1.0.3: {} @@ -17238,6 +17216,22 @@ snapshots: - bufferutil - utf-8-validate + webpack-bundle-analyzer@5.3.1: + dependencies: + '@discoveryjs/json-ext': 0.6.3 + acorn: 8.17.0 + acorn-walk: 8.3.5 + commander: 14.0.3 + escape-string-regexp: 5.0.0 + html-escaper: 3.0.3 + opener: 1.5.2 + picocolors: 1.1.1 + sirv: 3.0.2 + ws: 8.21.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.108.4(@swc/core@1.15.43)(postcss@8.5.15)): dependencies: colorette: 2.0.20