From fa8f37dc5751f67efb97f39a70730c3c59d1d0ca Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 04:56:42 +0000 Subject: [PATCH] Stop precompiling assets nothing loads Four separate sources of waste in `rails assets:precompile`, none of which were breaking anything, but all of which made the build slower and the output noisier than it needed to be. Sprockets was compiling every file twice. `manifest.js` had `link_directory` over app/assets/javascripts and app/assets/stylesheets, which emits a fingerprinted + gzipped standalone asset for each of the ~85 files in those directories. Those same files are all pulled into application.js/application.css by `require_tree .`, and no view links to any of them individually - a grep for `javascript_include_tag`/`stylesheet_link_tag`/`asset_path` across app/ and lib/ turns up only 'application' and 'thredded' (the latter still precompiled via config/initializers/assets.rb). Replaced the two directives with explicit links to the two manifests we actually serve. Webpack was building a pack nothing renders. `packs/server_rendering.js` exists for react-rails' `prerender: true`, and there are zero prerendered components in the codebase. It was costing 704 KiB of output, a 2.86 MiB source map, and a quarter of the build time on every deploy. Production source maps were being shipped and compressed. Webpacker defaults to `devtool: 'source-map'`, which annotates every bundle with a `sourceMappingURL` comment; with no browser-side Sentry SDK, the only consumers were visitors with devtools open pulling multi-megabyte maps. Switched to `hidden-source-map` so the maps still land in public/packs for manual debugging without being advertised, and dropped `.map` from both compression passes - those `.map.gz` and `.map.br` files accounted for 7 of the 14 entries in the asset size limit warning. Browser data was stale. Ran update-browserslist-db to bring caniuse-lite current (no target browser changes, so compiled output is unaffected) and added a resolution to dedupe baseline-browser-mapping, which two different transitive browserslist versions were pinning to old copies. Verified against a production webpack build: no `.map.gz`/`.map.br` emitted, no `sourceMappingURL` in the shipped bundles, no server_rendering entry, and both stale-data warnings gone. Same-machine before/after: public/packs 19M -> 12M, cold build 83s -> 45s, warm build ~28s -> ~21s. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0154QFGT71QHC6WCsjvdkyvJ --- app/assets/config/manifest.js | 11 ++++++++-- app/javascript/packs/server_rendering.js | 5 ----- config/webpack/environment.js | 22 ++++++++++++++++++++ package.json | 1 + yarn.lock | 26 ++++++++---------------- 5 files changed, 40 insertions(+), 25 deletions(-) delete mode 100644 app/javascript/packs/server_rendering.js diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index 7bd6f1111..cd3d1760f 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,6 +1,13 @@ //= link_tree ../images -//= link_directory ../javascripts .js -//= link_directory ../stylesheets .css + +// Only the bundles we actually reference from a layout get their own compiled +// output. app/assets/javascripts/application.js and app/assets/stylesheets/application.css +// already `require_tree .` everything beside them, so the `link_directory` +// directives that used to live here compiled every one of those ~85 files a +// second time as a standalone (fingerprinted, gzipped) asset that no view links to. +// Add an explicit `//= link` here if a file ever needs to be served on its own. +//= link application.js +//= link application.css //= link preload/jquery-3.1.1.min.js //= link Chart.bundle.js //= link chartkick.js diff --git a/app/javascript/packs/server_rendering.js b/app/javascript/packs/server_rendering.js deleted file mode 100644 index a31e683d4..000000000 --- a/app/javascript/packs/server_rendering.js +++ /dev/null @@ -1,5 +0,0 @@ -// By default, this pack is loaded for server-side rendering. -// It must expose react_ujs as `ReactRailsUJS` and prepare a require context. -var componentRequireContext = require.context("components", true); -var ReactRailsUJS = require("react_ujs"); -ReactRailsUJS.useContext(componentRequireContext); diff --git a/config/webpack/environment.js b/config/webpack/environment.js index 6e17e219d..95ce29a59 100644 --- a/config/webpack/environment.js +++ b/config/webpack/environment.js @@ -89,4 +89,26 @@ if (environment.plugins.getIndex('OptimizeCSSAssets') >= 0) { environment.plugins.delete('OptimizeCSSAssets') } +// This file is shared by all three environments, so guard production-only tweaks +// on NODE_ENV (config/webpack/production.js sets it before requiring us). +if (process.env.NODE_ENV === 'production') { + // Webpacker defaults to `devtool: 'source-map'`, which appends a + // `//# sourceMappingURL=` comment to every shipped bundle. Nothing consumes those + // maps automatically (there is no browser-side Sentry SDK), but every visitor who + // opens devtools pulls down a multi-megabyte map. `hidden-source-map` still writes + // the maps into public/packs for manual debugging, just without the comment. + environment.config.merge({ devtool: 'hidden-source-map' }) + + // Webpacker gzips and brotlis `.map` files too, which is where most of the + // "asset size limit" warnings in the precompile output come from. Maps are never + // fetched with content-encoding negotiation, so drop them from both passes. + const COMPRESSED_TYPES = /\.(js|css|html|json|ico|svg|eot|otf|ttf)$/ + const compressionPlugins = ['Compression', 'Compression Brotli'] + compressionPlugins.forEach((name) => { + if (environment.plugins.getIndex(name) < 0) return + const plugin = environment.plugins.get(name) + if (plugin && plugin.options) plugin.options.test = COMPRESSED_TYPES + }) +} + module.exports = environment diff --git a/package.json b/package.json index 5091d40f0..085d98605 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ }, "resolutions": { "@types/minimatch": "^3.0.5", + "baseline-browser-mapping": "^2.11.13", "chokidar": "^3.4.0", "minimatch": "^3.1.2", "rails_admin/@fortawesome/fontawesome-free": "^5.15.0" diff --git a/yarn.lock b/yarn.lock index bc46f04c0..f8f84df48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1845,15 +1845,10 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -baseline-browser-mapping@^2.10.12: - version "2.10.13" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz#5a154cc4589193015a274e3d18319b0d76b9224e" - integrity sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw== - -baseline-browser-mapping@^2.8.3: - version "2.8.9" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.9.tgz#fd0b8543c4f172595131e94965335536b3101b75" - integrity sha512-hY/u2lxLrbecMEWSB0IpGzGyDyeoMFQhCvZd2jGFSE5I17Fh01sYUBPCJtkWERw7zrac9+cIghxm/ytJa2X8iA== +baseline-browser-mapping@^2.10.12, baseline-browser-mapping@^2.11.13, baseline-browser-mapping@^2.8.3: + version "2.11.13" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz#660073103c1bee93e54df55f117b7528adf6af19" + integrity sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ== batch@0.6.1: version "0.6.1" @@ -2209,15 +2204,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001741: - version "1.0.30001745" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz#ab2a36e3b6ed5bfb268adc002c476aab6513f859" - integrity sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ== - -caniuse-lite@^1.0.30001774, caniuse-lite@^1.0.30001782: - version "1.0.30001784" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz#bdf9733a0813ccfb5ab4d02f2127e62ee4c6b718" - integrity sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001741, caniuse-lite@^1.0.30001774, caniuse-lite@^1.0.30001782: + version "1.0.30001809" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz#e6cf71f14ddfe008f114dd2a846923be3c03a07b" + integrity sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ== case-sensitive-paths-webpack-plugin@^2.4.0: version "2.4.0"