Skip to content

Instantly share code, notes, and snippets.

@stovberpv
Last active May 19, 2020 10:24
Show Gist options
  • Save stovberpv/c2a94a48642e25833e4b64c42c65b5c3 to your computer and use it in GitHub Desktop.
Save stovberpv/c2a94a48642e25833e4b64c42c65b5c3 to your computer and use it in GitHub Desktop.
TypeScript Paths Transformer with file watcher
{
"scripts": {
"dev": "ts-node scripts-dev.ts"
}
}
import { TypescriptCompiler } from '@poppinss/chokidar-ts';
import ts from 'typescript/lib/typescript';
import * as nodePath from 'path';
import * as nodeFs from 'fs';
const compiler = new TypescriptCompiler(process.cwd(), 'tsconfig.json', ts);
compiler.use(pathsTransformerFactory, 'before');
const { config } = compiler.configParser().parse(); // additional checks are needed here
const watcher = compiler.watcher(config!);
watcher.watch([...config.raw?.include, config.options.outDir], { ignored: config.raw?.exclude });
/**
*
* @param {ts} programm
* @param {ts.CompilerOptions} config
*
* @returns {ts.TransformerFactory<ts.SourceFile>}
*/
function pathsTransformerFactory(programm: typeof ts, config: ts.CompilerOptions): ts.TransformerFactory<ts.SourceFile> {
if (!config.paths) {
return;
}
/**
*
*/
type MappedPath = {
alias: string,
paths: Array<string>
};
/**
* TODO : refactor
*/
const paths: Array<MappedPath> =
Object.keys(config.paths).reduce((paths: Array<MappedPath>, alias: string) => {
const mappedPath: MappedPath = {
alias: alias.slice(0, -2),
paths: config.paths[alias].reduce((paths: Array<string>, path: string) => {
// trim endings '/*'
path = path.slice(0, -2);
const nomalizedPath: string = nodePath.normalize(process.cwd() + nodePath.sep + path);
paths.push(nomalizedPath);
return paths;
}, [])
};
paths.push(mappedPath);
return paths;
}, [])
;
/**
*
* @param {ts.ImportDeclaration} node
*
* @returns {string}
*/
function getModuleName(node: ts.ImportDeclaration): string {
if (!ts.isStringLiteral(node.moduleSpecifier)) {
return;
}
// getText returns string wrapped to single quotes
return node.moduleSpecifier.getText().slice(1, -1);
}
/**
*
* @param {string} moduleName
* @param {Array<MappedPath>} paths
*
* @returns {MappedPath}
*/
function resolvePath(moduleName: string, paths: Array<MappedPath>): MappedPath {
let resolvedMappedPath: MappedPath;
paths.some((mappedPath: MappedPath) => {
if (!moduleName.startsWith(mappedPath.alias)) {
return false;
}
return mappedPath.paths.some((path: string) => {
if (!nodeFs.existsSync(path)) {
return false;
}
resolvedMappedPath = {
alias: mappedPath.alias,
paths: [path]
};
return true;
});
});
return resolvedMappedPath;
}
/**
*
* @param {string} moduleName
* @param {MappedPath} resolvedMappedPath
*
* @returns {string}
*/
function buildResolvedModuleName(moduleName: string, resolvedMappedPath: MappedPath): string {
const searchValue: RegExp = new RegExp('^' + resolvedMappedPath.alias);
const replaceValue: string = resolvedMappedPath.paths[0];
const transformedPath: string = moduleName.replace(searchValue, replaceValue);
return nodePath.normalize(transformedPath);
}
/**
*
* @param {ts.ImportDeclaration} node
*
* @returns {ts.VisitResult<ts.Node>}
*/
function pathResolver(node: ts.ImportDeclaration): ts.VisitResult<ts.Node> {
const moduleName = getModuleName(node);
if (!moduleName) {
return;
}
const resolvedMappedPath: MappedPath = resolvePath(moduleName, paths);
if (!resolvedMappedPath) {
return;
}
const resolvedModuleName: string = buildResolvedModuleName(moduleName, resolvedMappedPath);
const newModuleSpecifier: ts.StringLiteral = ts.createStringLiteral(resolvedModuleName);
return ts.updateImportDeclaration(node, node.decorators, node.modifiers, node.importClause, newModuleSpecifier);
}
/**
*
* @param {ts.Node} node
*
* @returns {ts.VisitResult<ts.Node>}
*/
function visitor(node: ts.Node): ts.VisitResult<ts.Node> {
if (programm.isDecorator(node)) {
return;
}
if (programm.isImportDeclaration(node)) {
return pathResolver(node);
}
return programm.visitEachChild(node, visitor.bind(this), this);
}
/**
*
* @param {ts.SourceFile} sourceFile
*
* @returns {ts.SourceFile}
*/
function transformer(sourceFile: ts.SourceFile): ts.SourceFile {
return programm.visitNode(sourceFile, visitor.bind(this));
}
/**
*
* @param {ts.TransformationContext} context
*
* @returns {ts.Transformer<ts.SourceFile>}
*/
function transformerDecorator(context: ts.TransformationContext): ts.Transformer<ts.SourceFile> {
return transformer.bind(context);
}
return transformerDecorator;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment