From 3320513d7a6cec2812748e269eeef39bd2cac669 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Thu, 17 Sep 2026 10:11:12 +0700 Subject: [PATCH] Bundle only the icons the app renders Lucide ships one module per icon, but the barrel import dragged all ~1600 in. A local Babel plugin rewrites each named import to its own icon module, so the bundle drops from 4.66 MB to 3.40 MB and the earliest tap that reliably lands a photo moves from ~1.8 s to ~1.6 s on the test device. --- RELEASE_NOTES.md | 3 ++- babel.config.js | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 17a6dfe..322e40c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -13,7 +13,8 @@ Memory Cold start - The JS bundle is stored uncompressed in the APK. +- Named Lucide imports are rewritten to per-icon modules, so only the icons the app renders are bundled: 4.66 MB → 3.40 MB (−27%). - The export pipeline and the recipe/GPS helpers load on demand. - Hidden modals render nothing. -Measured on a vivo V2430 (Android 16): the cold-start work did not move the tap threshold (the earliest reliable tap is still ~1.9 s). The remaining time is spent before app JS — process init, SoLoader, Hermes eval. Trimming the bundle is the next candidate; `lucide-react-native` currently ships the whole icon set. +Measured on a vivo V2430 (Android 16) with a force-stop/start/tap probe: the earliest tap that reliably lands a photo moved from ~1.8 s to ~1.6 s once the bundle was trimmed. The rest is spent before app JS — process init, SoLoader, Hermes eval. diff --git a/babel.config.js b/babel.config.js index 4b84a6b..39ca6e5 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,28 @@ +// Lucide ships one module per icon, but the barrel import drags all ~1600 in. +// Rewrite each named import into its own icon module so Metro only walks what we use. +const lucidePerIcon = () => ({ + visitor: { + ImportDeclaration(path) { + if (path.node.source.value !== "lucide-react-native") return; + const t = require("@babel/types"); + const kebab = (n) => + n + .replace(/([a-z0-9])([A-Z])/g, "$1-$2") + .replace(/([A-Za-z])(\d)/g, "$1-$2") + .toLowerCase(); + path.replaceWithMultiple( + path.node.specifiers.map((s) => + s.type === "ImportSpecifier" + ? t.importDeclaration( + [t.importDefaultSpecifier(s.local)], + t.stringLiteral(`lucide-react-native/icons/${kebab(s.imported.name)}`) + ) + : t.importDeclaration([s], t.stringLiteral("lucide-react-native")) + ) + ); + }, + }, +}); module.exports = function (api) { api.cache(true); return { @@ -7,6 +32,7 @@ module.exports = function (api) { ], plugins: [ "react-native-reanimated/plugin", + lucidePerIcon ], }; };