laravel / vite-plugin

Laravel plugin for Vite.
MIT License
804 stars 152 forks source link

Stylesheets imported multiple times at runtime after build #294

Closed apademide closed 7 months ago

apademide commented 7 months ago

Vite Plugin Version

1.0.2

Laravel Version

10.10

Node Version

18.19.1

NPM Version

10.2.4

Operating System

macOS

OS Version

14.2.1

Web browser and version

Safari Version 17.2.1 (19617.1.17.11.12), Chrome Version 123.0.6312.107 (Official Build) (arm64)

Running in Sail?

No

Description

The main app-xxxxxx.css stylesheet is included twice on the page when Vite's lazy loading is used, causing styling issues with specificity.

Stack: Inertia + Solid JS (CSR)

We are using CSS modules which means selectors are always unique classnames sharing the same specificity, thus the styles declaration order is the only thing that defines what styles will be applied. We first noticed the inconsistency because the built styles were different than the styles in dev mode.

Here you can see the head after rendering. The orange part is what's generated by the Blade view and sent to the frontend. The blue part is what's generated dynamically by Vite at runtime. As you can see, the app css bundle is once preloaded then imported by Blade; expected. However it's then imported again after the local styles chunk, overriding it and breaking the layout.

Capture d’écran 2024-04-04 à 18 25 22 2

For context, everything that comes before <style type="text/css">…</style> are the synchronous dependencies of the page, and everything after are lazy assets.

Steps To Reproduce

The bug felt unpredictable at first, but it's actually connected to Vite's lazy imports in some way. The duplicated CSS import happens at runtime when a dynamic (lazy loaded) component is loaded.

A component must be lazily imported by Vite, using any of the allowed syntax: const component = () => import('component.tsx'), const components = import.meta.glob("./*.tsx"), then call the fetcher. The lazy part is important: {eager:true}-imports do not cause this issue.

@vite('resources/frontend/app.tsx')+@vite('resources/frontend/app.scss') or @vite('resources/frontend/app.tsx') and importing the stylesheet from the JS file has no difference.

I initially thought it could be related to Solid's implementation of lazy components or the Inertia adapter, but the most basic import (without even having to be used anywhere after being fetched) triggered the duplicate import. I also checked with a minimal Vite+Solid starter kit and couldn't replicate; it must be related to how Blade and Vite work together.

The component importing does not have to import styles. I could reproduce the bug with a component file as simple as

export default function() {
  return <div></div>
}

I am not aware enough of the internals to determine the exact cause, nor debug this further. My gut feeling would be that Vite is "not aware" that the app.css bundle is imported at all, and when a lazy component is loaded its module requirements are then loaded as well. Because app.css is required by all modules and Vite doesn't know Blade imports it, it gets re-added. This would explain why the empty component still triggers that addition.

Please let me know if you need any more context or information.

timacdonald commented 7 months ago

Hey there,

The initial two references are expected. The first is the preload directive and second is the stylesheet itself we output.

If I had to guess I would say that the others are being inserted because the first two contain a prefix URL and the last one does not,so the tags do not match.

That being said, the browser will see these as a noop because it will know they resolve to the same file.

Inserting of tags at runtime by Vite is not something we have control over. You would need to open an issue upstream, however I don't really see this as a bug.

mshamaseen commented 1 month ago

This is also happens to me, it become an issue when I'm trying to add unocss classes in the code that conflicts with the one in app.scss , unocss suppose to have priority over app.scss, but because app.scss is added multiple times (and one at the end) it override the unocss ones.

apademide commented 1 month ago

@mshamaseen The issue with the multiple occurrences of the same import is caused by URL mismatch. If you have your backend including absolute URLs and frontend including relative ones for example, the frontend ends up re-adding the ones included by the backend (myapp.com/asset != /asset)

The fix for me was to update my frontend to build absolute URLs. I did this by setting this value in my vite.config.ts: base: mode === "production" ? resolve(env.APP_URL, buildDirectory) : undefined,