Build a TypeScript Custom Transformer for On‑Demand Ant Design Imports
This article explains how to use TypeScript's custom transformation feature to rewrite import statements like "import { Button } from 'antd'" into per‑component imports, reducing bundle size, and walks through the required AST manipulation, visitor logic, and integration with ts‑loader.
Users of ant‑design may know babel-plugin-import, which rewrites imports such as import { Button } from 'antd' into per‑component imports to shrink bundle size. In a TypeScript project without Babel, the same effect can be achieved with a custom transformation introduced in TypeScript 2.3.
Custom transformers let you modify the TypeScript AST before emitting JavaScript. The article first introduces AST basics, showing a simple variable declaration and its tree representation, and explains that the root node is SourceFile. It also lists the main parts of the TS compilation pipeline: Scanner, Parser, Binder, Checker, and Emitter.
The transformer plugin is applied during the Emitter phase. To enable it, you can configure ts-loader in a Webpack setup by providing a getCustomTransformers option that returns the transformer.
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: {
// other loader options
getCustomTransformers: () => ({ before: [yourImportedTransformer] })
}
}The goal is to convert the example import into:
// before
import { Button } from 'antd'
// after
import Button from 'antd/lib/button'To achieve this, the transformer must replace the NamedImports node with its identifier and modify the StringLiteral to append the identifier in kebab‑case. Traversal is done with ts.visitEachChild, which allows returning a new node to replace the current one.
Node creation uses factory functions prefixed with create, e.g., ts.createIdentifier or ts.createLiteral. The article shows how to detect node types via ts.SyntaxKind or the ts‑is‑kind helper.
The core updateImportNode function examines each node: if it is a NamedImports, it extracts the identifier name and returns a new identifier; if it is a StringLiteral, it builds a new path using the identifier converted to dash‑case (e.g., Button → button).
function updateImportNode(node: ts.Node, ctx: ts.TransformationContext) {
const visitor: ts.Visitor = node => {
if (kind.isNamedImports(node)) {
const identifierName = node.getChildAt(1).getText();
return ts.createIdentifier(identifierName);
}
if (kind.isStringLiteral(node)) {
const libName = node.getText().replace(/["']/g, '');
if (identifierName) {
const fileName = camel2Dash(identifierName);
return ts.createLiteral(`${libName}/lib/${fileName}`);
}
}
if (node.getChildCount()) {
return ts.visitEachChild(node, visitor, ctx);
}
return node;
};
}With this implementation, the transformer successfully rewrites the import as shown earlier. The article notes that this is a minimal version; a full‑featured solution would need to handle multiple imports, aliasing with as, and CSS imports.
References
TS transformer plugin example: https://github.com/Brooooooklyn/ts-import-plugin
TypeScript declaration file: https://github.com/Microsoft/TypeScript/blob/.../typescript.d.ts
AST Explorer: http://astexplorer.net
ts‑loader transformer docs: https://github.com/TypeStrong/ts-loader#getcustomtransformers
Full plugin source: https://github.com/newraina/learning-ts-transfomer-plugin
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Baixing.com Technical Team
A collection of the Baixing.com tech team's insights and learnings, featuring one weekly technical article worth following.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
