lucide transofrm plugin
babel-plugin-lucide-transform.js
107 lines 3.3 kB view raw
1const fs = require("fs"); 2const path = require("path"); 3 4const wordSeparators = 5 /[\s\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]+/; 6const capital_plus_lower = /[A-ZÀ-Ý\u00C0-\u00D6\u00D9-\u00DD][a-zà-ÿ]/g; 7const capitals = /[A-Z0-9À-Ý\u00C0-\u00D6\u00D9-\u00DD]+/g; 8 9function kebabCase(str) { 10 let res = str; 11 res = str.replace( 12 capital_plus_lower, 13 (match) => ` ${match[0].toLowerCase() || match[0]}${match[1]}`, 14 ); 15 res = res.replace(capitals, (match) => ` ${match.toLowerCase()}`); 16 return res 17 .trim() 18 .split(wordSeparators) 19 .join("-") 20 .replace(/^-/, "") 21 .replace(/-\s*$/, ""); 22} 23 24// Build icon name to file mapping from lucide's export file 25let iconToFileMap; 26function getIconMapping(baseDir) { 27 if (iconToFileMap) return iconToFileMap; 28 29 iconToFileMap = {}; 30 try { 31 const lucideExportsPath = path.join( 32 baseDir, 33 "node_modules/lucide-react-native/dist/esm/lucide-react-native.js", 34 ); 35 const content = fs.readFileSync(lucideExportsPath, "utf8"); 36 37 // Match lines like: export { default as User2, default as UserRound } from './icons/user-round.js'; 38 const exportRegex = 39 /export\s*\{([^}]+)\}\s*from\s*['"]\\.\/icons\/([^'"]+)\.js['"]/g; 40 41 let match; 42 while ((match = exportRegex.exec(content)) !== null) { 43 const exports = match[1]; 44 const filename = match[2]; 45 46 // Extract all icon names from the export list 47 const names = exports 48 .split(",") 49 .map((name) => name.trim()) 50 .filter((name) => name.startsWith("default as ")) 51 .map((name) => name.replace("default as ", "").trim()); 52 53 // Map each name to the filename 54 names.forEach((name) => { 55 iconToFileMap[name] = filename; 56 }); 57 } 58 } catch (err) { 59 console.warn( 60 "Failed to parse lucide exports, falling back to kebab-case:", 61 err.message, 62 ); 63 iconToFileMap = {}; 64 } 65 66 return iconToFileMap; 67} 68 69function lucideTransformPlugin({ types: t }) { 70 return { 71 visitor: { 72 ImportDeclaration(path, state) { 73 const source = path.node.source.value; 74 if (source === "lucide-react-native") { 75 const imports = []; 76 const baseDir = state.file.opts.root || process.cwd(); 77 const mapping = getIconMapping(baseDir); 78 79 path.node.specifiers.forEach((specifier) => { 80 if (specifier.type === "ImportSpecifier") { 81 const importedName = specifier.imported.name; 82 const localName = specifier.local.name; 83 84 // Use the mapping if available, otherwise fall back to kebab-case 85 const filename = mapping[importedName] || kebabCase(importedName); 86 87 // Create individual default import for each icon 88 imports.push( 89 t.importDeclaration( 90 [t.importDefaultSpecifier(t.identifier(localName))], 91 t.stringLiteral( 92 `lucide-react-native/dist/esm/icons/${filename}`, 93 ), 94 ), 95 ); 96 } 97 }); 98 99 // Replace the original import with individual imports 100 path.replaceWithMultiple(imports); 101 } 102 }, 103 }, 104 }; 105} 106 107module.exports = lucideTransformPlugin;