Skip to content

Commit 4d98dca

Browse files
committed
Use TypeScript Compiler to fix paths
1 parent 16c168e commit 4d98dca

1 file changed

Lines changed: 75 additions & 54 deletions

File tree

config/fix-imports.js

Lines changed: 75 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,91 @@
1-
// Adapted from,
2-
// typescript-esm-example
3-
// By Andrey Sakharov
4-
// Original: https://github.com/muturgan/typescript-esm-example/blob/main/buildtools/fix-imports.js
5-
// MIT License: https://github.com/muturgan/typescript-esm-example/blob/main/LICENSE
6-
7-
import { extname, join } from 'path';
8-
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs';
9-
10-
const START_PATH = join(process.cwd(), 'dist/esm');
11-
const IMPORT_REGEXP = /^((import|export) [^';]* from "(\.\/|(\.\.\/)+)[^";]*)"/g;
12-
const JUST_ADD_AN_EXTENSION = `$1.js"`;
13-
const ADD_INDEX_FILE = `$1/index.js"`;
14-
const JS_EXT = '.js';
1+
import { existsSync, writeFileSync } from 'fs'
2+
import { dirname, join } from "path"
3+
import ts from 'typescript'
4+
const { createPrinter, isSourceFile, createCompilerHost, createProgram, isImportDeclaration, isExportDeclaration, isStringLiteral, ModuleKind, ModuleResolutionKind, ScriptTarget } = ts
155

166
/**
17-
* @param {string} rootPath
7+
*
8+
* @param {ts.SourceFile} sourceFile
9+
* @returns {ts.TransformerFactory<ts.Node>}
1810
*/
19-
function fixImportsAtFolder(rootPath) {
20-
const entries = readdirSync(rootPath);
11+
const generateTransformer = (sourceFile) => {
12+
const pathWithoutFileName = dirname(sourceFile.fileName);
2113

22-
entries.forEach((entry) => {
23-
const entryPath = join(rootPath, entry);
24-
if (entry.endsWith(JS_EXT)) {
25-
fixImportsAtFile(entryPath);
26-
}
27-
else {
28-
const extName = extname(entry);
29-
if (!extName) {
30-
const stat = statSync(entryPath);
31-
if (stat.isDirectory()) {
32-
fixImportsAtFolder(entryPath);
14+
return (/** @type {ts.TransformationContext} */context) => (/** @type {ts.Node} */rootNode) => {
15+
16+
function visit(/** @type {ts.Node} */node) {
17+
node = ts.visitEachChild(node, visit, context);
18+
19+
// Import Declaration
20+
const parentNodeIsExportOrImport = node.parent &&
21+
(isImportDeclaration(node.parent) || isExportDeclaration(node.parent))
22+
23+
// Node is the relative path of an import or export
24+
if (parentNodeIsExportOrImport && isStringLiteral(node)) {
25+
const relativePathWithQuotes = node.getFullText(sourceFile).trimStart()
26+
const relativePathWithoutQuotes = relativePathWithQuotes.substring(1, relativePathWithQuotes.length - 1)
27+
28+
if (relativePathWithoutQuotes.includes(".js")) {
29+
return node
30+
}
31+
32+
const fullImportPath = join(pathWithoutFileName, relativePathWithoutQuotes);
33+
34+
// Append .js or index.js to all imports
35+
if (existsSync(fullImportPath + ".js")) {
36+
node = context.factory.createStringLiteral(`${relativePathWithoutQuotes}.js`)
37+
}
38+
else if (existsSync(join(fullImportPath, "index.js"))) {
39+
node = context.factory.createStringLiteral(`${relativePathWithoutQuotes}/index.js`)
40+
}
41+
else {
42+
throw new Error(`Can't fix TypeScript paths: ${node.getFullText(sourceFile)}`)
3343
}
3444
}
45+
46+
return node
3547
}
36-
});
48+
49+
return ts.visitNode(rootNode, visit)
50+
}
3751
}
3852

3953
/**
40-
*
41-
* @param {string} filePath
54+
* Add .js extension to imports and exports
55+
* Specify index.js for folder imports
4256
*/
43-
function fixImportsAtFile(filePath) {
44-
const content = readFileSync(filePath).toString('utf8');
45-
const lines = content.split('\n');
46-
const fixedLines = lines.map((l) => {
47-
if (!l.match(IMPORT_REGEXP)) {
48-
return l;
49-
}
57+
const addJsToImportAndExports = () => {
58+
/** @type {ts.CompilerOptions} */
59+
const compilerOptions = {
60+
moduleResolution: ModuleResolutionKind.NodeJs,
61+
module: ModuleKind.NodeNext,
62+
esModuleInterop: true,
63+
target: ScriptTarget.ESNext,
64+
lib: [
65+
"es2020"
66+
],
67+
allowJs: true,
68+
checkJs: true
69+
};
5070

51-
const [_, importPath] = l.split(`"`);
52-
const fullPath = join(filePath, '..', importPath);
53-
const exists = existsSync(fullPath);
54-
if (exists === false) {
55-
return l.replace(IMPORT_REGEXP, JUST_ADD_AN_EXTENSION);
56-
}
71+
const host = createCompilerHost(compilerOptions);
72+
const program = createProgram(["./dist/esm/index.js"], compilerOptions, host);
73+
const sourceFiles = program.getSourceFiles();
5774

58-
const stat = statSync(fullPath);
59-
const isDirectory = stat.isDirectory();
60-
if (isDirectory === true) {
61-
return l.replace(IMPORT_REGEXP, ADD_INDEX_FILE);
75+
for (const sourceFile of sourceFiles) {
76+
if (!sourceFile.isDeclarationFile) {
77+
const result = ts.transform(
78+
sourceFile, [generateTransformer(sourceFile)]
79+
)
80+
81+
const transformedSourceFile = result.transformed[0]
82+
if (isSourceFile(transformedSourceFile)) {
83+
const printer = createPrinter()
84+
const file = printer.printFile(transformedSourceFile)
85+
writeFileSync(sourceFile.fileName, file)
86+
}
6287
}
63-
64-
return l;
65-
});
66-
const withFixedImports = fixedLines.join('\n');
67-
writeFileSync(filePath, withFixedImports);
88+
}
6889
}
6990

70-
fixImportsAtFolder(START_PATH);
91+
addJsToImportAndExports()

0 commit comments

Comments
 (0)