Skip to content

Instantly share code, notes, and snippets.

@jakebailey
Last active April 5, 2023 22:08
Show Gist options
  • Save jakebailey/1e4df8b3a33d2da7b0db4ae39861afd8 to your computer and use it in GitHub Desktop.
Save jakebailey/1e4df8b3a33d2da7b0db4ae39861afd8 to your computer and use it in GitHub Desktop.

evanw/esbuild#3019

Categorizing the options

  • emitted syntax (roughly)
    • target
    • alwaysStrict
    • jsx
    • jsxFactory
    • jsxFragmentFactory
    • jsxImportSource
    • importsNotUsedAsValues
    • preserveValueImports
    • verbatimModuleSyntax (not yet implemented)
    • useDefineForClassFields
  • module resolution
    • jsxImportSource?
    • baseUrl
    • moduleSuffixes
    • paths
  • other
    • extends

Syntax

  • target
    • why would anyone want multiple syntaxes within the same bundle?
    • IMO, respect only esbuild's top-level setting, then warn if esbuild option is unset but found in tsconfig
  • alwaysStrict
  • jsx, etc
    • Would someone intentionally use multiple jsx targets? Maybe, in a monorepo? But would they really be using React and Preact?
    • If someone ran tsc and emit, there would be no problem, because TS would emit JS with the right syntax/imports, so it feels like esbuild should allow it.
    • why would anyone want "preserve" as a target in esbuild?
  • importsNotUsedAsValues, preserveValueImports, verbatimModuleSyntax
    • Unfortunate effect of these is that bundlers have to handle all of them, so adding a third one didn't make things simpler. Bundlers still have to know which one a user is expecting, unless they universally throw their hands up and assume that we're in a verbatimModuleSyntax world
      • In 5.5, we delete the first two? Won't stop people from using old versions, esbuild may have to do all three for a long time
    • Unclear whether or not esbuild will actually respect side effects in verbatimModuleSyntax?
    • verbatimModuleSyntax forces writing import foo = require("foo") even though the bundler won't actually care?
  • useDefineForClassFields
    • Oh boy.
    • This can only affect the project being bundles, not dependencies; they will have already had their stuff set up as desired.
    • swc is moving to [[Define]] (in ES2022+?) unless configured otherwise.
      • Is that just TS's behavior?
    • Does it make sense for both to coexist if we can do multiple tsconfigs? Maybe?
    • evanw/esbuild#2993 (comment)
      • Talks about deriving this from esbuild's target
      • I agree this is a bad idea, this is a TS transformation problem
      • Doesn't affect JS, right? Pure JS only has define semantics?
  • experimentalDecorators
    • just like JSX, this could be scoped per-project

Module resolution options

Misc

  • tsconfig and .ts in node_modules? Seems ill-advised to ever have this.
  • how about js files included via allowJs?
  • could esbuild check to see if a tsconfig includes a file in includes/excludes/files and warn if a file is referenced but not listed?

Actual coherent recommendations

  • Generally, I think we can be guided by:
    • What would happen if everyone ran tsc and then ran esbuild on the output?
    • Would anyone actually want the behavior?
  • Keep supporting multiple tsconfigs. It seems required for too many options.
    • paths, baseUrl, jsx*, useDefineForClassFields, experimentalDecorators, etc
  • Entirely ignore tsconfig in node_modules. Better yet, ignore .ts in node_modules too. If esbuild weren't involved, the .js files will be executed, not the .ts files.
  • Ignore target, let it be controlled by esbuild globally. It doesn't make sense to have this configured per project.
    • Gilding the lily: warn if target is unset, but tsconfig has a value.
    • This is an exception to "what if tsc then esbuild".
  • Ignore alwaysStrict and always assume strict. For TS code, it's going to become effectively impossible to write code without being in strict mode.
  • Ignore moduleSuffixes and recommend esbuild's resolveExtensions, which is how it works in webpack and matches the user expectation of "global config".
    • They can set [".ios.ts", ".native.ts", ".ts", ...].
  • Allow multiple jsx settings per-project. It's possible that there are monorepos with mixed environments.
  • Respect useDefineForClassFields+target for TS code in choosing class emit style.
    • Alternatively, enabling this by default may also be fine; all "bad" code for this should only ever be in the user's project, not their dependencies. They can discover the problem themselves, right?
  • Respect experimentalDecorators for TS code for figuring out which decorator to use.
  • Respect paths/baseUrl, in the nearest tsconfig. There is really no way to determine which tsconfig is the one to emit any particular file, so this seems like the only reasonable thing, besides giving esbuild a list of tsconfig files that could apply and then have it run the globs to determine which one is the most applicable.
  • Respect importsNotUsedAsValues, preserveValueImports, verbatimModuleSyntax? This really only depends on how much side effect behavior esbuild is wanting to emulate. Unfortunately us adding a third variant means more work, because dropping the other two means not supporting TS <5.0.

Gotchas:

  • All of the above only apply to TS code...
    • Except JSX emit, as those need to get downleveled at some point. By nature of "what if tsc ran", those need to be configurable too.
    • Except .js files + paths/baseUrl; should paths apply? What do do? Should this be a plugin + aliases?

Alternatively...

For settings that would typically require a tsconfig, instead expand out esbuild's configuration to allow them to be configured by path.

  • baseUrl/paths replaced with alias.
  • jsx options can be configured dynamically.
  • useDefineForClassFields and experimentalDecorators set per path, to help work around differences.

This feels less friendly to the users, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment