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.
This commit is contained in:
2026-09-17 10:11:12 +07:00
parent 76cf4c6d7e
commit 3320513d7a
2 changed files with 28 additions and 1 deletions
+2 -1
View File
@@ -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.
+26
View File
@@ -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
],
};
};