In early September, the build result in WeChat DevTools went red again: the main bundle was 2.3 MB, over the 2 MB limit, and real-device preview wouldn't upload at all.
Back in 2018 we slimmed this tea mini program down from 1.9 MB to 1.1 MB and even wrote a post about how we measured it. Six years later, the weight was back. Digging through what had been added since turned out to be quite interesting.
Measure First, Don't Guess
Same rule as before: decide how to measure first. We exported the DevTools code dependency analysis, then sorted every file in the build output by size into a spreadsheet. Don't look at the total — look at who's taking it.
The top offenders:
- A chart library bundling 380 KB, used on exactly one page: "Sales Trends".
- A full lodash import when only 4 methods were actually used — a bit over 70 KB.
- A date library duplicating formatting the platform already provides.
- Three PNGs left over from old campaigns, long without an entry point, still sitting in the main bundle.
- Three pages: a "Points Mall" from 2019, a "Store Navigation" from 2021, and a "Survey" that never shipped.
Images weren't the culprit this time — the CDN and WebP setup from six years ago still works. What had actually grown were dependencies and pages, each added with a casual "might as well".
Step 1: Clear Out What Nobody Uses
Dead code is the easiest to cut. First we checked whether anyone still visited the three pages: six months of mini program backend analytics showed the Points Mall at 11 visits, Store Navigation at 0, Survey at 0. We kept the 11-visit page but moved it into a subpackage; the two zeros came out entirely — the code stays in Git history, not in the bundle.
Deleting those three PNGs saved 90-plus KB.
This step felt the best — no one to negotiate with, and the bundle shrank immediately.
Step 2: Dependency Audit
Five npm packages; for each we asked "where is it used, and could we just write it ourselves?"
lodash was used for debounce, get, groupBy, and cloneDeep. We replaced them with four hand-written functions — under 60 lines total — saving 70 KB. We're not against libraries; this mini program genuinely used that little of it.
The date library went entirely. On the client, native Date plus a 20-line formatting function was enough — the server already returned pre-formatted strings.
The chart library was the hardest. It was only used on the Sales Trends page — which lived in the main bundle. We moved the page into a subpackage, then replaced the library with a lightweight canvas implementation that draws just line and bar charts, a little over 100 lines. Size went from 380 KB to under 40 KB; the client looked at it and said they couldn't tell the difference.
One pitfall here: subpackages can't reference each other's custom components. The chart component had been shared by two pages, so when splitting we either had to duplicate it into the subpackage or hoist it into the main bundle. We chose the latter — shared components stay in the main bundle, and things used by only one subpackage stay there. Don't pile everything upward in the name of "reuse".
Step 3: Restructure the Subpackages
The 2018 setup was one main bundle plus one subpackage. Over six years that subpackage had grown to 1.4 MB on its own, mixing low-frequency and high-frequency features.
We reorganized into a main bundle plus three subpackages:
- Main bundle: home, categories, product detail, cart, login, plus globally shared components.
- trade subpackage: order list, order detail, after-sales, Points Mall.
- user subpackage: profile, addresses, invoices, favorites.
- misc subpackage: brand intro, help, campaign archive, Sales Trends.
We configured three preload rules: after entering the home page, trade downloads during idle time; before entering the profile, user preloads. misc doesn't preload — if someone actually taps it, a short wait is fine.
The main bundle still carried legacy baggage: an early utils module was imported by the main bundle and every subpackage; we'd flagged it once before, and this time found it had been copied twice more. We unified it into the main bundle and added a build check that warns when the same code gets bundled twice.
Step 4: Lazy-Load by Route
Some logic isn't a page but "initializes the moment the app opens": the analytics SDK, a stats reporter, and login-state checks. All of it used to sit in app.js onLaunch, and the first screen waited for all of it.
We made it on-demand: analytics initializes after the first page's onShow, stats reporting moves to idle callbacks, and the login check stays but does a single lightweight validation. None of this shrinks the bundle — it shrinks time-to-first-screen.
The Numbers
After rebuilding, we pulled out the same methodology from the 1.1 MB era: 20 runs on a real device, take the median.
- Main bundle: 2.3 MB down to 1.8 MB, leaving 200 KB of headroom under the 2 MB limit.
- The three subpackages total 1.6 MB — smaller than the old mixed one.
- First screen median: 2.1s down to 1.6s; slowest run 2.0s.
- The chart used only on the Trends page no longer loads on first screen at all.
No framework change, no new hardware — mostly deleting and moving.
Q: Why Did It Grow Back?
Someone asked: you got it to 1.1 MB six years ago — how did you let it grow back?
Honestly, no single commit was wrong. The chart library was added because a trend chart was needed; the survey page because feedback collection was wanted at the time. Every addition had a reason. Bundle size, like a desk drawer, creeps up when nobody watches it.
This round we added a build gate: the build fails if the main bundle exceeds 1.9 MB, instead of discovering it when upload fails. And a line in the README: before adding a dependency, look at the dependency analysis table first.
We wrapped up on the afternoon of September 6, and the build went from red back to green. Someone pasted the two screenshots side by side in the docs: 2018 on the left, 2024 on the right, with six years and a pile of "might as well" additions in between.

