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

Better handling of dynamic imports in dead code #5676

Open
6 of 7 tasks
blckngm opened this issue Nov 14, 2021 · 11 comments
Open
6 of 7 tasks

Better handling of dynamic imports in dead code #5676

blckngm opened this issue Nov 14, 2021 · 11 comments
Labels
has workaround p3-minor-bug An edge case that only affects very specific usage (priority)

Comments

@blckngm
Copy link

blckngm commented Nov 14, 2021

Describe the bug

When working with SSR, one would expect patterns like this to work:

if (import.meta.env.SSR) {
  const { data } = await import('./loadServer');
  console.log(data);
} else {
  const data = await fetch('/data').then((r) => r.text());
  console.log(data);
}

I.e., ignore loadServer for the browser bundle. It actually works fine with the dev server, but when loadServer (transitively) imports node builtin modules, vite build fails with e.g.:

Error: 'readFile' is not exported by __vite-browser-external, imported by loadServer.js

It would be nice if vite could just figure out that loadServer will never be needed for the browser bundle, and don't try to analyze/resolve code in it. It would also make dev and build behavior more consistent and might perform better in some cases.

I also tried this with esbuild and it does just ignore loadServer in this case:

❯ npx esbuild --format=esm --bundle main.js --define:import.meta.env.SSR=false
// main.js
if (false) {
  const { data } = await null;
  console.log(data);
} else {
  const data = await fetch("/data").then((r) => r.text());
  console.log(data);
}

Reproduction

https://stackblitz.com/edit/vitejs-vite-ts9jc6?devtoolsheight=33&file=main.js

System Info

System:
    OS: Linux undefined
    CPU: (4) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: Unknown - /bin/jsh
  Binaries:
    Node: 14.16.0 - /usr/local/bin/node
    Yarn: 1.22.10 - /bin/yarn
    npm: 7.17.0 - /bin/npm
  npmPackages:
    vite: ^2.6.13 => 2.6.14

Used Package Manager

npm

Logs

No response

Validations

@blckngm
Copy link
Author

blckngm commented Nov 15, 2021

This plugin mitigates the issue but breaks vue SFC styling:

// Replace import.meta.env.SSR in a pre plugin so that esbuild can eliminate
// dynamic imports in the dead branch.
function DefineSSR(): PluginOption {
  return {
    name: "define ssr",
    async transform(code, _id, ssr) {
      if (!ssr && code.includes("import.meta.env.SSR")) {
        return code.replace(/import.meta.env.SSR/g, "false");
      }
      return undefined;
    },
    enforce: "pre",
  };
}

@bluwy
Copy link
Member

bluwy commented Nov 15, 2021

That's strange. Vite does replace import.meta.env.SSR here. Could be an issue with the order of Vite plugins being ran 🤔

@blckngm
Copy link
Author

blckngm commented Nov 15, 2021

That's strange. Vite does replace import.meta.env.SSR here. Could be an issue with the order of Vite plugins being ran 🤔

Yes. Moving the define plugin before esbuild will solve this issue, but it breaks vue SFC styling.

@bluwy bluwy added the p3-minor-bug An edge case that only affects very specific usage (priority) label Mar 30, 2022
@ygj6
Copy link
Member

ygj6 commented Apr 20, 2022

Use the following configuration as a workaround to solve the problem, and the dead code is removed after a successful build.

import { defineConfig } from 'vite'
import { builtinModules } from 'module'

export default defineConfig({
  build: {
    rollupOptions: {
      external: builtinModules
    }
  }
})

This may not be the replacement of import.meta.env.SSR, because I just change it to false in the source code and still get the same error.

Perhaps we should adjust the order in which dead code is removed at build time, removing the dead code first and then performing subsequent build steps.

In dev, the imported module is handled by vite only when it is executed, where errors do not occur because it is not executed.

In build, Vite uses the vite:esbuild-transpile plug-in to perform tree-shaking, which is posted. Until then, dead code will still be processed by other plugins and rollup.

After some experimentation, I found that I couldn't avoid this behavior, either by bringing the tree-shaking of vite:esbuild-transpile forward (which would fail due to relying on the processing of the previous plugin) or changing the default behavior of rollup to avoid errors (which I'm not familiar with).

@DaniilIsupov
Copy link

I use crypto inside if (import.meta.env.SSR) { check and get an warning: [plugin:vite:resolve] Module "crypto" has been externalized for browser compatibility, imported by "/path-to-script.ts". See http://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.

in practice this code does not get into browser bundle, but this warning makes me worry

Info

vite/4.4.9 darwin-arm64 node-v20.5.1

@nestle49
Copy link

I use crypto inside if (import.meta.env.SSR) { check and get an warning: [plugin:vite:resolve] Module "crypto" has been externalized for browser compatibility, imported by "/path-to-script.ts". See http://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.

in practice this code does not get into browser bundle, but this warning makes me worry

Info

vite/4.4.9 darwin-arm64 node-v20.5.1

I have the same problem

@DaniilIsupov
Copy link

Problem is still relevant

Tnze added a commit to Tnze/ffxiv-best-craft that referenced this issue Oct 27, 2023
Tnze added a commit to Tnze/ffxiv-best-craft that referenced this issue Oct 27, 2023
@KTibow
Copy link

KTibow commented Oct 30, 2023

You could use define instead

@Tnze
Copy link

Tnze commented Oct 30, 2023

You could use define instead

How do you dynamic setting it? Like yarn vite build --define XXX?

@DaniilIsupov
Copy link

You could use define instead

i didn't notice the difference at all, warning remained

@KTibow
Copy link

KTibow commented Oct 30, 2023

@Tnze either have multiple config files or have your config file change the defined value based on the mode or environment variable or something
@DaniilIsupov we can't help you if you don't share your code and config

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has workaround p3-minor-bug An edge case that only affects very specific usage (priority)
Projects
None yet
Development

No branches or pull requests

7 participants