UNPKG

@plugjs/plug

Version:
110 lines (94 loc) 4.18 kB
import path from 'node:path' import { stat } from '../../fs' import { assertAbsolutePath, resolveAbsolutePath, resolveFile } from '../../paths' import type { Plugin } from 'esbuild' /** * A simple ESBuild plugin fixing extensions for `require` and `import` calls. * * This can be useful when compiling dual-module packages (`esm` and `cjs`), * where the file module type is determined by the `.mjs` or `.cjs` extension. * * For example this will make sure all `import` statements use the `.mjs` * extensions, while all `require` use `.cjs`. * * ``` * await find('*.ts', { directory: 'src' }) * .esbuild({ * outdir: 'dist', * format: 'cjs', * plugins: [ fixExtensions ], * outExtension: { '.js': '.mjs' }, * }) * * await find('*.ts', { directory: 'src' }) * .esbuild({ * outdir: 'dist', * format: 'esm', * plugins: [ fixExtensions ], * outExtension: { '.js': '.mjs' }, * }) * ``` */ export function fixExtensions(): Plugin { return { name: 'fix-extensions', setup(build): void { /* When using this, we fake esbuild's "bundle" functionality */ build.initialOptions.bundle = true /* Our ".js" extension, might be remapped by `outExtension`s */ const cjs = build.initialOptions.outExtension?.['.cjs'] || '.cjs' const mjs = build.initialOptions.outExtension?.['.mjs'] || '.mjs' const js = build.initialOptions.outExtension?.['.js'] || '.js' /* Extensions for files to look for */ const exts = build.initialOptions.resolveExtensions || [ '.ts', '.js', '.tsx', '.jsx' ] /* Intercept resolution */ build.onResolve({ filter: /.*/ }, async (args) => { /* Ignore the entry points (when the file is not being imported) */ if (! args.importer) return null /* Anything not starting with "."? external node module */ if (! args.path.match(/^\.\.?\//)) return { external: true } /* Some easy pathing options */ const resolveDir = args.resolveDir assertAbsolutePath(resolveDir) /* First of all, check if the _real_ filename exists */ const resolved = resolveAbsolutePath(resolveDir, args.path) if (resolveFile(resolved)) return { path: args.path, external: true } /* * Thank you TypeScript 4.7!!! If the file is ".js", ".mjs" or ".cjs" we * need to check if we have the corresponding ".ts", ".mts" or ".cjs" * and return whatever ESBuild maps that particular extension to. */ const match = args.path.match(/(.*)(\.[mc]?js$)/) if (match) { const [ , name, ext ] = match const tspath = name + ext!.replace('js', 'ts') const tsfile = resolveAbsolutePath(resolveDir, tspath) if (resolveFile(tsfile)) { const newext = ext === '.mjs' ? mjs : ext === '.cjs' ? cjs : js return { path: name + newext, external: true } } } /* Check if ".../filename.ext" exists in our sources */ for (const ext of exts) { const fileName = `${args.path}${ext}` const filePath = path.resolve(args.resolveDir, fileName) const isFile = await stat(filePath).then((stat) => stat.isFile(), (error) => void error) if (isFile) return { path: `${args.path}${js}`, external: true } } /* If ".../filename" is not a directory, we end here */ const dirPath = path.resolve(args.resolveDir, args.path) const isDir = await stat(dirPath).then((stat) => stat.isDirectory(), (error) => void error) if (! isDir) return { external: true } /* Check if ".../filename/index.ext" exists in our sources */ for (const ext of exts) { const fileName = path.join(args.path, `index${ext}`) const filePath = path.resolve(args.resolveDir, fileName) const isFile = await stat(filePath).then((stat) => stat.isFile(), (error) => void error) if (isFile) return { path: `${args.path}/index${js}`, external: true } } /* Nothing was found, then just mark this external */ return { external: true } }) }, } }