esbuild icon indicating copy to clipboard operation
esbuild copied to clipboard

import.meta.main

Open jamesdiacono opened this issue 1 year ago • 4 comments

It appears that esbuild in --bundle mode does not yet support import.meta.main.

Consider this playground example. Two modules, entry.js and file.js, are bundled into a new module, bundle.js. entry.js imports file.js, and both source modules contain functionality that only runs when import.meta.main is true. That is, both entry.js and file.js can either be executed as an entry point or imported as a module.

Now consider what happens when bundle.js is executed as an entry point. Because esbuild passed through occurrences of import.meta.main, both entry.js's and file.js's conditional functionality will run. This is a problem because the semantics of import.meta.main are such that no two modules in a program should ever have their import.meta.main evaluate to true.

At the time of writing, import.meta.main is only implemented in Deno and in Bun, and is not yet part of a standard. Regardless, I have found it to be a very useful construct and also safe, because import.meta.main is always falsy (undefined) in other runtimes.

I would like to propose a strategy for bundling import.meta.main:

  • Occurrences of import.meta.main in bundle entry points, such as entry.js, remain untouched. That way, the resulting bundle retains the flexibility of being run as an entry point or imported as a module.
  • Occurrences of import.meta.main in all other modules are replaced with false. This has the added bonus that conditional "main" code and its dependencies can be tree shaken away.

Of course, this is assuming --format=esm. Things get more complicated if the resulting bundle is something like commonjs, which does not have an equivalent concept to import.meta.main. Fortunately, it appears that esbuild does not attempt to support such translation anyway.

Thank you for the excellent bundler.

jamesdiacono avatar Jul 20 '24 06:07 jamesdiacono

It is possible to do what you said with the plugin system of esbuild. See this example:

import {build} from 'esbuild'
import {join} from 'path'

await build({
  entryPoints: ['src/entry.js'],
  bundle: true,
  format: 'esm',
  // minifySyntax: true,
  plugins: [{
    name: 'import.meta.main',
    setup({ esbuild, onResolve, onLoad }) {
      // TODO: better filter by initialOptions.entryPoints
      onResolve({ filter: /()/ }, args => {
        if (args.kind == 'entry-point') {
          // TODO: should resolve file extensions
          const path = join(args.resolveDir, args.path)
          // TODO: should reject non-js variants (like css)
          return { path, pluginData: 'entry-point' }
        }
      })
      // TODO: should filter js variants
      onLoad({ filter: /()/ }, async args => {
        // remain untouched
        if (args.pluginData == 'entry-point') return;
        // strip the main block
        const {outputFiles} = await esbuild.build({
          entryPoints: [args.path],
          define: { 'import.meta.main': 'false' },
          write: false,
        })
        const contents = outputFiles[0].contents
        return {contents}
      })
    }
  }],
}).catch(() => process.exit(1))

hyrious avatar Jul 22 '24 22:07 hyrious

That is what I'm doing right now. I created this issue because I feel import.meta.main deserves built-in support.

jamesdiacono avatar Jul 22 '24 23:07 jamesdiacono

I am trying to point out that esbuild's lack of support for import.meta.main can be hazardous in some situations. For example, suppose you had written a Deno program that depended on a third party module. Now suppose that that module was modified to contain import.meta.main. While this will not affect your program running from source, your program's bundle is likely broken.

jamesdiacono avatar Jul 22 '24 23:07 jamesdiacono

I just discovered that import.meta.main is recognized by WinterCG, so it seems unlikely that any runtime will implement conflicting semantics in the future. See https://github.com/wintercg/import-meta-registry.

jamesdiacono avatar Jul 25 '24 01:07 jamesdiacono