From 1c2a0b2402a6df1d26e40233dbf2d52d9fbaa914 Mon Sep 17 00:00:00 2001 From: Ori Date: Tue, 21 May 2024 15:52:08 +0300 Subject: [PATCH] add barrel file analysis for third-party packages --- README.md | 4 + package.json | 14 +- src/barrel.js | 402 ++++++++++++++++++++++++++++-------------------- src/main.js | 48 ++++++ src/packages.js | 101 ++++++++++++ src/path.js | 92 +++++++++-- 6 files changed, 480 insertions(+), 181 deletions(-) create mode 100644 src/main.js create mode 100644 src/packages.js diff --git a/README.md b/README.md index 1a22612..f46efed 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ # babel-plugin-transform-barrels + +[![npm](https://badgen.net/npm/v/babel-plugin-transform-barrels)](https://www.npmjs.com/package/babel-plugin-transform-barrels) +[![downloads](https://badgen.net/npm/dt/babel-plugin-transform-barrels)](https://www.npmjs.com/package/babel-plugin-transform-barrels) + This Babel plugin transforms indirect imports through a barrel file (index.js) into direct imports. ### Note diff --git a/package.json b/package.json index 0fe98d4..a795be3 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,19 @@ { "name": "babel-plugin-transform-barrels", - "version": "1.0.12", + "version": "1.0.13", "description": "This Babel plugin transforms indirect imports through a barrel file (index.js) into direct imports.", "homepage": "https://github.com/FogelAI/babel-plugin-transform-barrels", - "main": "src/barrel.js", + "main": "src/main.js", + "keywords": [ + "barrel", + "babel", + "plugin", + "transform", + "import", + "webpack", + "jest", + "index" + ], "repository": { "type": "git", "url": "FogelAI/babel-plugin-transform-barrels" diff --git a/src/barrel.js b/src/barrel.js index a8ea7cc..702e775 100644 --- a/src/barrel.js +++ b/src/barrel.js @@ -1,191 +1,261 @@ +const ospath = require("path"); const t = require("@babel/types"); const AST = require("./ast"); const PathFunctions = require("./path"); -const { PackageJson, WebpackConfig, JestConfig } = require("./alias"); +const resolver = require("./resolver"); +const packageManager = require("./packages"); -class BarrelFilesMapping { - constructor() { - this.mapping = {}; - } +class BarrelFile { + constructor(path) { + this.path = path; + this.exportMapping = {}; + this.importMapping = {}; + } - isBarrelFile(modulePath) { - const isBarrelFilename = (modulePath) => { + static isBarrelFilename(path) { const barrelFileRegex = new RegExp(`index\.(js|mjs|jsx|ts|tsx)$`); - return barrelFileRegex.test(modulePath); - } - const isScannedBarrelFilename = (modulePath) => { - return !!this.mapping[modulePath]; - } - const isBarrelFileContent = (modulePath) => { - return !PathFunctions.isObjectEmpty(this.mapping[modulePath]); - } - if (!isBarrelFilename(modulePath)) return false; - if (!isScannedBarrelFilename(modulePath)) { - this.createSpecifiersMapping(modulePath); - }; - return isBarrelFileContent(modulePath); - } - - createSpecifiersMapping(fullPathModule, forceFullScan = false) { - const barrelAST = AST.filenameToAST(fullPathModule); - this.mapping[fullPathModule] = {}; - const imports = {}; - barrelAST.program.body.every((node) => { - const originalExportedPath = node.source?.value || fullPathModule; - const convertedExportedPath = webpackConfig.convertAliasToOriginal(fullPathModule, originalExportedPath); - let absoluteExportedPath = PathFunctions.getModuleAbsolutePath(fullPathModule, convertedExportedPath); - if (t.isExportNamedDeclaration(node)) { - node.specifiers.forEach((specifier) => { - const specifierExportedName = specifier.exported.name; - let specifierLocalName = specifier?.local?.name; - let specifierType = AST.getSpecifierType(specifier); - // if node.source exist -> export { abc } from './abc'; - if (!node.source) { - // if node.source doesnt exist -> export { abc }; - if (specifierLocalName in imports) { - absoluteExportedPath = imports[specifierLocalName]["path"]; - specifierType = imports[specifierLocalName]["type"]; - specifierLocalName = imports[specifierLocalName]["importedName"]; + return barrelFileRegex.test(path); + } + + get isBarrelFileContent() { + return !PathFunctions.isObjectEmpty(this.exportMapping); + } + + handleExportNamedDeclaration(node) { + if (node.specifiers.length > 0) { + node.specifiers.forEach((specifier) => { + let specifierObj = SpecifierFactory.createSpecifier("export"); + if (node.source) { + // if node.source exist -> export { abc } from './abc'; + specifierObj.exportedName = specifier.exported.name; + specifierObj.localName = specifier?.local?.name; + specifierObj.type = AST.getSpecifierType(specifier); + const exportPath = node.source.value; + specifierObj.esmPath = resolver.resolve(exportPath, this.path).absEsmFile; + } else { + // if node.source doesnt exist -> export { abc }; + const localName = specifier?.local?.name; + if (localName in this.importMapping) { + specifierObj = this.importMapping[localName].toExportSpecifier(); + specifierObj.exportedName = specifier.exported.name; + } } - } - this.mapping[fullPathModule][specifierExportedName] = - this.createDirectSpecifierObject(absoluteExportedPath, specifierExportedName, specifierLocalName, specifierType); - }); - if (node.declaration && !forceFullScan) { - this.mapping[fullPathModule] = {}; - return false; - } + const { exportedName } = specifierObj; + this.exportMapping[exportedName] = this.getDeepestDirectSpecifierObject(specifierObj); + }); + }; if (node.declaration) { - const specifierType = "named"; + const specifierObj = SpecifierFactory.createSpecifier("export"); + specifierObj.type = "named"; + specifierObj.esmPath = this.path; const declarations = node.declaration.declarations || [node.declaration]; // if declaration exists -> export function abc(){}; // if declaration.declarations exists -> export const abc = 5, def = 10; declarations.forEach((declaration) => { - const specifierName = declaration.id.name; - this.mapping[fullPathModule][specifierName] = - this.createDirectSpecifierObject(absoluteExportedPath, specifierName, specifierName, specifierType); + specifierObj.localName = declaration.id.name; + specifierObj.exportedName = declaration.id.name; + const { exportedName } = specifierObj; + this.exportMapping[exportedName] = this.getDeepestDirectSpecifierObject(specifierObj); }); } - } else if (t.isExportDefaultDeclaration(node)) { + } + + handleExportDefaultDeclaration(node) { // export default abc; if (node.declaration.name) { - let specifierLocalName = node.declaration.name; - if (specifierLocalName in imports) { - const specifierType = imports[specifierLocalName]["type"]; - const specifierExportedName = "default"; - const absoluteExportedPath = imports[specifierLocalName]["path"]; - specifierLocalName = imports[specifierLocalName]["importedName"]; - this.mapping[fullPathModule][specifierExportedName] = - this.createDirectSpecifierObject(absoluteExportedPath, specifierExportedName, specifierLocalName, specifierType); + const localName = node.declaration.name; + if (localName in this.importMapping) { + const specifierObj = this.importMapping[localName].toExportSpecifier(); + specifierObj.exportedName = "default"; + const { exportedName } = specifierObj; + this.exportMapping[exportedName] = this.getDeepestDirectSpecifierObject(specifierObj); } } - } else if (t.isExportAllDeclaration(node)) { + } + + handleExportAllDeclaration(node) { // export * from './abc'; - if (!this.mapping[absoluteExportedPath]) { - this.createSpecifiersMapping(absoluteExportedPath, true); - } - Object.assign(this.mapping[fullPathModule],this.mapping[absoluteExportedPath]); - delete this.mapping[absoluteExportedPath]; - } else if (t.isImportDeclaration(node)) { - if (!AST.isAnySpecifierExist(node.specifiers) && !forceFullScan) { - // import './abc'; - this.mapping[fullPathModule] = {}; - return false; - } + const exportPath = node.source.value; + let absoluteExportedPath = resolver.resolve(exportPath, this.path).absEsmFile; + const exportedAllFile = new BarrelFile(absoluteExportedPath); + exportedAllFile.createSpecifiersMapping(true); + Object.assign(this.exportMapping, exportedAllFile.exportMapping); + } + + handleImportDeclaration(node) { node.specifiers.forEach((specifier) => { - // import {abc, def} from './abc'; - const specifierImportedName = specifier?.imported?.name; - const specifierLocalName = specifier?.local?.name; - const specifierType = AST.getSpecifierType(specifier); - const originalExportedPath = node.source.value; - const convertedExportedPath = webpackConfig.convertAliasToOriginal(fullPathModule, originalExportedPath); - const absoluteExportedPath = PathFunctions.getModuleAbsolutePath(fullPathModule, convertedExportedPath); - imports[specifierLocalName] = { - importedName: specifierImportedName, - localName: specifierLocalName, - path: absoluteExportedPath, - type: specifierType, - }; + // import {abc, def} from './abc'; + const specifierObj = SpecifierFactory.createSpecifier("import"); + specifierObj.importedName = specifier?.imported?.name; + specifierObj.localName = specifier.local.name; + specifierObj.type = AST.getSpecifierType(specifier); + const importPath = node.source.value; + specifierObj.esmPath = resolver.resolve(importPath, this.path).absEsmFile; + const { localName } = specifierObj; + this.importMapping[localName] = specifierObj; }); - } else { - if (forceFullScan) { + } + + createSpecifiersMapping(forceFullScan = false) { + const barrelAST = AST.filenameToAST(this.path); + barrelAST.program.body.every((node) => { + if (t.isExportNamedDeclaration(node)) { + // export { abc } from './abc'; + // export { abc }; + // export function abc(){}; + // export const abc = 5, def = 10; + if (node.declaration && !forceFullScan) { + this.exportMapping = {}; + return false; + } + this.handleExportNamedDeclaration(node); + } else if (t.isExportDefaultDeclaration(node)) { + // export default abc; + this.handleExportDefaultDeclaration(node); + } else if (t.isExportAllDeclaration(node)) { + // export * from './abc'; + this.handleExportAllDeclaration(node); + } else if (t.isImportDeclaration(node)) { + if (!AST.isAnySpecifierExist(node.specifiers) && !forceFullScan) { + // import './abc'; + this.exportMapping = {}; + return false; + } + // import {abc, def} from './abc'; + this.handleImportDeclaration(node); + } else { + if (forceFullScan) { + return true; + } else { + this.exportMapping = {}; + return false; + } + } return true; - } else { - this.mapping[fullPathModule] = {}; - return false; + }); + this.path = PathFunctions.normalizeModulePath(this.path); + } + + getDeepestDirectSpecifierObject(specifierObj) { + const { esmPath, localName } = specifierObj; + if (BarrelFile.isBarrelFilename(esmPath)) { + const barrelFile = BarrelFileManager.getBarrelFile(esmPath); + if (barrelFile.isBarrelFileContent) { + const deepestSpecifier = barrelFile.getDirectSpecifierObject(localName); + return this.getDeepestDirectSpecifierObject(deepestSpecifier); + } + } + specifierObj.esmPath = PathFunctions.normalizeModulePath(specifierObj.esmPath); + return specifierObj; + } + + getDirectSpecifierObject(specifierExportedName) { + return this.exportMapping[specifierExportedName]; + } +} + +class BarrelFileManager { + constructor() { + this.barrelFiles = new Map(); + } + + getBarrelFileInner(path) { + let barrelFile = new BarrelFile(path); + if (BarrelFile.isBarrelFilename(path)) { + const barrelKeyName = PathFunctions.normalizeModulePath(path); + if (!this.barrelFiles.has(barrelKeyName)) { + barrelFile.createSpecifiersMapping(); + this.barrelFiles.set(barrelKeyName, barrelFile); + } + barrelFile = this.barrelFiles.get(barrelKeyName); + }; + return barrelFile; + } + + static getBarrelFile(path) { + if (!BarrelFile.isBarrelFilename(path)) return new BarrelFile(); + const packageObj = packageManager.getMainPackageOfModule(path, new BarrelFileManager()); + const barrelFile = packageObj.barrelFileManager.getBarrelFileInner(path); + return barrelFile; + } +} + +class SpecifierFactory { + static createSpecifier(type) { + switch (type) { + case 'export': + return new ExportSpecifier(); + case 'import': + return new ImportSpecifier(); + default: + throw new Error('Invalid specifier type'); + } + } +} + +class ExportSpecifier { + constructor() { + this.esmPath = ""; + this.exportedName = ""; + this.localName = ""; + this.type = ""; + } + + toImportSpecifier() { + const specifierObj = SpecifierFactory.createSpecifier("import"); + specifierObj.type = this.type; + specifierObj.esmPath = this.esmPath; + if (!PathFunctions.isNodeModule(specifierObj.esmPath)) { + specifierObj.esmPath = ospath.join(process.cwd(), specifierObj.esmPath); } - } - return true; - }); - } - - createDirectSpecifierObject(fullPathModule, specifierExportedName, specifierLocalName, specifierType) { - if (this.isBarrelFile(fullPathModule)) { - const originalPath = this.mapping[fullPathModule][specifierLocalName]["path"]; - const originalExportedName = this.mapping[fullPathModule][specifierLocalName]["exportedName"]; - const originalLocalName = this.mapping[fullPathModule][specifierLocalName]["localName"]; - const originalType = this.mapping[fullPathModule][specifierLocalName]["type"]; - return this.createDirectSpecifierObject(originalPath, originalExportedName, originalLocalName, originalType); - } - return { - exportedName: specifierExportedName, - localName: specifierLocalName, - path: fullPathModule, - type: specifierType, - }; - } - - getDirectSpecifierObject(fullPathModule, specifierExportedName) { - return this.mapping[fullPathModule][specifierExportedName]; - } + specifierObj.importedName = this.localName; + return specifierObj; + } + + get absEsmPath() { + return PathFunctions.getAbsolutePath(this.esmPath, resolver.from); + } + + get cjsPath() { + const packageObj = packageManager.getMainPackageOfModule(this.absEsmPath, new BarrelFileManager()); + return packageObj.convertESMToCJSPath(this.esmPath); + } + + get path() { + const packageObj = packageManager.packages.get("."); + return packageObj.type === "commonjs" ? this.cjsPath : this.esmPath; + } +} + +class ImportSpecifier { + constructor() { + this.esmPath = ""; + this.importedName = ""; + this.localName = ""; + this.type = ""; + } + + toExportSpecifier() { + const specifierObj = SpecifierFactory.createSpecifier("export"); + specifierObj.type = this.type; + specifierObj.esmPath = this.esmPath; + specifierObj.localName = this.importedName; + return specifierObj; + } + + get absEsmPath() { + return PathFunctions.getAbsolutePath(this.esmPath, resolver.from); + } + + get cjsPath() { + const packageObj = packageManager.getMainPackageOfModule(this.absEsmPath, new BarrelFileManager()); + return packageObj.convertESMToCJSPath(this.esmPath); + } + + get path() { + const packageObj = packageManager.packages.get("."); + return packageObj.type === "commonjs" ? this.cjsPath : this.esmPath; + } } -const mapping = new BarrelFilesMapping(); -const webpackConfig = new WebpackConfig(); -const jestConfig = new JestConfig(); -const packageJsonConfig = new PackageJson(); - -const importDeclarationVisitor = (path, state) => { - const originalImportsSpecifiers = path.node.specifiers; - if (!AST.isAnySpecifierExist(originalImportsSpecifiers)) return; - if (AST.getSpecifierType(originalImportsSpecifiers[0]) === "namespace") return; - const parsedJSFile = state.filename - const originalImportsPath = path.node.source.value; - const convertedImportsPath = webpackConfig.convertAliasToOriginal(parsedJSFile, originalImportsPath); - if (PathFunctions.checkIfModule(convertedImportsPath)) return; - const importModuleAbsolutePath = PathFunctions.getModuleAbsolutePath(parsedJSFile, convertedImportsPath); - if (!mapping.isBarrelFile(importModuleAbsolutePath)) return; - const directSpecifierASTArray = originalImportsSpecifiers.map( - (specifier) => { - const directSpecifierObject = mapping.getDirectSpecifierObject( - importModuleAbsolutePath, - specifier?.imported?.name || "default" - ); - const newImportProperties = { - localName: specifier.local.name, - importedName: directSpecifierObject["localName"], - path: directSpecifierObject["path"], - type: directSpecifierObject["type"], - } - return AST.createASTImportDeclaration(newImportProperties); - } - ); - path.replaceWithMultiple(directSpecifierASTArray); -}; - -module.exports = function (babel) { - const PLUGIN_KEY = 'transform-barrels'; - return { - name: PLUGIN_KEY, - pre(state) { - const plugins = state.opts.plugins; - const plugin = plugins.find(plugin => plugin.key === PLUGIN_KEY); - webpackConfig.getWebpackAlias(plugin); - webpackConfig.appendAlias(packageJsonConfig.getAlias()); - webpackConfig.appendAlias(jestConfig.getJestAlias(plugin)); - }, - visitor: { - ImportDeclaration: importDeclarationVisitor, - }, - }; -}; +module.exports = BarrelFileManager; \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..7723f9c --- /dev/null +++ b/src/main.js @@ -0,0 +1,48 @@ +const AST = require("./ast"); +const AliasFactory = require("./alias"); +const resolver = require("./resolver"); +const BarrelFileManager = require("./barrel"); + +const importDeclarationVisitor = (path, state) => { + const importsSpecifiers = path.node.specifiers; + if (!AST.isAnySpecifierExist(importsSpecifiers)) return; + if (AST.getSpecifierType(importsSpecifiers[0]) === "namespace") return; + const parsedJSFile = state.filename + const importsPath = path.node.source.value; + resolver.from = parsedJSFile; + const resolvedPathObject = resolver.resolve(importsPath ,parsedJSFile); + const barrelFile = BarrelFileManager.getBarrelFile(resolvedPathObject.absEsmFile); + if (!barrelFile.isBarrelFileContent) return; + const directSpecifierASTArray = importsSpecifiers.map((specifier) => + { + const importedName = specifier?.imported?.name || "default"; + const importSpecifier = barrelFile.getDirectSpecifierObject(importedName).toImportSpecifier(); + importSpecifier.localName = specifier.local.name; + return AST.createASTImportDeclaration(importSpecifier); + } + ); + path.replaceWithMultiple(directSpecifierASTArray); +}; + +module.exports = function (babel) { + const PLUGIN_KEY = 'transform-barrels'; + return { + name: PLUGIN_KEY, + pre(state) { + const plugins = state.opts.plugins; + const plugin = plugins.find(plugin => plugin.key === PLUGIN_KEY); + const aliasJest = AliasFactory.createAlias("jest", plugin.options); + const aliasWebpack = AliasFactory.createAlias("webpack", plugin.options); + const aliasWorkspaces = AliasFactory.createAlias("workspaces"); + const alias = { + ...aliasWebpack.getAlias(), + ...aliasWorkspaces.getAlias(), + ...aliasJest.getAlias() + } + resolver.appendAlias(alias); + }, + visitor: { + ImportDeclaration: importDeclarationVisitor, + }, + }; +}; diff --git a/src/packages.js b/src/packages.js new file mode 100644 index 0000000..f8b6b49 --- /dev/null +++ b/src/packages.js @@ -0,0 +1,101 @@ +const ospath = require("path"); +const PathFunctions = require("./path"); + +class Package { + constructor(path, barrelFileManager) { + this.path = path; + this.name = this.getHighestPackageName(); + this.data; + this.barrelFileManager = barrelFileManager; + } + + static getHighestParentPackageDir(path) { + if (!PathFunctions.isNodeModule(path)) return process.cwd(); + let highestPackageJsonDir; + let currentDir = path; + let lastSegment; + while (currentDir && lastSegment!=="node_modules") { + const packagePath = ospath.join(currentDir, "package.json"); + if (PathFunctions.fileExists(packagePath)) { + highestPackageJsonDir = currentDir; + } + currentDir = currentDir.substring(0, currentDir.lastIndexOf(ospath.sep)); + lastSegment = currentDir.split(ospath.sep).pop(); + } + return highestPackageJsonDir; + } + + static getMainPackageJSON(path) { + const HighestParentPackageDir = Package.getHighestParentPackageDir(path); + const HighestParentPackagePath = ospath.join(HighestParentPackageDir, "package.json"); + return HighestParentPackagePath; + } + + removeNodeModulesPath(path) { + const PackageDirName = path.replace(/.:.+node_modules[\/|\\]/g,""); + return PackageDirName; + } + + getHighestPackageName() { + if (!PathFunctions.isNodeModule(this.path)) return "."; + const HighestParentPackageDir = Package.getHighestParentPackageDir(this.path); + return this.removeNodeModulesPath(HighestParentPackageDir); + } + + get cjsEntry() { + return this.data.main && ospath.join(this.name, this.data.main); + } + + get esmEntry() { + return this.data.module && ospath.join(this.name, this.data.module); + } + + get cjsFolder() { + return this.cjsEntry && ospath.dirname(this.cjsEntry) + } + + get esmFolder() { + return this.esmEntry && ospath.dirname(this.esmEntry) + } + + get type() { + return this.data.type || "commonjs"; + } + + convertESMToCJSPath(path) { + return path.replace(this.esmFolder, this.cjsFolder); + } + + load() { + const packageJsonObj = require(this.path); + this.data = packageJsonObj; + } +} + +let instance; + +class PackageManager { + constructor() { + this.packages = new Map(); + if (instance) { + throw new Error("You can only create one instance!"); + } + instance = this; + } + + getMainPackageOfModule(modulePath, barrelFileManager) { + const moduleDir = ospath.dirname(modulePath) + const mainPackageJSONFile = Package.getMainPackageJSON(moduleDir); + let packageObj = new Package(mainPackageJSONFile, barrelFileManager); + if (!this.packages.has(packageObj.name)) { + packageObj.load(); + this.packages.set(packageObj.name, packageObj); + } + packageObj = this.packages.get(packageObj.name); + return packageObj; + }; +} + +const singletonPackageManager = new PackageManager(); + +module.exports = singletonPackageManager; \ No newline at end of file diff --git a/src/path.js b/src/path.js index dca3840..9ed9a8a 100644 --- a/src/path.js +++ b/src/path.js @@ -1,6 +1,8 @@ const ospath = require("path"); const fs = require("fs"); +const nodeModulesFolder = {}; + class PathFunctions { static isObjectEmpty(obj) { if (typeof obj === 'object' && Object.keys(obj).length !== 0) { @@ -10,34 +12,98 @@ class PathFunctions { } } + static pathExists(path) { + try { + return !fs.accessSync(path, fs.constants.F_OK); + } catch (e) { + return false; + } + } + static fileExists(path) { try { - return !fs.accessSync(path, fs.F_OK); + return (PathFunctions.pathExists(path) && fs.lstatSync(path).isFile()); } catch (e) { return false; } } + + static folderExists(path) { + return (fs.existsSync(path) && fs.lstatSync(path).isDirectory()); + } static isRelativePath(path) { return path.match(/^\.{0,2}\//); } - static checkIfModule(path) { - const notModuleRegExp = /^\.$|^\.[\\\/]|^\.\.$|^\.\.[\/\\]|^\/|^[A-Z]:[\\\/]/i; - const isModuleVar = !notModuleRegExp.test(path) || path.includes("node_modules"); - return isModuleVar; + static isRegularPath(path) { + const regularPathRegExp = /^\.$|^\.[\\\/]|^\.\.$|^\.\.[\/\\]|^\/|^[A-Z]:[\\\/]/i; + const isRegularPath = regularPathRegExp.test(path); + return isRegularPath; + } + + static isNodeModule(path) { + const isNodeModuleVar = !PathFunctions.isRegularPath(path) || path.includes("node_modules"); + return isNodeModuleVar; } + + static removeLastSegment(path) { + return path.substring(0, path.lastIndexOf(ospath.sep)); + } - static getModuleAbsolutePath(parsedJSFile, convertedImportsPath) { - let absolutePath = convertedImportsPath; - if (!ospath.isAbsolute(convertedImportsPath)) { - absolutePath = ospath.join(ospath.dirname(parsedJSFile), convertedImportsPath); + static getAbsolutePath(path, from=process.cwd()) { + if (ospath.isAbsolute(path)) return path; + let currentDir = from; + if (!PathFunctions.isNodeModule(path)) return ospath.join(currentDir, path); + let mainPackage = path.split("/")[0]; + if (nodeModulesFolder[mainPackage] !== undefined) { + if (nodeModulesFolder[mainPackage] === null) { + return null; + } else { + return ospath.join(nodeModulesFolder[mainPackage], path) + } + } + while (currentDir) { + if (currentDir.endsWith("node_modules")) { + currentDir = PathFunctions.removeLastSegment(currentDir); + continue; + } + const nodeModulesPath = ospath.join(currentDir, "node_modules"); + const packagePath = ospath.join(nodeModulesPath, mainPackage); + if (PathFunctions.pathExists(packagePath)) { + nodeModulesFolder[mainPackage] = nodeModulesPath; + return ospath.join(nodeModulesPath, path); + } + currentDir = PathFunctions.removeLastSegment(currentDir); + } + nodeModulesFolder[mainPackage] = null; + return null; + } + + static absoluteToRelative(fromAbsolute, toAbsolute) { + let relativeFilePath = ospath.relative(fromAbsolute, toAbsolute).replace(/\\/g, "/"); + if (relativeFilePath.length === 0) { + relativeFilePath = './'; + } + if (!['.', '/'].includes(relativeFilePath[0])) { + relativeFilePath = `./${relativeFilePath}`; } - const ext = ['.js','.jsx','.ts','.tsx', '/index.js','/index.jsx','/index.ts','/index.tsx'].find((ext)=> PathFunctions.fileExists(absolutePath + ext)) || ""; - absolutePath = absolutePath + ext; - // const resolvedAbsolutePath = require.resolve(absolutePath); - return absolutePath; + return relativeFilePath; } + + static removeNodeModulesPath(path) { + const PackageDirName = path.replace(/.:.+node_modules[\/|\\]/g,""); + return PackageDirName; + } + + static normalizeModulePath(path) { + if (!ospath.isAbsolute(path)) return path; + if (PathFunctions.isNodeModule(path)) { + return PathFunctions.removeNodeModulesPath(path); + } else { + return "." + path.replace(process.cwd(), ""); + } + } } module.exports = PathFunctions; \ No newline at end of file