|
| 1 | +const t = require("@babel/types"); |
| 2 | +const ospath = require("path"); |
| 3 | +const fs = require("fs"); |
| 4 | +const AST = require("./ast"); |
| 5 | + |
| 6 | +class PathFunctions { |
| 7 | + static isRelativePath(path) { |
| 8 | + return path.match(/^\.{0,2}\//); |
| 9 | + } |
| 10 | + |
| 11 | + static isLocalModule(importModulePath) { |
| 12 | + try { |
| 13 | + return !!require.resolve(importModulePath) && !importModulePath.includes("node_modules"); |
| 14 | + } catch { |
| 15 | + return false; |
| 16 | + } |
| 17 | + } |
| 18 | + |
| 19 | + static isScriptFile(importModulePath) { |
| 20 | + return importModulePath.match(/\.(js|mjs|jsx|ts|tsx)$/); |
| 21 | + } |
| 22 | + |
| 23 | + static getBaseUrlFromTsconfig() { |
| 24 | + try { |
| 25 | + const filename = ospath.resolve("jsconfig.json"); |
| 26 | + const content = JSON.parse(fs.readFileSync(filename, "utf-8")); |
| 27 | + return content?.["compilerOptions"]?.["baseUrl"] || "./"; |
| 28 | + } catch (error) { |
| 29 | + throw error; |
| 30 | + } |
| 31 | + } |
| 32 | + |
| 33 | + static getModuleFile(filenameImportFrom, modulePath) { |
| 34 | + // solution for require function for ES modules |
| 35 | + // https://stackoverflow.com/questions/54977743/do-require-resolve-for-es-modules |
| 36 | + // https://stackoverflow.com/a/50053801 |
| 37 | + // import { createRequire } from "module"; |
| 38 | + // const require = createRequire(import.meta.url); |
| 39 | + try { |
| 40 | + const filenameDir = ospath.dirname(filenameImportFrom); |
| 41 | + const basePath = PathFunctions.isRelativePath(modulePath) ? |
| 42 | + filenameDir : ospath.resolve(PathFunctions.getBaseUrlFromTsconfig()); |
| 43 | + return require.resolve(ospath.resolve(basePath, modulePath)); |
| 44 | + } catch { |
| 45 | + try { |
| 46 | + return require.resolve(modulePath); |
| 47 | + } catch { |
| 48 | + return "MODULE_NOT_FOUND"; |
| 49 | + } |
| 50 | + } |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | + |
| 55 | +class BarrelFilesMapping { |
| 56 | + constructor() { |
| 57 | + this.mapping = {}; |
| 58 | + } |
| 59 | + |
| 60 | + static isBarrelFile(modulePath) { |
| 61 | + return modulePath.endsWith("index.js"); |
| 62 | + } |
| 63 | + |
| 64 | + verifyFilePath (importModuleAbsolutePath) { |
| 65 | + return !BarrelFilesMapping.isBarrelFile(importModuleAbsolutePath) || !PathFunctions.isLocalModule(importModuleAbsolutePath) || !PathFunctions.isScriptFile(importModuleAbsolutePath) |
| 66 | + } |
| 67 | + |
| 68 | + createSpecifiersMapping(fullPathModule) { |
| 69 | + const barrelAST = AST.filenameToAST(fullPathModule); |
| 70 | + this.mapping[fullPathModule] = {}; |
| 71 | + barrelAST.program.body.forEach((node) => { |
| 72 | + if (t.isExportNamedDeclaration(node)) { |
| 73 | + const originalExportedPath = node.source?.value || fullPathModule; |
| 74 | + const absoluteExportedPath = node.source?.value ? PathFunctions.getModuleFile(fullPathModule, originalExportedPath) : fullPathModule; |
| 75 | + node.specifiers.forEach((specifier) => { |
| 76 | + const specifierName = specifier.exported.name; |
| 77 | + const specifierType = AST.getSpecifierType(specifier); |
| 78 | + this.mapping[fullPathModule][specifierName] = |
| 79 | + this.createDirectSpecifierObject(absoluteExportedPath, specifierName, specifierType); |
| 80 | + }); |
| 81 | + if (t.isVariableDeclaration(node.declaration)) { |
| 82 | + const specifierType = "named"; |
| 83 | + node.declaration.declarations.forEach((declaration) => { |
| 84 | + const specifierName = declaration.id.name; |
| 85 | + this.mapping[fullPathModule][specifierName] = |
| 86 | + this.createDirectSpecifierObject(absoluteExportedPath, specifierName, specifierType); |
| 87 | + }); |
| 88 | + } else if (t.isFunctionDeclaration(node.declaration)) { |
| 89 | + const specifierType = "named"; |
| 90 | + const specifierName = node.declaration.id.name; |
| 91 | + this.mapping[fullPathModule][specifierName] = |
| 92 | + this.createDirectSpecifierObject(absoluteExportedPath, specifierName, specifierType); |
| 93 | + } |
| 94 | + } else if (t.isExportAllDeclaration(node)) { |
| 95 | + const originalExportedPath = node.source.value; |
| 96 | + const absoluteExportedPath = PathFunctions.getModuleFile(fullPathModule, originalExportedPath); |
| 97 | + if (!this.mapping[absoluteExportedPath]) { |
| 98 | + this.createSpecifiersMapping(absoluteExportedPath); |
| 99 | + } |
| 100 | + Object.assign(this.mapping[fullPathModule],this.mapping[absoluteExportedPath]); |
| 101 | + } |
| 102 | + }); |
| 103 | + } |
| 104 | + |
| 105 | + createDirectSpecifierObject(fullPathModule, specifierName, specifierType) { |
| 106 | + if (BarrelFilesMapping.isBarrelFile(fullPathModule)) { |
| 107 | + if (!this.mapping[fullPathModule]) { |
| 108 | + this.createSpecifiersMapping(fullPathModule); |
| 109 | + } |
| 110 | + const originalPath = this.mapping[fullPathModule][specifierName]["path"]; |
| 111 | + const originalName = this.mapping[fullPathModule][specifierName]["name"]; |
| 112 | + const originalType = this.mapping[fullPathModule][specifierName]["type"]; |
| 113 | + return this.createDirectSpecifierObject(originalPath, originalName, originalType); |
| 114 | + } |
| 115 | + return { |
| 116 | + name: specifierName, |
| 117 | + path: fullPathModule, |
| 118 | + type: specifierType, |
| 119 | + }; |
| 120 | + } |
| 121 | + |
| 122 | + getDirectSpecifierObject(fullPathModule, specifierName) { |
| 123 | + if (!this.mapping[fullPathModule]) { |
| 124 | + this.createSpecifiersMapping(fullPathModule); |
| 125 | + } |
| 126 | + return this.mapping[fullPathModule][specifierName]; |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +const mapping = new BarrelFilesMapping(); |
| 131 | + |
| 132 | +const importDeclarationVisitor = (path, state) => { |
| 133 | + const parsedJSFile = state.filename |
| 134 | + const originalImportsPath = path.node.source.value; |
| 135 | + const originalImportsSpecifiers = path.node.specifiers; |
| 136 | + // const importModulePath = resolve.sync(originalImports.source.value,{basedir: ospath.dirname(state.filename)}); |
| 137 | + const importModuleAbsolutePath = PathFunctions.getModuleFile(parsedJSFile, originalImportsPath); |
| 138 | + if (mapping.verifyFilePath(importModuleAbsolutePath)) return; |
| 139 | + const directSpecifierASTArray = originalImportsSpecifiers.map( |
| 140 | + (specifier) => { |
| 141 | + const directSpecifierObject = mapping.getDirectSpecifierObject( |
| 142 | + importModuleAbsolutePath, |
| 143 | + specifier.imported.name |
| 144 | + ); |
| 145 | + return AST.createASTImportDeclaration(directSpecifierObject); |
| 146 | + } |
| 147 | + ); |
| 148 | + path.replaceWithMultiple(directSpecifierASTArray); |
| 149 | +}; |
| 150 | + |
| 151 | +module.exports = function (babel) { |
| 152 | + return { |
| 153 | + visitor: { |
| 154 | + ImportDeclaration: importDeclarationVisitor, |
| 155 | + }, |
| 156 | + }; |
| 157 | +}; |
0 commit comments