vitejs / vite

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

'URL' named export breaks static file import in production build #11853

Open gergof opened 1 year ago

gergof commented 1 year ago

Describe the bug

When using a package (react-dnd-html5-backend in my case) that exports 'URL' as a named export, it will break the static file import (which uses new URL(...) in the production build).

Depending on the import order you will get "Cannot access 'URL' before initialization" or "'URL' is not a constructor" when running the packaged application.

It works perfectly fine in dev mode, but it breaks in production build.

Reproduction

https://stackblitz.com/edit/vitejs-vite-zguhve

Steps to reproduce

npm install

Then if you run npm run dev you will see the vite logo and under it the "URL named export" string.

But if you run npm run build and npm run preview you will see a blank white page indicating a broken react application. If you open the console, you can see the "Cannot access 'URL' before initialization" error message.

System Info

System:
    OS: Linux 4.19 Debian GNU/Linux 10 (buster) 10 (buster)
    CPU: (8) x64 Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
    Memory: 6.19 GB / 15.36 GB
    Container: Yes
    Shell: 5.0.3 - /bin/bash
  Binaries:
    Node: 16.19.0 - /usr/bin/node
    npm: 8.5.0 - /usr/bin/npm
  Browsers:
    Chrome: 109.0.5414.119
    Firefox: 102.7.0esr
  npmPackages:
    @vitejs/plugin-react: ^2.1.0 => 2.1.0 
    vite: ^3.2.5 => 3.2.5

Used Package Manager

npm

Logs

No response

Validations

gergof commented 1 year ago

As a workaround you can create a variable with the URL constructor somewhere and it will force esbuild to add a suffix for the named export.

frostzt commented 1 year ago

Hey! Lemme try picking this up. Also is it cool if I ask questions for a little bit of help if I get stuck?

faga295 commented 1 year ago

Waiting for an ingenious solution

frostzt commented 1 year ago

Tbh just was looking into it, vite is the bundler though uses Rollup I do believe its with Rollup since the transpilation happens there! I am not sure if I am correct though!

bluwy commented 1 year ago

The repro is using Vite 3, but I can confirm it happens in Vite 4 too. It's likely a bug in Rollup as it should internally alias the URL import to something else to not conflict with the global URL variable. It already does keep a list of known globals.

bluwy commented 1 year ago

Hmm actually looks like Rollup is working fine, perhaps Vite's dynamic usage of URL confuses Rollup, but I'm not sure how.

sun0day commented 1 year ago

Hmm actually looks like Rollup is working fine, perhaps Vite's dynamic usage of URL confuses Rollup, but I'm not sure how.

The problem seems vite render the new URL(...) with const URL = .... together, which may let rollup think they are the same variable.

...
const image = "__VITE_ASSET__24400c48__"; // __VITE_ASSET__ will be replace to new URL via asset plugin
const URL = 'xxxx'
...
faga295 commented 1 year ago

https://github.com/rollup/rollup/blob/master/src/utils/deconflictChunk.ts#L210 seems rollup resolve global variable conflict with this function

patak-dev commented 1 year ago

Would it be acceptable to switch to new globalThis.URL( instead of new URL(? Gzipped, the diff in size shouldn't be that big. And we could later optimize and remove the globalThis if there isn't a URL variable in the chunk, but I don't know if it is worth it.

BlakeB415 commented 1 year ago

This happens to me on Nuxt 3 for dependencies that use the url-parse module (in my case tus-js-client). I had to override the Vite version to 4.3.1 to get it to build properly.

quanglam2807 commented 1 year ago

Same issue when using Vite with uuid as uuid exports URL: https://github.com/uuidjs/uuid/blob/4de23a6030e65ac72b3b015680f08e7e292681ed/src/v35.js#L17

jjsearle commented 1 year ago

I am also experiencing this issue with a project that uses react-dnd-html5-backend and uuid, both export URL. They do not collide with each other, rollup recognises that conflict, but they do collide with the global URL.

Anyone have a work around for this issue?

jjsearle commented 1 year ago

Managed to workaround the issue by using @rollup-plugins/replace. It is less than ideal but got me going again.

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import svgr from 'vite-plugin-svgr'
import replace from '@rollup/plugin-replace'

export default defineConfig({
  base: './',
  plugins: [
    react(),
    svgr(),
    replace({
      'new URL(': 'new globalThis.URL(',
      delimiters: ['', ''],
      preventAssignment: true
    })
  ],
  server: {
    port: 3000,
    host: true
  },
  build: {
    outDir: './build',
    manifest: true,
    minify: true
  },
});
MaestroLegato commented 1 year ago

Managed to workaround the issue by using @rollup-plugins/replace. It is less than ideal but got me going again.

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import svgr from 'vite-plugin-svgr'
import replace from '@rollup/plugin-replace'

export default defineConfig({
  base: './',
  plugins: [
    react(),
    svgr(),
    replace({
      'new URL(': 'new globalThis.URL(',
      delimiters: ['', ''],
      preventAssignment: true
    })
  ],
  server: {
    port: 3000,
    host: true
  },
  build: {
    outDir: './build',
    manifest: true,
    minify: true
  },
});

This workaround worked for me, thanks!

If that helps - It happens to me with SvelteKit when importing bunch of icons from $lib. I tried to make reproduction repo but it seems to be dependent on other things that go into the bundle, when I tried to make contrived example it didn't behave like that. Vite 4.4.8 Svelte 4.1.2 SvelteKit 1.22.4

import AAA from '$lib/icons/AAA.svg';
import BBB from '$lib/icons/BBB.svg';
import CCC from '$lib/icons/CCC.svg';
import DDD from '$lib/icons/DDD.svg';
import EEE from '$lib/icons/EEE.svg';
import FFF from '$lib/icons/FFF.svg';

const iconMap: Record<string, string> = {
    AAA,
    BBB,
    CCC,
    DDD,
    EEE,
    FFF
};

export default iconMap;
quanglam2807 commented 9 months ago

It looks like this issue has been fixed in the latest version of Vite.

oyzhen commented 8 months ago

It looks like this issue has been fixed in the latest version of Vite.

Vite 5.1.6, the problem still exists.

rynoV commented 4 months ago

I think I'm also running into this on vite 5.3.3

Managed to workaround the issue by using @rollup-plugins/replace. It is less than ideal but got me going again.

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import svgr from 'vite-plugin-svgr'
import replace from '@rollup/plugin-replace'

export default defineConfig({
  base: './',
  plugins: [
    react(),
    svgr(),
    replace({
      'new URL(': 'new globalThis.URL(',
      delimiters: ['', ''],
      preventAssignment: true
    })
  ],
  server: {
    port: 3000,
    host: true
  },
  build: {
    outDir: './build',
    manifest: true,
    minify: true
  },
});

This fixed the issue for me, thanks!

jimmycallin commented 4 months ago

Also seeing this, with the proposed workaround resolving the issue. Seems to me like uuid's export of URL is the underlying issue.

pronebel commented 3 months ago

After adopting the replace scheme, an error occurred during loading after packaging when wasm was involved. However, the issue was resolved by using manualChunks to output react-dnd as a separate file. It seems that by packaging react-dnd as an independent file, the closure within react-dnd handled the pollution of URL, thus not affecting the URL processing of resources like images.

AS below:

manualChunks(id, { getModuleInfo, getModuleIds }) {          
   if (id.includes('react-dnd')) {
     return 'react-dnd';
   }    
}     
gergof commented 3 months ago

The easiest fix workaround that worked for me back then and still works is to place this in my entry file (index.tsx in my case):

// TODO: remove this when this issue gets resolved: https://github.com/vitejs/vite/issues/11853
const _urlFix = new URL('https://example.com');