esbuild-fix-imports-plugin
Version:
An ESBuild plugin that fixes import paths by applying fixAliasPlugin, fixFolderImportsPlugin, and fixExtensionsPlugin. It ensures correct file extensions, resolves path aliases, and fixes directory imports in your build output when using 'tsup' with 'bund
1 lines • 6.11 kB
Source Map (JSON)
{"version":3,"sources":["../../src/fixFolderImportsPlugin.ts"],"sourcesContent":["import { dirname, resolve } from \"path\";\nimport { type Plugin } from \"esbuild\";\n\n/**\n * ESBuild plugin that replaces import paths pointing to directories\n * with explicit paths to the index file, but only if the output files\n * indicate that a direct file doesn't exist and an index file does.\n *\n * For example, it transforms:\n * ```javascript\n * import { myFunction } from '../folder';\n * ```\n * into:\n * ```javascript\n * import { myFunction } from '../folder/index';\n * ```\n *\n * @returns {import('esbuild').Plugin} An ESBuild plugin object.\n */\nexport const fixFolderImportsPlugin = (): Plugin => ({\n name: \"fixFolderImportsPlugin\",\n setup(build) {\n // Determine the output file extension based on the build options.\n const outExtension = build.initialOptions.outExtension?.[\".js\"] ?? \".js\";\n\n // Hook into the 'onEnd' event of the build process.\n build.onEnd((result) => {\n // If there are build errors, do not proceed.\n if (result.errors.length > 0) {\n return;\n }\n\n // Collect all .js output file paths in a set for quick existence checks.\n const allJsOutputs = new Set(\n (result.outputFiles ?? [])\n .filter((f) => f.path.endsWith(outExtension))\n .map((f) => f.path)\n );\n\n // Iterate over each JS output file\n for (const outputFile of result.outputFiles ?? []) {\n if (!outputFile.path.endsWith(outExtension)) {\n continue;\n }\n\n // Original file contents and the file path\n const fileContents = outputFile.text;\n const filePath = outputFile.path;\n\n // Modify the file contents by replacing directory imports\n // with explicit index file imports based on the set of output files\n const nextFileContents = modifyFolderImports(\n fileContents,\n filePath,\n outExtension,\n allJsOutputs\n );\n\n // Update the output file contents only if something changed\n if (nextFileContents !== fileContents) {\n outputFile.contents = Buffer.from(nextFileContents, \"utf-8\");\n }\n }\n });\n },\n});\n\n/**\n * Regex for matching ESM and CJS relative imports.\n */\nconst ESM_RELATIVE_IMPORT_EXP = /from\\s*['\"](\\..+?)['\"]/g;\nconst CJS_RELATIVE_IMPORT_EXP = /require\\s*\\(\\s*['\"](\\..+?)['\"]\\s*\\)/g;\n\n/**\n * Regex to detect if an import path already has a file extension.\n */\nconst hasExtensionRegex = /\\.[^./\\\\]+$/;\n\n/**\n * For each import/require statement in a file, check if it refers to a directory\n * without an explicit index file. If so, and if an `index` file actually exists,\n * transform the path to include `/index`.\n */\nconst modifyFolderImports = (\n contents: string,\n filePath: string,\n outExtension: string,\n allJsOutputs: Set<string>\n): string => {\n // Replace ESM imports\n contents = contents.replace(ESM_RELATIVE_IMPORT_EXP, (match, importPath) => {\n const newPath = replaceFolderImport(\n importPath,\n filePath,\n outExtension,\n allJsOutputs\n );\n return match.replace(importPath, newPath);\n });\n\n // Replace CJS requires\n contents = contents.replace(CJS_RELATIVE_IMPORT_EXP, (match, importPath) => {\n const newPath = replaceFolderImport(\n importPath,\n filePath,\n outExtension,\n allJsOutputs\n );\n return match.replace(importPath, newPath);\n });\n\n return contents;\n};\n\n/**\n * Decide whether the given import path should have `/index` appended\n * by checking against the set of all generated output paths.\n */\nconst replaceFolderImport = (\n importPath: string,\n filePath: string,\n outExtension: string,\n allJsOutputs: Set<string>\n): string => {\n // If the path already ends with a slash, a dot, or has an extension, skip\n if (\n importPath.endsWith(\"/\") ||\n importPath.endsWith(\".\") ||\n hasExtensionRegex.test(importPath)\n ) {\n return importPath;\n }\n\n // Compute the absolute paths for \"importPath\" and \"importPath + /index\"\n const currentDir = dirname(filePath);\n const candidateFile = resolve(currentDir, importPath) + outExtension;\n const candidateIndex =\n resolve(currentDir, importPath, \"index\") + outExtension;\n\n // If candidateFile is found, do not add /index; otherwise check candidateIndex\n if (allJsOutputs.has(candidateFile)) {\n return importPath;\n } else if (allJsOutputs.has(candidateIndex)) {\n return importPath + \"/index\";\n }\n\n return importPath;\n};\n"],"mappings":"AAAA,SAAS,SAAS,eAAe;AAmB1B,MAAM,yBAAyB,OAAe;AAAA,EACnD,MAAM;AAAA,EACN,MAAM,OAAO;AAEX,UAAM,eAAe,MAAM,eAAe,eAAe,KAAK,KAAK;AAGnE,UAAM,MAAM,CAAC,WAAW;AAEtB,UAAI,OAAO,OAAO,SAAS,GAAG;AAC5B;AAAA,MACF;AAGA,YAAM,eAAe,IAAI;AAAA,SACtB,OAAO,eAAe,CAAC,GACrB,OAAO,CAAC,MAAM,EAAE,KAAK,SAAS,YAAY,CAAC,EAC3C,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,MACtB;AAGA,iBAAW,cAAc,OAAO,eAAe,CAAC,GAAG;AACjD,YAAI,CAAC,WAAW,KAAK,SAAS,YAAY,GAAG;AAC3C;AAAA,QACF;AAGA,cAAM,eAAe,WAAW;AAChC,cAAM,WAAW,WAAW;AAI5B,cAAM,mBAAmB;AAAA,UACvB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAGA,YAAI,qBAAqB,cAAc;AACrC,qBAAW,WAAW,OAAO,KAAK,kBAAkB,OAAO;AAAA,QAC7D;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAKA,MAAM,0BAA0B;AAChC,MAAM,0BAA0B;AAKhC,MAAM,oBAAoB;AAO1B,MAAM,sBAAsB,CAC1B,UACA,UACA,cACA,iBACW;AAEX,aAAW,SAAS,QAAQ,yBAAyB,CAAC,OAAO,eAAe;AAC1E,UAAM,UAAU;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO,MAAM,QAAQ,YAAY,OAAO;AAAA,EAC1C,CAAC;AAGD,aAAW,SAAS,QAAQ,yBAAyB,CAAC,OAAO,eAAe;AAC1E,UAAM,UAAU;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO,MAAM,QAAQ,YAAY,OAAO;AAAA,EAC1C,CAAC;AAED,SAAO;AACT;AAMA,MAAM,sBAAsB,CAC1B,YACA,UACA,cACA,iBACW;AAEX,MACE,WAAW,SAAS,GAAG,KACvB,WAAW,SAAS,GAAG,KACvB,kBAAkB,KAAK,UAAU,GACjC;AACA,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,QAAQ,QAAQ;AACnC,QAAM,gBAAgB,QAAQ,YAAY,UAAU,IAAI;AACxD,QAAM,iBACJ,QAAQ,YAAY,YAAY,OAAO,IAAI;AAG7C,MAAI,aAAa,IAAI,aAAa,GAAG;AACnC,WAAO;AAAA,EACT,WAAW,aAAa,IAAI,cAAc,GAAG;AAC3C,WAAO,aAAa;AAAA,EACtB;AAEA,SAAO;AACT;","names":[]}