vite icon indicating copy to clipboard operation
vite copied to clipboard

Wrong esbuild target when using `vite dev`

Open newcat opened this issue 2 years ago β€’ 20 comments

Describe the bug

When setting a build target in the config using either build.target or esbuild.target, this only applies to vite build. So far, this is expected, since #10207 automatically sets the esbuild target to esnext when using vite dev. However, when I import a library that requires the target to be set to esnext (bson for example) and try running vite dev I get this error message:

[ERROR] Top-level await is not available in the configured target environment ("chrome87", "edge88", "es2020", "firefox78", "safari14" + 2 overrides)

Running vite build works fine. So I am wondering, why the target environment during vite dev is set to the default target instead of esnext.

Reproduction

https://stackblitz.com/edit/vitejs-vite-rfowdk?file=main.js

Steps to reproduce

  • Running vite build succeeds
  • Running vite dev fails

System Info

System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 16.20.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 9.4.2 - /usr/local/bin/npm
    pnpm: 8.6.3 - /usr/local/bin/pnpm
  npmPackages:
    vite: ^4.4.0 => 4.4.2

Used Package Manager

npm

Logs

No response

Validations

newcat avatar Jul 09 '23 23:07 newcat

Glad to answer your questions.

Vite use esbuild to pre-bundle dependencies in node_modules. For pre-bundle stage, optimizeDeps.esbuildOptions is used as transform api's parameter. You can try it.

https://github.com/vitejs/vite/blob/c8a741adec14568f2ba9599056cb2eebab52215f/packages/vite/src/node/optimizer/index.ts#L809

I just tried it, and it work. https://github.com/haijie-x/vite-issues/tree/master/esbuild-target

Not sure if I misunderstood you, but I hope this can help you.

hai-x avatar Jul 10 '23 03:07 hai-x

I am also having this issue. I'm working on an Adobe plugin and using vite to build/develop. Adobe's current environment is using chrome version 88, so having the default build output for dev being set to esnext does not work in this case, as there are many features in esnext that do not work in chrome 88. Changing this line from esnext to es2020 does resolve the problem.

I understand there is supposed to be a way to override this behavior using optimizeDeps.esbuildOptions, however it does not appear to work for me if I set optimizeDeps.esbuildOptions.target to es2020. Seems like this should default to the target defined in the tsconfig file rather than needing to override this in the esbuild options?

treardon17 avatar Jul 13 '23 18:07 treardon17

However, it's also worth noting that I'm not having this issue on version 4.3.9, but I am having this issue on 4.4.0 and above. It appears the line I referenced above is present in both versions, so it is likely not the root cause of the issue.

treardon17 avatar Jul 13 '23 18:07 treardon17

Changing this line from esnext to es2020 does resolve the problem.

The configuration for that line is through the esbuild.target config for your source code. The optimizeDeps.esbuildOptions config is used to configure optimizing deps code.

bluwy avatar Jul 17 '23 07:07 bluwy

@bluwy looks like that works, thank you! I am curious though... shouldn't the build be picking up my tsconfig target? it seems a little odd and unfortunate to need to define the build target in multiple places.

treardon17 avatar Jul 17 '23 15:07 treardon17

So as far as I understand, this is how the target is determined for the different parts:

Command App Deps
vite dev set to esnext automatically optimizeDeps.esbuildOptions.target
vite build build.target build.target

I find this quite confusing, honestly. IMO everything should be affected by build.target. Maybe it could be overridden by optimizeDeps.esbuildOptions.target, but the default value should be the one from build.target and not the global default. Is there any specific reason for how it's implemented currently?

newcat avatar Jul 23 '23 22:07 newcat

@newcat build.target should only affect the build as its name suggest. For dev, the goal is to only transpile to normal JS as serve it as-is. E.g. if you're writing .js file with bleeding-edge features, Vite isn't going to run that file through esbuild either. So that PR was keeping it in line between .ts and .js. (But I'd admit that I initially didn't plan for esnext to always override in that PR, I figured it was more of a fallback if compilerOptions.target is not specified.)

However, it's also worth noting that I'm not having this issue on version 4.3.9, but I am having this issue on 4.4.0 and above.

@treardon17 I'm currently also tracking this behaviour in https://github.com/vitejs/vite/issues/13863, but I haven't yet figure out where is causing this issue.


I agree that it would be better to have a single place to configure target instead though, but I'm not sure how that looks like as it conflicts with Vite's transpiling setup. The configs in dev are intentionally "more verbose" due to the reasons above.

bluwy avatar Jul 26 '23 07:07 bluwy

@bluwy I may be misunderstanding something, but I'm not sure if separating the targets for build vs dev is a good default behavior. I could see it being a useful opt-in behavior if it's explicitly set. With the exception of minification, I expect (or hope) my code will have the same output in both scenarios because debugging/fixing something is much more difficult if it is built for a different environment.

treardon17 avatar Jul 26 '23 16:07 treardon17

That's how Vite works fundamentally (even before I started maintaining it). Dev and build are two separate paths, and while we try to keep things consistent between them, dev has kept it's philosophy of serving things as-is without too many work that slow things down.

Maybe I could see dependency prebundling falling back to build.target instead of Vite's hardcoded set of targets though. They're making a build after-all, and if we're able to nail prebundling in builds too, the change makes more sense.


I took another look in the issue and it seems to not be a regression like I thought. It is working as expected and only needs the right optimizeDeps.esbuildOptions.target configuration. I think we can document this better at https://vitejs.dev/guide/#browser-support

bluwy avatar Jul 31 '23 07:07 bluwy

