Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vite plugin for optimization #1944

Open
2 of 14 tasks
david-plugge opened this issue Mar 7, 2024 · 4 comments
Open
2 of 14 tasks

Vite plugin for optimization #1944

david-plugge opened this issue Mar 7, 2024 · 4 comments
Labels
💡 idea New ideas

Comments

@david-plugge
Copy link

Package

  • lucide
  • lucide-angular
  • lucide-flutter
  • lucide-preact
  • lucide-react
  • lucide-react-native
  • lucide-solid
  • lucide-svelte
  • lucide-vue
  • lucide-vue-next
  • Figma plugin
  • all JS packages
  • site

Description

While the new way of importing components in svelte using the full path works pretty well to avoid annoyingly long vite optimizations, i think its just bad DX. So after a bit of playing around i came up with a vite plugin that solves this issue.

function lucideSvelteImportOptimizer(): Plugin {
	return {
		name: 'lucide-svelte optimizer',
		transform(code, id) {
			const ms = new MagicString(code, { filename: id });

			ms.replace(
				/([ \t]*)import\s+\{(.*?)\}\s+from\s+['"]lucide-svelte['"];?/g,
				(match, whitespace: string, importNames: string) => {
					const hasSemi = match.endsWith(';');

					const imports = importNames
						.split(',')
						.map((v) => v.trim())
						.map((name) => {
							const path = name
								.split('')
								.map((c, i) => {
									const code = c.charCodeAt(0);
									return code >= 65 && code <= 90
										? (i === 0 ? '' : '-') + c.toLowerCase()
										: c;
								})
								.join('');

							return `${whitespace}import ${name} from 'lucide-svelte/icons/${path}'${hasSemi ? ';' : ''}`;
						});

					return imports.join('\n');
				},
			);

			if (ms.hasChanged()) {
				return {
					code: ms.toString(),
					map: ms.generateMap(),
				};
			}
		},
	};
}

I did not yet test if it works with aliases (eg. User as UserIcon) with other than that it works pretty well!

Use cases

<script>
  import { User, FolderCog } from 'lucide-svelte'
  // is transformed to
  import User from 'lucide-svelte/icons/user';
  import FolderCog from 'lucide-svelte/icons/folder-cog'
</script>

<User />
<FolderCog />

Checklist

  • I have searched the existing issues to make sure this bug has not already been reported.
@david-plugge david-plugge added the 💡 idea New ideas label Mar 7, 2024
@david-plugge
Copy link
Author

I have not checked out if other packages than lucide-svelte expose the icons via the .../icons/* path but if so it would work for any lucide package when used with vite

@ericfennis
Copy link
Member

@david-plugge This is awesome thanks for sharing.

Aliases could be a problem, but not in the way you described. We currently have alias imports for certain icons, sometimes we rename icons when needed, we solve this by exporting the "old" name. But I think we can fix this.

@TheEisbaer
Copy link

@david-plugge thank you so much, this has solved my problem over at tabler icons:

function tablerSvelteImportOptimizer(): import('vite').Plugin {
	return {
		name: 'tabler-svelte optimizer',
		transform(code, id) {
			const ms = new MagicString(code, { filename: id });
			ms.replace(
				/([ \t]*)import\s+\{([^;]*?)\}\s+from\s+['"]@tabler\/icons-svelte['"];/g,
				(match, whitespace: string, importNames: string) => {
					const hasSemi = match.endsWith(';');
					const imports = importNames
						.split(',')
						.map((v) => v.trim())
						.map((name) => {
							const path = name;
							return `${whitespace}import ${name} from '@tabler/icons-svelte/${path}.svelte'${hasSemi ? ';' : ''}`;
						});
					return imports.join('\n');
				}
			);

			if (ms.hasChanged()) {
				return {
					code: ms.toString(),
					map: ms.generateMap()
				};
			}
		}
	};
}

@jamesbirtles
Copy link

cool plugin, I had to modify it a bit to get it to work with icons with numbers in them, as well as new lines within the import.
Whilst i was there I also bodged in support for lucide aliases by checking against the .d.ts for each icon, I'm sure this pretty inefficient but its still faster than transforming all the icons

function lucideSvelteImportOptimizer(): Plugin {
	const aliases = readFileSync('node_modules/lucide-svelte/dist/aliases.d.ts', 'utf8');
	return {
		name: 'lucide-svelte optimizer',
		transform(code, id) {
			const ms = new MagicString(code, { filename: id });

			ms.replace(
				/([ \t]*)import\s+\{([^}]+)\}\s+from\s+["']lucide-svelte["'];/g,
				(match, whitespace: string, importNames: string) => {
					const hasSemi = match.endsWith(';');

					const imports = importNames
						.split(',')
						.map((v) => v.trim())
						.map((name) => {
							let path;
							const alias = aliases.match(
								new RegExp(`as ${name} } from '\\.\\/icons\\/(.+)\\.svelte';`),
							);
							if (alias) {
								path = alias[1]!;
							} else {
								path = name
									.split('')
									.map((c, i) => {
										const code = c.charCodeAt(0);
										return (code >= 65 && code <= 90) || (code >= 48 && code <= 57)
											? (i === 0 ? '' : '-') + c.toLowerCase()
											: c;
									})
									.join('');
							}

							return `${whitespace}import ${name} from 'lucide-svelte/icons/${path}'${hasSemi ? ';' : ''}`;
						});

					return imports.join('\n');
				},
			);

			if (ms.hasChanged()) {
				return {
					code: ms.toString(),
					map: ms.generateMap(),
				};
			}
		},
	};
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💡 idea New ideas
Projects
None yet
Development

No branches or pull requests

4 participants