Last active
August 29, 2019 12:34
-
-
Save AviVahl/40e031bd72c7264890f349020d04130a to your computer and use it in GitHub Desktop.
TypeScript transformer to remove .js from import and export statements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 } } | |
] | |
} | |
}; |
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!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@AviVahl no no, you understood correctly.
I've managed to code this piece, which deletes imports included in an inputted array.
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.