vitejs / vite

Next generation frontend tooling. It's fast!
http://vite.dev
MIT License
68.31k stars 6.16k forks source link

Bundle requests twice in Firefox 93 with default polyfillModulePreload enabled #5532

Closed imShara closed 1 year ago

imShara commented 2 years ago

Describe the bug

I have found that default vite build makes bundle request twice in firefox.

2021-11-03_02-54

When I switch off build.polyfillModulePreload: false it makes only one request. Why this happens? Vite docs says that polyfill need for firefox and safari, Can I Use says that modulepreload not supported in Firefox. Is this proper polyfill work?

There is no problem in Chrome or Safari

Reproduction

pnpm create vite test
> vue
> vue-ts
cd test
pnpm install
pnpm  build
pnpm serve

Open http://127.0.0.1:5000/ in Firefox 93.0 (64-bit) Check dev tools F12 -> Network ->Reload

System Info

System:
    OS: Linux 5.14 Arch Linux
    CPU: (16) x64 AMD Ryzen 7 5800H with Radeon Graphics
    Memory: 19.28 GB / 27.27 GB
    Container: Yes
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.11.1 - /usr/bin/node
    Yarn: 1.22.17 - /usr/bin/yarn
    npm: 8.1.2 - /usr/bin/npm
  Browsers:
    Firefox: 93.0

Used Package Manager

pnpm

Logs

▶ pnpm vite build --debug
  vite:config bundled config file loaded in 89.20ms +0ms
  vite:config using resolved config: {
  vite:config   plugins: [
  vite:config     'alias',
  vite:config     'vite:modulepreload-polyfill',
  vite:config     'vite:resolve',
  vite:config     'vite:html-inline-script-proxy',
  vite:config     'vite:css',
  vite:config     'vite:esbuild',
  vite:config     'vite:json',
  vite:config     'vite:wasm',
  vite:config     'vite:worker',
  vite:config     'vite:asset',
  vite:config     'vite:vue',
  vite:config     'vite:define',
  vite:config     'vite:css-post',
  vite:config     'vite:build-html',
  vite:config     'commonjs',
  vite:config     'vite:data-uri',
  vite:config     'rollup-plugin-dynamic-import-variables',
  vite:config     'vite:asset-import-meta-url',
  vite:config     'vite:build-import-analysis',
  vite:config     'vite:esbuild-transpile',
  vite:config     'vite:reporter',
  vite:config     'vite:load-fallback'
  vite:config   ],
  vite:config   build: {
  vite:config     target: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ],
  vite:config     polyfillModulePreload: true,
  vite:config     outDir: 'dist',
  vite:config     assetsDir: 'assets',
  vite:config     assetsInlineLimit: 4096,
  vite:config     cssCodeSplit: true,
  vite:config     cssTarget: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ],
  vite:config     sourcemap: false,
  vite:config     rollupOptions: {},
  vite:config     minify: 'esbuild',
  vite:config     terserOptions: {},
  vite:config     write: true,
  vite:config     emptyOutDir: null,
  vite:config     manifest: false,
  vite:config     lib: false,
  vite:config     ssr: false,
  vite:config     ssrManifest: false,
  vite:config     reportCompressedSize: true,
  vite:config     chunkSizeWarningLimit: 500,
  vite:config     watch: null,
  vite:config     commonjsOptions: { include: [Array], extensions: [Array] },
  vite:config     dynamicImportVarsOptions: { warnOnError: true, exclude: [Array] }
  vite:config   },
  vite:config   define: { __VUE_OPTIONS_API__: true, __VUE_PROD_DEVTOOLS__: false },
  vite:config   ssr: { external: [ 'vue', '@vue/server-renderer' ] },
  vite:config   configFile: '/home/user/test/vite.config.ts',
  vite:config   configFileDependencies: [ 'vite.config.ts' ],
  vite:config   inlineConfig: {
  vite:config     root: undefined,
  vite:config     base: undefined,
  vite:config     mode: undefined,
  vite:config     configFile: undefined,
  vite:config     logLevel: undefined,
  vite:config     clearScreen: undefined,
  vite:config     build: {}
  vite:config   },
  vite:config   root: '/home/user/test',
  vite:config   base: '/',
  vite:config   resolve: { dedupe: undefined, alias: [ [Object], [Object] ] },
  vite:config   publicDir: '/home/user/test/public',
  vite:config   cacheDir: '/home/user/test/node_modules/.vite',
  vite:config   command: 'build',
  vite:config   mode: 'production',
  vite:config   isProduction: true,
  vite:config   server: { fs: { strict: undefined, allow: [Array] } },
  vite:config   env: { BASE_URL: '/', MODE: 'production', DEV: false, PROD: true },
  vite:config   assetsInclude: [Function: assetsInclude],
  vite:config   logger: {
  vite:config     hasWarned: false,
  vite:config     info: [Function: info],
  vite:config     warn: [Function: warn],
  vite:config     warnOnce: [Function: warnOnce],
  vite:config     error: [Function: error],
  vite:config     clearScreen: [Function: clearScreen],
  vite:config     hasErrorLogged: [Function: hasErrorLogged]
  vite:config   },
  vite:config   createResolver: [Function: createResolver],
  vite:config   optimizeDeps: {
  vite:config     esbuildOptions: { keepNames: undefined, preserveSymlinks: undefined }
  vite:config   }
  vite:config } +6ms
vite v2.6.13 building for production...
✓ 14 modules transformed.
dist/assets/logo.03d6d6da.png    6.69 KiB
dist/index.html                  0.48 KiB
dist/assets/index.e40264f2.js    1.95 KiB / gzip: 1.03 KiB
dist/assets/index.459f8680.css   0.34 KiB / gzip: 0.24 KiB
dist/assets/vendor.dac2b406.js   49.61 KiB / gzip: 19.93 KiB

Validations

SergeiMinaev commented 2 years ago

The same here. It looks like this polyfill doesn't work in Firefox.

imShara commented 2 years ago

~Actually, it looks like polyfill works AND native import works too. I can see no problems when polyfill disabled.~

It's wrong, see below.

imShara commented 2 years ago

Okay, I have made minimal reproducible example of polyfill used in vite and confirm that it makes two requests by design.

First, dummy, request makes polyfill with fetch(link) function, which used to cache resource. Second request makes import from link when module loads at first time in application runtime.

index.html

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <link rel="modulepreload" href="module.mjs">
</head>

<body>
  Hello

  <script>
    function processPreload(link) {
      const fetchOpts = {};
      if (link.integrity)
        fetchOpts.integrity = link.integrity;
      if (link.referrerpolicy)
        fetchOpts.referrerPolicy = link.referrerpolicy;
      if (link.crossorigin === 'use-credentials')
        fetchOpts.credentials = 'include';
      else if (link.crossorigin === 'anonymous')
        fetchOpts.credentials = 'omit';
      else
        fetchOpts.credentials = 'same-origin';

      fetch(link.href, fetchOpts).then(res => res.ok && res.arrayBuffer());
    }

    for (const link of document.querySelectorAll('link[rel=modulepreload]')) {
      processPreload(link);
    }
  </script>

  <script type="module">
    import { test } from './module.mjs';
    test('yay');
  </script>
</body>

</html>

module.mjs

export function test(message) {
  alert(message)
}

access.log

"GET / HTTP/2.0" 200 919 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0"
"GET /module.mjs HTTP/2.0" 200 50 "https://modulepreload.local/" "Mozilla/5.0 (X11; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0"
"GET /module.mjs HTTP/2.0" 200 50 "https://modulepreload.local/" "Mozilla/5.0 (X11; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0"
"GET /favicon.ico HTTP/2.0" 404 153 "https://modulepreload.local/" "Mozilla/5.0 (X11; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0"

I think it's can't be fixed. Any suggestions?

kunKun-tx commented 2 years ago

Can confirm this issue also occurs on Firefox 95.0.2. It's a simple React project using Vite and Firefox is downloading all non index.js assets twice.

Kadeluxe commented 2 years ago

So what is the workaround for that? I created empty Vue project using pnpm create vite, did cd project && pnpm build and it indeed makes double requests for all js files except index.js in Firefox 95.0.2. It does double requests even if I use build.polyfillModulePreload: false. What's the deal?

Edit: actually if I turn off build.polyfillModulePreload: false then it only does double requests for dynamically imported modules because it generates the following code:

function __variableDynamicImportRuntime0__(path) {
  switch (path) {
    case "../../pages/overview.vue":
      return __vitePreload(() => import("./overview.85cf6486.js"), true ? ["assets/overview.85cf6486.js","assets/vendor.0dbf3cec.js"] : void 0);
    default:
      return new Promise(function(resolve, reject) {
        (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(reject.bind(null, new Error("Unknown variable dynamic import: " + path)));
      });
  }
}

Notice how module has dependency on itself. That's why it would load twice, first by doing dependency loading and then by doing browser import.

Zsapi commented 2 years ago

I've just noticed this myself, and although it is not causing any measurable harm in my case, it is really messing with me. Vite version 2.8.4, Firefox 97.0.1. I'm using Vue, and the vendor js file is downloaded twice.

sutarmin commented 2 years ago

Same for me, vendor.js is loaded twice and that bothers me a lot!

fahidmohammad commented 2 years ago

Any update on this issue? not only bundle, but every file is loading twice.

image
sapphi-red commented 2 years ago

I think this is because maxAge is not set here. https://github.com/vitejs/vite/blob/9a932339aae8bfbb9f3b522706c551a21a1eea3a/packages/vite/src/node/preview.ts#L93 If there is no Cache-Control header, browser needs to revalidate the response.

gangsthub commented 2 years ago

It seems that Vite is trying to be smart with this option. It’s a recommended optimization technique, so the JS chunks and their dependencies are loaded in parallel, instead of in waterfall.

First, they download (modulepreload) the assets, then you see a second request because it's trying to import native JS modules in the browser.

The fact that we see these duplicated requests happening on Safari and Firefox is because it relies on (a polyfill of) a feature that is only available in Chromium browsers. More info: https://developer.chrome.com/blog/modulepreload/

I wouldn’t worry about non-Chromium browsers for now, since the second request is always returned from cache.

I think it’s a good default we can have for now. Even if it is bothering to see in the Network tab.

kito-inv commented 2 years ago

I wouldn’t worry about non-Chromium browsers for now, since the second request is always returned from cache.

That doesn't always happen sadly, if the first request hasn't finished before the second one started then the second one is not returned from cache

mihai-sysbio commented 2 years ago

since the second request is always returned from cache

On the latest Firefox (macOS) I do see the data transferred twice, so, as @kito-inv mentioned above, it's not cached.

I think it’s a good default we can have for now. Even if it is bothering to see in the Network tab.

According to the caniuse data, ~25% of the userbase may be affected. Personally, it's not really a percentage I am comfortable with on a production server for a default behavior.

tarasovsn commented 1 year ago

The same problem, but in Firefox 102.5.0esr (64-bit) (Mac OS Monterey 12.6.1)

screenshot

In Safari and Chrome there is no such problem.

owlas commented 1 year ago

I'm seeing the behaviour in both safari and Firefox but not chrome. Loading vendor files are a big chunk of load time so this really affects the user experience in these two browsers:

Screenshot from safari:

Screenshot 2023-01-20 at 13 33 07
EthanML commented 1 year ago

Chiming in to say I see the same issue on Firefox 109.0 for Mac OS 👎 Same issue with Safari, but not in Chrome.

Screenshot 2023-01-26 at 13 19 38

Could we get confirmation on what the recommended course of action here is? If we disable the preload polyfill option as described above by the original poster, is that going to cause issues elsewhere? If so, what?


Edit: Found my way to https://github.com/vitejs/vite/pull/9938 and followed instructions there to disable this feature. I believe this issue should be closed and pointed to that PR.

hadson172 commented 1 year ago

Having same issues, any updates on the topic?

I am using dynamic imports for subpages and react router. On each of the browser this is what i am getting:

obraz

There should be single request for dynamic import not two of those.

Shakeskeyboarde commented 1 year ago

Same issue. I've tried disabling optimizeDeps and modulePreload. Still happening. I'm fully capable of parallelizing/preloading my own promises, thanks 🙄. This isn't something that it should be generating code for and trying to do automatically.

Teamoh commented 1 year ago

I am having the same behavior, no matter if modulePreload is polyfilled or not.

It looks like this is not an issue from Vite, but a behavior (bug?) from Firefox.

When doing a dynamic import (import(...) Firefox ignores the fact, that this module was already preloaded using <link rel="preload">.

So either this is a bug in Firefox, or it will be repaired as soon as Firefox starts supporting rel=modulepreload.

But if you modifiy the generated index.js to always use rel=preload even in Chrome, it will still work in Chrome (i.e. load only once), so there is definitely a different behavior between browsers here and Firefox should understand that the module was already loaded even today.

I also tested to change the Cache-Control header using preview.headers which defaults to no-cache but this doesn't fix it in Firefox.

Teamoh commented 1 year ago

I would like to be able to disable the preload feature completly by setting build.modulePreload to false, but as soon as I do that, my CSS is not loaded anymore as a dependency. The CSS files are correctly built, but they are not referenced anymore in any of the generated JS files.

Kadeluxe commented 1 year ago

It's not a Firefox fault, as I stated in my comment there is a clear reason why this is not working properly.

mohammad1111 commented 1 year ago

I'm seeing the behaviour in both safari and Firefox but not chrome. Loading vendor files are a big chunk of load time so this really affects the user experience in these two browsers:

Screenshot from safari:

Screenshot 2023-01-20 at 13 33 07

I have the same problem as you. Did you solve this problem? plz guide me.

mihai-sysbio commented 1 year ago

Bumping this as it is still unresolved for Firefox.

asvishnyakov commented 1 year ago

I have exactly the same behavior for Chrome with vite 4.4.8, but only in dev mode. production is fine. Also modulePreload: { polyfill: false } doesn't help

adbenitez commented 1 year ago

notice that this is not only about network traffic, I am migrating a project to vite and there a canvas is added to the body in a index.ts script, I noticed that 2 canvas were added to the body and was scratching my head until I realized it was vite loading the same script twice

iamphonghg commented 1 year ago

Hey folks,

I just switched from webpack to vite (using vite for react in laravel 6) and encountered the same thing and found a solution.

Files that are dynamically imported will be called twice. I don't know if your case is the same as mine, but I noticed that the Request URL of the two calls is not the same: 1st time: ...domain/assets/chunk-file.js, this is wrong, returns the html of the web. 2nd time: ...domain/dist/assets/chunk-file.js, this is correct, returns chunk file.

My public folder structure looks like this: /public/dist/assets/[chunk js and css]

And a similar problem with the CSS file of that chunk file, it is only loaded once like the 1st of the chunk file: ...domain/assets/css-of-chunk-file.css, this is not correct so Can't get css for chunk file.

And I added 'base' config like this: Before:

build: {
     manifest: true,
     input: 'resources/js/index.jsx',
     outDir: 'public/dist',
     copyPublicDir: false
},

After:

 base: '/dist/',
 build: {
     manifest: true,
     input: 'resources/js/index.jsx',
     outDir: 'public/dist',
     copyPublicDir: false
  }

Dynamic chunks are now only loaded once and CSS is also loaded correctly. Hope it will be useful to someone.

benmccann commented 1 year ago

Fixed in Firefox 115: https://caniuse.com/link-rel-modulepreload