mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-10 13:18:25 +00:00
fix(lucide-react-native): Add icon exports property to the package.json file (#4522)
* Add export per icon * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Add documentations * Format code --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
parent
4c663b9a32
commit
7291cbcc0f
@@ -72,6 +72,11 @@ export const reactNativeSidebar = [
|
||||
link: '/guide/react-native/advanced/combining-icons',
|
||||
desc: 'Combine multiple icons into one',
|
||||
},
|
||||
{
|
||||
text: 'Optimizations',
|
||||
link: '/guide/react-native/advanced/optimizations',
|
||||
desc: 'Optimizing your app for performance',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Optimizations - React Native
|
||||
description: Learn how to optimize the bundle size of your React Native and Expo web exports by tree-shaking Lucide icons.
|
||||
---
|
||||
|
||||
# Optimizations
|
||||
|
||||
By default you import icons from the `lucide-react-native` entry point:
|
||||
|
||||
```jsx
|
||||
import { Camera } from 'lucide-react-native';
|
||||
```
|
||||
|
||||
This is convenient, but it relies on the bundler to _tree-shake_ the entry point (a "barrel" file that re-exports every icon) so that only the icons you actually use end up in your bundle.
|
||||
|
||||
On native platforms this is usually fine. On **web exports** it can be a problem, because the Metro bundler is currently not very good at tree-shaking barrel imports and may include **all** icons in your final bundle.
|
||||
|
||||
## The Metro tree-shaking limitation
|
||||
|
||||
When you build for web with Expo (`expo export --platform web`), Metro is used to bundle your JavaScript. Metro bundles modules on-demand and, by default, does not remove unused exports from a barrel file.
|
||||
|
||||
This means an import like the one below can pull in the entire icon set, even though only a single icon is rendered:
|
||||
|
||||
```jsx
|
||||
// May bundle every Lucide icon on web exports
|
||||
import { Camera } from 'lucide-react-native';
|
||||
```
|
||||
|
||||
Expo documents an experimental optimization for [React Native web imports](https://docs.expo.dev/guides/tree-shaking/#react-native-web-imports) and for [removing unused imports and exports](https://docs.expo.dev/guides/tree-shaking/#remove-unused-imports-and-exports). In practice this tree-shaking does **not** currently work reliably for `lucide-react-native` barrel imports, so the unused icons are not removed from web exports.
|
||||
|
||||
## Recommended: import icons individually
|
||||
|
||||
To guarantee that only the icons you use are bundled — regardless of the bundler's tree-shaking capabilities — import each icon directly from its own module:
|
||||
|
||||
```jsx
|
||||
import Camera from 'lucide-react-native/icons/camera';
|
||||
|
||||
// Usage
|
||||
const App = () => {
|
||||
return <Camera />;
|
||||
};
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
Because each icon lives in its own file, the bundler only ever includes the icons you explicitly import. This keeps your web export small without depending on Metro's experimental tree-shaking.
|
||||
|
||||
The icon module name is the [kebab-case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case) version of the icon name. For example, the `ArrowRight` icon is imported from `lucide-react-native/icons/arrow-right`:
|
||||
|
||||
```jsx
|
||||
import ArrowRight from 'lucide-react-native/icons/arrow-right';
|
||||
```
|
||||
|
||||
::: tip
|
||||
Individual icon imports work identically on native and web, so you can safely use this style across your whole project to keep bundles as small as possible on every platform.
|
||||
:::
|
||||
@@ -25,20 +25,29 @@
|
||||
"amdName": "lucide-react-native",
|
||||
"main": "dist/cjs/lucide-react-native.js",
|
||||
"module": "dist/esm/lucide-react-native.mjs",
|
||||
"typings": "dist/lucide-react-native.d.ts",
|
||||
"typings": "dist/types/lucide-react-native.d.ts",
|
||||
"react-native": "dist/esm/lucide-react-native.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/lucide-react-native.d.ts",
|
||||
"types": "./dist/types/lucide-react-native.d.ts",
|
||||
"react-native": "./dist/esm/lucide-react-native.mjs",
|
||||
"import": "./dist/esm/lucide-react-native.mjs",
|
||||
"browser": "./dist/esm/lucide-react-native.mjs",
|
||||
"require": "./dist/cjs/lucide-react-native.js"
|
||||
},
|
||||
"./icons": {
|
||||
"types": "./dist/icons.d.ts",
|
||||
"types": "./dist/types/icons.d.ts",
|
||||
"react-native": "./dist/esm/icons/index.mjs",
|
||||
"import": "./dist/esm/icons/index.mjs",
|
||||
"browser": "./dist/esm/icons/index.mjs",
|
||||
"require": "./dist/cjs/icons/index.js"
|
||||
},
|
||||
"./icons/*": {
|
||||
"types": "./dist/types/icons/*.d.ts",
|
||||
"react-native": "./dist/esm/icons/*.mjs",
|
||||
"import": "./dist/esm/icons/*.mjs",
|
||||
"browser": "./dist/esm/icons/*.mjs",
|
||||
"require": "./dist/cjs/icons/*.js"
|
||||
}
|
||||
},
|
||||
"sideEffects": false,
|
||||
@@ -49,13 +58,14 @@
|
||||
"build": "pnpm clean && pnpm copy:license && pnpm build:icons && pnpm build:bundles",
|
||||
"copy:license": "cp ../../LICENSE ./LICENSE",
|
||||
"clean": "rm -rf dist && rm -rf stats && rm -rf ./src/icons/*.ts",
|
||||
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --iconFileExtension=.ts --exportFileName=index.ts --withAliases --aliasesFileExtension=.ts",
|
||||
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --iconFileExtension=.ts --exportFileName=index.ts --withAliases --separateAliasesFile --separateAliasesFileIgnore=fingerprint --aliasesFileExtension=.ts",
|
||||
"build:bundles": "rollup -c ./rollup.config.mjs",
|
||||
"test": "pnpm build:icons && vitest run",
|
||||
"test:watch": "vitest watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lucide/build-icons": "workspace:*",
|
||||
"@lucide/helpers": "workspace:*",
|
||||
"@lucide/rollup-plugins": "workspace:*",
|
||||
"@lucide/shared": "workspace:*",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import plugins from '@lucide/rollup-plugins';
|
||||
import dts from 'rollup-plugin-dts';
|
||||
import pkg from './package.json' with { type: 'json' };
|
||||
import getIconEntryNamesAndAliases from './scripts/getIconEntryNamesAndAliases.mts';
|
||||
|
||||
const packageName = 'LucideReact';
|
||||
const outputFileName = 'lucide-react-native';
|
||||
@@ -22,6 +23,9 @@ const bundles = [
|
||||
},
|
||||
];
|
||||
|
||||
const iconAndAliasEntries = await getIconEntryNamesAndAliases();
|
||||
const typesModuleMatcher = /[/\\]src[/\\]types\.(d\.)?ts$/;
|
||||
|
||||
const configs = bundles.map(({ inputs, outputDir, format, preserveModules, extension = 'js' }) => ({
|
||||
input: inputs,
|
||||
plugins: plugins({ pkg }),
|
||||
@@ -52,7 +56,7 @@ export default [
|
||||
input: inputs[0],
|
||||
output: [
|
||||
{
|
||||
file: `dist/${outputFileName}.d.ts`,
|
||||
file: `dist/types/${outputFileName}.d.ts`,
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
@@ -62,17 +66,32 @@ export default [
|
||||
input: 'src/icons/index.ts',
|
||||
output: [
|
||||
{
|
||||
file: `dist/icons.d.ts`,
|
||||
file: `dist/types/icons.d.ts`,
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
},
|
||||
{
|
||||
input: iconAndAliasEntries,
|
||||
// Treat `src/types.ts` as external so `LucideProps` isn't bundled into a shared
|
||||
// hashed chunk; `output.paths` then rewrites the import to the main entry.
|
||||
external: [typesModuleMatcher],
|
||||
output: [
|
||||
{
|
||||
format: 'es',
|
||||
dir: 'dist/types/icons',
|
||||
entryFileNames: '[name].d.ts',
|
||||
paths: (id) => (typesModuleMatcher.test(id) ? `../${outputFileName}.js` : id),
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
},
|
||||
{
|
||||
input: `src/${outputFileName}.suffixed.ts`,
|
||||
output: [
|
||||
{
|
||||
file: `dist/${outputFileName}.suffixed.d.ts`,
|
||||
file: `dist/types/${outputFileName}.suffixed.d.ts`,
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
@@ -82,7 +101,7 @@ export default [
|
||||
input: `src/${outputFileName}.prefixed.ts`,
|
||||
output: [
|
||||
{
|
||||
file: `dist/${outputFileName}.prefixed.d.ts`,
|
||||
file: `dist/types/${outputFileName}.prefixed.d.ts`,
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import path from 'path';
|
||||
import { readSvgDirectory } from '@lucide/helpers';
|
||||
import getIconMetaData from '@lucide/build-icons/utils/getIconMetaData';
|
||||
|
||||
const ICONS_DIR = path.resolve(process.cwd(), '../../icons');
|
||||
|
||||
const ignoreAliases = ['fingerprint'];
|
||||
|
||||
export default async function getIconEntryNamesAndAliases() {
|
||||
const svgFiles = await readSvgDirectory(ICONS_DIR);
|
||||
const iconNames = svgFiles.map((file) => path.basename(file, '.svg'));
|
||||
|
||||
const metaJsonFiles = await getIconMetaData(ICONS_DIR);
|
||||
const iconWithAliases = Object.values(metaJsonFiles).filter(({ aliases }) => aliases != null);
|
||||
|
||||
const aliases = iconWithAliases.flatMap(({ aliases }) => aliases ?? []);
|
||||
const aliasNames = aliases
|
||||
.filter((alias) => !ignoreAliases.includes(alias.name))
|
||||
.map((alias) => alias.name);
|
||||
|
||||
// Return an object mapping the entry name to its source path so Rollup uses the
|
||||
// key as the `[name]` placeholder and emits declaration files flat in the output
|
||||
// directory instead of preserving the `src/icons/` directory structure.
|
||||
return Object.fromEntries(
|
||||
[...iconNames, ...aliasNames].map((name) => [name, path.join('src/icons', `${name}.ts`)]),
|
||||
);
|
||||
}
|
||||
Generated
+3
@@ -511,6 +511,9 @@ importers:
|
||||
'@lucide/build-icons':
|
||||
specifier: workspace:*
|
||||
version: link:../../tools/build-icons
|
||||
'@lucide/helpers':
|
||||
specifier: workspace:*
|
||||
version: link:../../tools/build-helpers
|
||||
'@lucide/rollup-plugins':
|
||||
specifier: workspace:*
|
||||
version: link:../../tools/rollup-plugins
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"declaration": true,
|
||||
"noEmitOnError": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "nodenext",
|
||||
"module": "NodeNext",
|
||||
"target": "ESNext",
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext"],
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
"./utils/*": {
|
||||
"import": "./utils/*.ts",
|
||||
"require": "./utils/*.ts"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./types.ts",
|
||||
"import": "./types.ts"
|
||||
}
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"declaration": true,
|
||||
"noEmitOnError": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "nodenext",
|
||||
"module": "NodeNext",
|
||||
"target": "ESNext",
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext"],
|
||||
|
||||
@@ -40,7 +40,7 @@ export type IconDeprecationReason = 'icon.renamed' | '';
|
||||
export type IconMetadataBase = {
|
||||
toBeRemovedInVersion?: string;
|
||||
categories: string[];
|
||||
aliases?: (string | AliasDeprecation)[];
|
||||
aliases?: AliasDeprecation[];
|
||||
tags: string[];
|
||||
deprecationReason?: IconDeprecationReason;
|
||||
deprecated?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user