What is the recommended way to target older versions of a browser for development/debugging purposes? I've tried setting the three different target parameters but none of them seem to be working to change the target of what the NPM dependencies are transformed to.

frank-weindel avatar Oct 06 '23 16:10 frank-weindel

optimizeDeps.esbuildOptions.target would be the config option as the dependencies are optimized by esbuild. Polyfills etc are not supported in dev. Dev usually needs a more modern browser if not evergreen as newer JS syntaxes are not transpiled for performance.

bluwy avatar Oct 09 '23 06:10 bluwy

So would you recommend using vite build --watch with vite preview running concurrently for development on older browsers?

frank-weindel avatar Oct 10 '23 02:10 frank-weindel

Yes that would be the way, however it may be bring a slower development loop depending on your project size. It's a tradeoff Vite makes for the DX side of things.

bluwy avatar Oct 10 '23 10:10 bluwy

optimizeDeps.esbuildOptions.target would be the config option as the dependencies are optimized by esbuild. Polyfills etc are not supported in dev. Dev usually needs a more modern browser if not evergreen as newer JS syntaxes are not transpiled for performance.

I'm using explicit resource management feature, which hasn't been supported by any browsers, nor node. Currently, I add a plugin which calls esbuild in transform hook, which do work despite breaking the sourcemap. As typescript sources are converted into javascript, there should be a transpiling process working underly, I believe. So I just wonder, if there IS sth. like esbuild or other transpiler working, why vite just locking its target to esnext instead of allowing users to override, as in some corner cases users accept the cost of performances?


After some test, seems that my problem isn't related to target, as vite does use es2022 as target when I inspect into it. Maybe there are some other flags that disable transpiling logic.

Things get weird. I've copied the config that passed into esbuild by vite to my plugin, which still works. Does it mean that esbuild behaves differently between outside and inside vite?

config copied from debug console.

            {
              target: 'es2022',
              charset: 'utf8',
              jsxDev: true,
              minify: false,
              minifyIdentifiers: false,
              minifySyntax: false,
              minifyWhitespace: false,
              treeShaking: false,
              keepNames: false,
              supported: {
                'dynamic-import': true,
                'import-meta': true
              },
              loader: 'ts',
              tsconfigRaw: {
                compilerOptions: {
                  target: 'es2022',
                  verbatimModuleSyntax: true
                }
              }
            }

neko-para avatar Mar 30 '24 16:03 neko-para

Inability to override the default target for development mode makes it impossible to use Vite with esbuild in projects which use the stage 3 decorators, because the bundler does not transpile them when target is esnext. The solution may be to use the @rollup/plugin-typescript as a replacement for esbuild, but in that case all dependencies are managed by typescript as well. Either way it's a loss.

Kapelianovych avatar Jun 12 '24 10:06 Kapelianovych

You can set esbuild.target: "es2020" or esbuild.supported.decorators: false (see https://github.com/vitejs/vite/issues/17308) to get it to work. If you want to use decorators in JS too, you need to configure esbuild to process JS files too with esbuild.include. You don't need an external plugin for this.

bluwy avatar Jun 12 '24 14:06 bluwy

@bluwy thanks. I did try changing esbuild.target and providing an overrides but I didn't use the .jsx extension (my files contain JSX). Tried to set esbuild.loader for .js but failed and somewhere on the way gave up. Your message helped a lot!

Edit: actually it works for .jsx files, but when I am using decorators in .js files, decorators are not compiled. I have the following configuration:

export default {
  esbuild: {
      jsx: "automatic",
      target: "es2020",
      include: /\.jsx?$/,
      jsxImportSource: "preact",
  }
}

Edit 2: I figured out that the esbuild.exclude must be set at least to empty value to avoid setting the default value which always excludes .js files. So final configuration looks like this:

export default {
  esbuild: {
      jsx: "automatic",
      target: "es2020",
      include: /\.jsx?$/,
      exclude: [],
      jsxImportSource: "preact",
  }
}

Kapelianovych avatar Jun 12 '24 21:06 Kapelianovych

I wonder if we should change the default target esnext to any present day target like es2025 on dev. This should allow using and decorator to work out-of-the-box for most of the cases (though it doesn't cover all the js scenario mentioned in this issue and react babel scenario https://github.com/vitejs/vite-plugin-react/issues/895).

Vitest currently sets target: "node18" automatically as a fallback to achieve exactly this and I haven't seen people complaining this since then.

hi-ogawa avatar Oct 14 '25 23:10 hi-ogawa

Hey @hi-ogawa! First of all, thank you so much for your work on such an incredible bundler!

I have a project that uses https://lit.dev/. This framework is heavily based on decorators (although it's possible to use it without them, all the examples use decorators). I build the project using Vite, and in the past, I used Babel as a Vite plugin – which I never really liked.

Later, when esbuild added decorator support, and I finally had time to remove the Babel plugin, I thought it would be simpler. But I still had to add something to the Vite config to make decorators work, and honestly, I don't really understand what's going on there (thanks by the way to @Kapelianovych for the research):

esbuild: {
		exclude: [],
		include: /\.js$/,
		target: `es2024`,
},

Here is the PR, btw.

Maybe if it's not about changing the target (though I don't quite understand why that's needed, since according to the esbuild docs, decorators should work if the target is set to esnext πŸ€” Or maybe I'm missing something?), then perhaps this could be added to the documentation - how to enable decorators, using, and so on in Vite? Because right now, it feels almost impossible to figure out without looking into the source code.

Also, it's a bit strange that the current target seems to be node18, since it’s already EoL (End-of-life) πŸ˜…

what1s1ove avatar Oct 18 '25 13:10 what1s1ove