Skip to content

Instantly share code, notes, and snippets.

@konsalex
Created April 26, 2023 18:02
Show Gist options
  • Save konsalex/1d253eb382c29b8492d01f16c9abf4f5 to your computer and use it in GitHub Desktop.
Save konsalex/1d253eb382c29b8492d01f16c9abf4f5 to your computer and use it in GitHub Desktop.
Replace barrel imports
import acorn, { SourceLocation } from 'acorn';
import { ancestor } from 'acorn-walk';
type ImportType = 'local' | 'package';
function generateIndividualImport(iconName: string, type: ImportType) {
/**
* Icon name can be:
* - CheckCircleIconSolid - hero icon
* - CheckCircleIconOutline - hero icon
* - CheckCircleIcon - custom icon
*/
const iconType =
iconName.includes('Solid') || iconName.includes('Outline')
? 'hero'
: 'custom';
if (iconType === 'hero') {
const type = iconName.endsWith('Solid') ? 'solid' : 'outline';
const cleanName = iconName.replace('Solid', '').replace('Outline', '');
return `import { default as ${iconName} } from '@heroicons/react/24/${type}/${cleanName}';`;
}
/**
* An icon can be imports
* 1. locally like "../icons/"
* 2. from the package "@neo4j-ndl/react/src/icons"
*/
let path = '..';
if (type === 'package') {
path = '../../react/src';
}
return `export { default as ${iconName} } from '${path}/icons/generated/custom/${iconName.replace(
'Icon',
''
)}';`;
}
function replace(src: string) {
const parsedTree = acorn.Parser.extend().parse(src, {
sourceType: 'module',
ecmaVersion: 'latest',
locations: true,
});
let loc: SourceLocation | undefined;
const icons: { name: string; type: ImportType }[] = [];
ancestor(parsedTree, {
Literal(_node, state, ancestors) {
const value = (_node as any).value as string;
if (value === '../icons' || value === '@neo4j-ndl/react/src/icons') {
const type: ImportType = value === '../icons' ? 'local' : 'package';
const importNode = ancestors.find(
(n) => n.type === 'ImportDeclaration'
);
if (!importNode) return src;
// Get all imported icons
(importNode as any).specifiers.forEach((n: any) => {
icons.push({
name: n.imported.name,
type: type,
});
});
// Block state location
loc = importNode.loc;
}
},
});
if (!loc) return src;
const sourceSplit = src.split('\n');
sourceSplit.splice(
loc.start.line - 1,
loc.end.line - loc.start.line + 1,
...icons.map((i) => generateIndividualImport(i.name, i.type))
);
return sourceSplit.join('\n');
}
/**
* Plugin that transforms barrel
* imports from icons to explicit path imports
* Example:
* `import { CheckCircleIcon } from '../icons';` becomes
* `import { default as CheckCircleIcon } from '@heroicons/react/24/outline/CheckCircleIcon';`
*
* so it will not import the barrel file, that will lead to loading all the icons in once
*/
export function barrelModifier() {
const fileRegex = /\.tsx$/;
return {
name: 'transform-barrel-imports',
transform(src: string, id: string) {
const iconMatcher = /icons[\"\']/;
if (fileRegex.test(id)) {
if (iconMatcher.test(src)) {
console.info(`Found icon import in file: ${id}`);
return {
code: replace(src),
map: null, // provide source map if available
};
}
return {
code: src,
map: null, // provide source map if available
};
}
},
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment