Last active
May 19, 2020 10:24
-
-
Save stovberpv/c2a94a48642e25833e4b64c42c65b5c3 to your computer and use it in GitHub Desktop.
TypeScript Paths Transformer with file watcher
This file contains hidden or 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
{ | |
"scripts": { | |
"dev": "ts-node scripts-dev.ts" | |
} | |
} |
This file contains hidden or 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
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