-
-
Save AviVahl/40e031bd72c7264890f349020d04130a to your computer and use it in GitHub Desktop.
// https://github.com/Microsoft/TypeScript/issues/16577#issuecomment-343699395 | |
const path = require('path'); | |
const ts = require('typescript'); | |
const { isImportDeclaration, isExportDeclaration, isStringLiteral } = require('tsutils/typeguard/node'); | |
function getCustomTransformers() { | |
return { before: [stripJsExt] } | |
function stripJsExt(context) { | |
return sourceFile => visitNode(sourceFile); | |
function visitNode(node) { | |
if ((isImportDeclaration(node) || isExportDeclaration(node)) && | |
node.moduleSpecifier && isStringLiteral(node.moduleSpecifier)) { | |
const targetModule = node.moduleSpecifier.text; | |
if (targetModule.endsWith('.js')) { | |
const newTarget = targetModule.slice(0, targetModule.length - 3); | |
return isImportDeclaration(node) ? | |
ts.updateImportDeclaration( | |
node, | |
node.decorators, | |
node.modifiers, | |
node.importClause, | |
ts.createLiteral(newTarget) | |
) : | |
ts.updateExportDeclaration( | |
node, | |
node.decorators, | |
node.modifiers, | |
node.exportClause, | |
ts.createLiteral(newTarget) | |
); | |
} | |
} | |
return ts.visitEachChild(node, visitNode, context); | |
} | |
} | |
} | |
module.exports = { | |
entry: './src/index.ts', | |
output: { | |
path: path.resolve(__dirname, 'dist'), | |
filename: 'app.bundle.js' | |
}, | |
resolve: { | |
extensions: ['.ts', '.tsx', '.js', '.json'] | |
}, | |
module: { | |
rules: [ | |
{ test: /\.tsx?$/, loader: 'ts-loader', options: { getCustomTransformers } } | |
] | |
} | |
}; |
@lppedd do you mean removing a single imported value? like One or Two? that would require updating the importClause
.
or did I miss-understand your question?
On a side note: a useful AST viewer to understand which nodes are involved:
https://ts-ast-viewer.com
@AviVahl no no, you understood correctly.
I've managed to code this piece, which deletes imports included in an inputted array.
if (ts.isNamedImports(node)) {
const namedImports = node as ts.NamedImports;
const newElements = namedImports.elements.filter(
v => !decoratorNames.includes(v.name.text)
);
ts.updateNamedImports(namedImports, newElements);
}
Seems good to you?
I'd also like to entirely remove the import if newElements.length === 0
, but I wasn't able to do it.
I've managed to do it, but maybe there is a cleaner way. Basically I save the ImportDeclaration
reference and later I look for it.
The complete code is
export default (decorators: string[]) => {
const importDeclarationsToRemove = [] as ts.ImportDeclaration[];
const updateNamedImports = (node: ts.NamedImports) => {
const newElements = node.elements.filter(v => !decorators.includes(v.name.getText()));
if (newElements.length > 0) {
ts.updateNamedImports(node, newElements);
} else {
importDeclarationsToRemove.push(node.parent.parent);
}
};
const createVisitor = (
context: ts.TransformationContext
): ((node: ts.Node) => ts.VisitResult<ts.Node>) => {
const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult<any> => {
// Remove Decorators from imports
if (ts.isNamedImports(node)) {
updateNamedImports(node);
}
// Remove Decorators applied to elements
if (ts.isDecorator(node)) {
const decorator = node as ts.Decorator;
const identifier = decorator.getChildAt(1) as ts.Identifier;
if (decorators.includes(identifier.getText())) {
return undefined;
}
}
const resultNode = ts.visitEachChild(node, visitor, context);
const index = importDeclarationsToRemove.findIndex(id => id === resultNode);
if (index !== -1) {
importDeclarationsToRemove.splice(index, 1);
return undefined;
}
return resultNode;
};
return visitor;
};
return (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) =>
sourceFile.fileName.endsWith('component.ts')
? ts.visitNode(sourceFile, createVisitor(context))
: sourceFile;
};
you can update the sourceFile's statements
field... filtering out top level imports/exports you want to filter.
I've got several cool transformers (in several patterns) implemented here: https://github.com/AviVahl/ts-tools/tree/master/packages/robotrix
might be useful for reference.
@AviVahl Thanks! So you're saying I can simply .statements.filter(...)
inside the visitor?
you need to ts.updateSourceFile
with the filtered statements, but yes.
@AviVahl understood. Thanks again for the useful advices!
@AviVahl Thanks for the example. What about removing a single import in, e.g.