Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remap multi entry points ext by outExtension #2973

Closed
1aron opened this issue Mar 8, 2023 · 1 comment
Closed

Remap multi entry points ext by outExtension #2973

1aron opened this issue Mar 8, 2023 · 1 comment

Comments

@1aron
Copy link

1aron commented Mar 8, 2023

By default, Node cannot import corresponding extensionless modules based on its own file extension.

Input

src/index.ts

export * from './options'

src/options.ts

export const options = {}

Build them with:

esbuild.build({
  entryPoints: ['src/index.ts', 'src/options.ts'],
  outbase: 'src',
  outdir: 'dist',
  bundle: false,
  outExtension: { '.js': '.mjs' },
})

Output

src/index.mjs

export * from './options'

src/options.mjs

export const options = {}

Import

Import above package into your project with "type": "module" and get the error:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'dist/options' imported from dist/index.mjs

The reason is that dist/options reads dist/options.js which does not exist.

Expect

Is there a way for us to add the extension mjs to the path of import and export syntax according to entryPoints and the relationship of the dependency tree?

src/index.mjs

export * from './options.mjs'

Current solution

As a package author, the currently conceivable solution is to package them into common dist/esm and dist/cjs structures and use the original .js extension to rely on the behavior that Node maps to the .js module.

dist/esm

esbuild.build({
  entryPoints: ['src/index.ts', 'src/options.ts'],
  format: 'esm',
  outbase: 'src',
  outdir: 'dist/esm',
  bundle: false
})

dist/cjs

esbuild.build({
  entryPoints: ['src/index.ts', 'src/options.ts'],
  format: 'cjs',
  outbase: 'src',
  outdir: 'dist/cjs',
  bundle: false
})
@1aron
Copy link
Author

1aron commented Mar 10, 2023

I wrote an ESBuild plugin to temporarily solve this problem, automatically adding extensions to any import, export syntax.

import { Plugin } from 'esbuild'
import fs from 'fs'
import fg from 'fast-glob'
import path from 'upath'

export function createFillModuleExtPlugin(outext = '.js', outdir = 'src'): Plugin {
    const resolvedOutdir = path.resolve(outdir)
    return {
        name: 'fill-module-ext',
        setup(build) {
            const started: any = {}
            started.promise = new Promise(resolve => {
                started.resolve = resolve
            })
            build.onStart(() => {
                started.resolve(true)
            })
            build.onLoad({ filter: /\.ts$/ }, async (args) => {
                if (await started.promise === true) {
                    const content = await fs.promises.readFile(args.path, { encoding: 'utf8' })
                    const currentDirPath = path.dirname(args.path)
                    return {
                        contents: content
                            .replace(/((?:(?:import|export)(?:.*from | ))|(?:(?:import))\()'((\.(?:\.)?\/.*)|\.)'/gmi,
                                (...matches) => {
                                    const modulePath: string = matches[2]
                                    const parsedModulePath = path.parse(modulePath)
                                    if (parsedModulePath.ext) {
                                        return matches[1]
                                    }
                                    const targetDir = path.resolve(currentDirPath, modulePath)
                                    const foundModuleSourcePath = fg.sync([
                                        targetDir + '.{ts,js,mjs,jsx,tsx}',
                                        path.join(targetDir, 'index.{ts,js,mjs,jsx,tsx}')
                                    ])[0]
                                    if (!foundModuleSourcePath) {
                                        return matches[0]
                                    }
                                    let targetModulePath = path.relative(resolvedOutdir, path.changeExt(foundModuleSourcePath, outext))
                                    const parsedTargetModulePath = path.parse(targetModulePath)
                                    if (modulePath === '.' || modulePath === './') {
                                        targetModulePath = './index' + outext
                                    } else if (parsedTargetModulePath.name === parsedModulePath.name) {
                                        targetModulePath = modulePath + outext
                                    } else {
                                        targetModulePath = modulePath + '/index' + outext
                                    }
                                    return `${matches[1]}'${targetModulePath}'`
                                }),
                        loader: 'ts',
                    }
                }
            })
        }
    }
}

@1aron 1aron closed this as completed Mar 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant