vitejs / vite

Next generation frontend tooling. It's fast!
http://vitejs.dev
MIT License
67.21k stars 6.04k forks source link

Dependency Pre-Bundling does not bundle styles #7719

Open ascott18 opened 2 years ago

ascott18 commented 2 years ago

Describe the bug

Dependency pre-bundling does not bundle CSS (or other css langs; in this case, SASS) imports within dependencies.

The result of this is that in serve mode when using component frameworks like Vuetify with per-component imports, each individual CSS file for each individual component is loaded with an independent HTTP request, even though all the scripts for these components are bundled into a single file.

This results in 100+ extra HTTP requests in serve mode.

Reproduction

https://github.com/ascott18/repro-vite-css-prebundling

System Info

System:
    OS: Windows 10 10.0.19044
    CPU: (16) x64 Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz   
    Memory: 12.01 GB / 31.94 GB
  Binaries:
    Node: 16.13.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.15.2 - C:\Program Files (x86)\Yarn\bin\yarn.CMD  
    npm: 8.1.3 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 100.0.4896.75
    Edge: Spartan (44.19041.1266.0), Chromium (100.0.1185.36)
    Internet Explorer: 11.0.19041.1566
  npmPackages:
    vite: 2.9.3 => 2.9.3

Used Package Manager

npm

Logs

(including image because I can't capture Chrome network tools output in a reasonable text format) image

Validations

ascott18 commented 2 years ago

After some tinkering, I was able to achieve the desired outcome here using the following configuration. Took a while to figure out since extensions is undocumented. (Other experimental features are documented, like SSR - is this an oversight?):

    optimizeDeps: {
      extensions: [".scss", ".sass"],
      esbuildOptions: {
        plugins: [
          (await import("esbuild-sass-plugin")).sassPlugin({
            type: "style",
            // Setting `logger.warn` to no-op is a workaround for
            // https://github.com/sass/sass/issues/3065#issuecomment-868302160.
            // Since `optimizeDeps` is only processing third party dependencies and only during dev,
            // we don't care about *any* deprecation warnings. The `quietDeps` SASS option doesn't work.
            logger: { warn() {} },
          }),
        ],
      },
    },

Would there ever be consideration to making this functionality baseline? Since build mode supports popular CSS preprocessors, I think it would make sense to also support them here in serve. I realize that different "glue" would have to be built to plug them into esbuild rather than rollup (as to not add third-party dependencies like I did in my config here). Is this something that would be worth my while to start a PR for?

bluwy commented 2 years ago

The extensions option is intentionally undocumented since it's not ready for public use, unlike SSR which is more on the stable side (though still experimental). I think prebundling styles is something worth exploring too, and it's great to see extensions covers this case too. I'm not sure there's any gotchas doing so at the moment, but if we can experiment this in userland as a Vite plugin to gather feedback, that would be helpful to justify bringing into core.

stratdev3 commented 1 year ago

@ascott18 thanks for the workaround.

One more precision

vuetify 2 is limited to sass v1.32, see discussion https://github.com/vuetifyjs/vuetify/issues/13694#issuecomment-852957010

To satisfy this dependencie, need to have { "esbuild-sass-plugin": "^1.7.0" } in package.json

ricardovanlaarhoven commented 1 year ago

Is there any news on this?

@ascott18 When i add your code to my vite.config.ts i'm getting the following error:

X [ERROR] Top-level await is currently not supported with the "cjs" output format

    vite.config.ts:39:9:
      39 │         (await import("esbuild-sass-plugin")).sassPlugin({
         ╵          ~~~~~

What am i doing wrong? i've installed the esbuild-sass-plugin package

ghost commented 1 year ago

@ricardovanlaarhoven I assume an object is passed to your defineConfig instead of an async function.

export default defineConifg(async () => ({ // now you can do await import }))

ricardovanlaarhoven commented 1 year ago

Thanks that helped (makes real sense. That i didn't think of that myself). Unfortunately it doesn't result in what i'd hoped for. Every time a new view is opened. It's loading really slow and eventually it fails and when i reload it works immediately

image

ghost commented 1 year ago

@ricardovanlaarhoven It's the same for me and on huge projects is very time consuming.

stratdev3 commented 1 year ago

@ricardovanlaarhoven

X [ERROR] Top-level await is currently not supported with the "cjs" output format

    vite.config.ts:39:9:
      39 │         (await import("esbuild-sass-plugin")).sassPlugin({
         ╵          ~~~~~

What am i doing wrong? i've installed the esbuild-sass-plugin package

I've had the same issue.

It's nodes and you need to edit your package.json to set type

{
  "type": "module",
}

It fix all my problem and vite/vue(2.7)/vuetify(2) perform well and fast at each request even on cold start.

ricardovanlaarhoven commented 1 year ago

vite.config

// Plugins
import vue from "@vitejs/plugin-vue";
import vuetify, { transformAssetUrls } from "vite-plugin-vuetify";

// Utilities
import { defineConfig } from "vite";
import { fileURLToPath, URL } from "node:url";

// https://vitejs.dev/config/
export default defineConfig(async () => ({
  cacheDir: "/etc/cache/vite",
  plugins: [
    vue({
      template: { transformAssetUrls },
    }),
    // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
    vuetify({
      autoImport: true,
      styles: {
        configFile: "src/styles/settings.scss",
      },
    }),
  ],
  define: {
    "process.env": {},
    __VUE_I18N_FULL_INSTALL__: true,
    __VUE_I18N_LEGACY_API__: false,
    __INTLIFY_PROD_DEVTOOLS__: false,
  },
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
    extensions: [".js", ".json", ".jsx", ".mjs", ".ts", ".tsx", ".vue"],
  },
  server: {
    host: true,
    port: 30002,
  },
  optimizeDeps: {
    extensions: [".scss", ".sass"],
    esbuildOptions: {
      plugins: [
        (await import("esbuild-sass-plugin")).sassPlugin({
          type: "style",
          // Setting `logger.warn` to no-op is a workaround for
          // https://github.com/sass/sass/issues/3065#issuecomment-868302160.
          // Since `optimizeDeps` is only processing third party dependencies and only during dev,
          // we don't care about *any* deprecation warnings. The `quietDeps` SASS option doesn't work.
          logger: { warn() {} },
        }),
      ],
    },
  },
}));

added to the package.json:

        "esbuild-sass-plugin": "^2.10.0",
        "esbuild": "^0.18.0"

and "type": "module",

result is still the same, with often a reload of the complete site.

image

ghost commented 1 year ago

@ricardovanlaarhoven Now Vue VLS 1.8.8 reports incompatible type for esbuild-sass-plugin inside the vite.config.ts

Vue: Type

import("/path/to/project/node_modules/esbuild/lib/main").Plugin

is not assignable to type

import("/path/to/project/node_modules/vite/node_modules/esbuild/lib/main").Plugin

Types of property setup are incompatible.

optimizeDeps: {
    extensions: [".scss", ".sass"],
    esbuildOptions: {
      plugins: [
        sassPlugin({ // 
          type: "style",
          logger: { warn() {} },
        }),
      ],
    },
 }

The current workaround I found:

optimizeDeps: {
    extensions: [".scss", ".sass"],
    esbuildOptions: {
      plugins: [
        sassPlugin({ // 
          type: "style",
          logger: { warn() {} },
        }),
      ],
    } as DepOptimizationConfig['esbuildOptions']
 }
sotirr commented 12 months ago

@ascott18 It's work but it's breaks the next settings:

css: {
    preprocessorOptions: {
      sass: {
        additionalData: [
          '@import "./src/styles/variables.sass"',
          '',
        ].join('\n'),
      }
    }
  },
ricardovanlaarhoven commented 7 months ago

Vuetify does only seem to have this problem when you use the config file option in the vite config.

  styles: {
    configFile: 'src/styles/settings.scss',
  },
})

This option is needed when doing component specific sass variable overrides. (which i need..)

Also

import { defineConfig, DepOptimizationConfig } from "vite";
import Vue from "@vitejs/plugin-vue";
import vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
import { fileURLToPath, URL } from "node:url";
import { sassPlugin } from "esbuild-sass-plugin";

if (process.env.NODE_ENV === "development") {
  const dotenv = await import("dotenv");
  dotenv.config({
    path: "./.env.development",
  });
}
const API_HOST_DEVELOPMENT = process.env.API_HOST_DEVELOPMENT;
const apiHost = API_HOST_DEVELOPMENT ? process.env.API_HOST_DEVELOPMENT.replace(/\/$/, "") : "";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    Vue({
      template: {
        transformAssetUrls,
      },
    }),
    // https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
    vuetify({
      styles: {
        configFile: "src/styles/settings.scss",
      },
    }),
  ],
  define: {
    "process.env": {},
  },
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
    extensions: [
      ".js",
      ".json",
      ".jsx",
      ".mjs",
      ".ts",
      ".tsx",
      ".vue",
    ],
  },
  server: {
    port: 4000,
    proxy: {
      "/api/": {
        target: apiHost,
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    },
  },

  optimizeDeps: {
    extensions: [".scss", ".sass"],
    esbuildOptions: {
      plugins: [
        sassPlugin({ //
          type: "style",
          logger: {
            warn () {},
          },
        }),
      ],
    } as DepOptimizationConfig["esbuildOptions"],
  },
});

The optimize deps doesn't seem to really speed thing up. It looks like its 7s where it was 10s pending, in a failry fresh project

ricardovanlaarhoven commented 5 months ago

With vuetify this specific time issue happens when you use vuetifys sass variables file AND use component specific sass variables.

So once you add this to the vite config:

export default defineConfig({
  plugins: [
    // https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
    vuetify({ styles: { configFile: "src/styles/settings.scss" } }),
  ],

and change @use 'vuetify' with ( into @use 'vuetify/settings' with ( in your settings.scss

I guess because every sass file vuetify imports needs to be recompiled to add the settings.scss above it.

I've had contact with the founder from vuetify, but they don't know how to resolve it and if they are doing something the wrong way, they like to know it.

Hopefully anyone knows how to handle this, in any way. Can i do something? Does vuetify have to do something? Does vite need to change anything?

NotAsea commented 2 months ago

I remove the option

 styles: {
        configFile: "src/styles/settings.scss",
      },

and i observe the web load near instant, my guess is include this scss file make Vite have to run sass compile all the sass file of vuetify thus make loading feel like century, another reason to have a nonstyle component like PrimeVue so we can swap to use more performance css system like tailwindcss or unocss

ricardovanlaarhoven commented 2 months ago

This option is necessary for component-specific SASS variable overrides. Webpack does not have this issue. I tried adding all components and SASS from Vuetify to the Vite server warmup configuration (https://vitejs.dev/config/server-options.html#server-warmup), but that doesn't seem to work either.

I'm wondering if this is a Vite issue or a Vuetify issue. I've spoken to @johnleider from Vuetify, and they are unsure what to do differently with the Vuetify loader. It would be great if someone from Vite could let us know if Vuetify needs to make any changes or if this is a Vite issue that we need to wait for a fix on.

And perhaps the label "has workaround" should be removed, or could anyone tell me what the workaround is, i've tried everything.

hi-ogawa commented 1 month ago

OP's reproduction is quite old (vite 2.9 and vuetify 2.4.8), so it would be great if someone can prepare a reproduction based on latest version of dependencies. That would help us verifying whether optimizeDeps.extensions based workaround is currently working on latest Vite.

Also orthogonal to this pre-bundling issue (which is about reducing the number of css requests from browser), there is an effort to improve sass processing in general by using latest feature of sass (and sass-embedded) package. It's not merged yet but hopefully this will be available soon (see performance comparison in the PR https://github.com/vitejs/vite/pull/17728#issuecomment-2247572134).

gduliscouet-ubitransport commented 1 month ago

Hello @hi-ogawa, I forked the repro here and tried to upgrade it without changing it too much.

From what I see we still have a request per sass file, but it is limited to the vuetify components used:

image

For the example I only used a few components from vuetify, but on a real project it can quickly go up

ricardovanlaarhoven commented 1 month ago

I've created a new vuetify project, added some sass variables and this is the result:

image

https://github.com/ricardovanlaarhoven/vuetify-vite-sass-speed-issues

Vuetify with vite speed issue

run pnpm install run pnpm dev go to localhost:300 go to the network tab and see a lot of pending sass/scss requests Once done, click on the Go to test link And once again see a lot of pending requests When you repeat this, the speed is excelent When you cut the server, and rerun pnpm dev, the speed is slow again for the first time.

Keep in mind that this is a really really small project, just the starter of vuetify with 3 simple components on the test page. My pc the loading time is about 4s here. On my real project, it's about 20s and after every new page with 1 simple new component again. This test project only has 1 page.

You could add some more pages and components to see the difference in speed. For exmaple adding VDataTable adds makes it go up to 11 seconds

hi-ogawa commented 1 month ago

Thanks for providing a reproduction. I had a look and it appears that there are a few intertwined issues, so try to comment on them separately.

First of all, pre-bundling scss/sass files using esbuild-sass-plugin should technically work at least for simple cases. I made a small example here https://github.com/hi-ogawa/reproductions/tree/main/vite-sass-pre-bundling and required configuration looks like this (which were briefly mentioned in https://github.com/vitejs/vite/issues/7719#issuecomment-1694483567)

import { defineConfig } from 'vite'
import { sassPlugin } from "esbuild-sass-plugin";

export default defineConfig({
  optimizeDeps: {
    extensions: [".scss", ".sass"],
    esbuildOptions: {
      plugins: [
        sassPlugin({
          // default `type: "css"` doesn't work since it emits separate css files
          // and removes css import from js files.
          // https://esbuild.github.io/content-types/#css-from-js
          // https://github.com/glromeo/esbuild-sass-plugin#type-style
          type: "style",
        }),
      ]
    }
  }
})

The reason why this approach is not working specifically for vuetify is that because it relies on vite plugin to rewrite original .css import into .sass virtual module https://github.com/vuetifyjs/vuetify-loader/blob/e1587d8d6b9da45d159ec5eee39cfa901bc61aca/packages/vite-plugin/src/stylesPlugin.ts#L54-L58. This mechanism is probably difficult to apply to pre-bundling which is based on esbuild.


2nd issue is that current Vite's default sass setup is not best for speed performance. As I mentioned in https://github.com/vitejs/vite/issues/7719#issuecomment-2255383411, it's planned to integrate improved sass compilation API inside Vite. Before this feature is available, there are already two ways you might be able to improve performance by using css.preprocessorMaxWorkers https://vitejs.dev/config/shared-options.html#css-preprocessormaxworkers or sass-embedded package via alias (cf. https://github.com/vitejs/vite/issues/6734#issuecomment-1492975259). This is the result when I tested https://github.com/ricardovanlaarhoven/vuetify-vite-sass-speed-issues on my machine:

default ![image](https://github.com/user-attachments/assets/1b888eef-9e07-4281-abd8-f4257eb67184)
preprocessorMaxWorkers: true ![Image](https://github.com/user-attachments/assets/fedd7deb-2b04-4c98-9b6d-44b09c4a9d75)
sass-embedded ![Image](https://github.com/user-attachments/assets/33676379-7438-42c9-b9f8-272cbdb9164a)

3rd issue is that you are likely experiencing frequent full-reload on navigation due to deps optimization triggered when discovering new dependencies in new pages. This is a different issue on its own and it happens when Vite's default heuristics is not able to discover dependencies used in user source code. For example, you can find a similar issue and potential solutions discussed on https://github.com/nuxt/nuxt/issues/26783 (I also remember other Vite frameworks like Sveltekit, Remix etc... experienced a similar issue). I tried optimizeDeps.exclude: ["vuetify"] in https://github.com/ricardovanlaarhoven/vuetify-vite-sass-speed-issues and indeed it looks like it can prevent spontaneous full-reload when navigating from / to /test (even though this will prevent pre-bundling and increase the number of js requests).


From what I can see, this issue having "has workaround" status seems right. I think it's still possible to improve vuetify (+ custom sass configFile) experience by figuring out 2nd and 3rd issues.

ricardovanlaarhoven commented 1 month ago

Thnx @hi-ogawa For taking so much time to investigate this with me. Perhaps @KaelWD from vuetify can read along with us.

I've tested it in my real-life project. I'm on a table page with already a lot of vuetify components, and going to a form page, it takes 48 seconds to load. When adding the following

   optimizeDeps: { exclude: ["vuetify"] },
   css: { preprocessorMaxWorkers: true },

it takes 9 seconds

When doing pnpm i -D sass@npm:sass-embedded@1.77.5 Mind that i have to change the version since vuetifyhas some deprecations (https://sass-lang.com/d/mixed-dec)

it takes 9 seconds

But i do still use the optimizeDeps and preprocessorMaxWorkers. And perhaps i'm not doing enough?

I'm grateful for the impressive time-cut.

a few points/questions:

hi-ogawa commented 1 month ago

adding the optimizeDeps has one downside, the first time you open the app, it goed to a white screen after loading. Then i can refresh the browser page and sometimes it's immidiatly there, sometimes i have to refresh one last time.

Figuring this out requires more domain knowledge of framework/library side and app specifics. I'm not familiar with Vue in general, so probably it's better to seek a help elsewhere.

did i do anything wrong with the sass embed workaround?

When using sass-embedded override, having preprocessorMaxWorkers: true might have a negative effect. Can you try removing it? (cf. https://github.com/hi-ogawa/test-vite-sass/issues/1#issuecomment-2249076746)

When to excpect the improbed sass api? vite 5.4? or 6 or?

Hopefully the improvement will be available in v5.4. One PR is merged https://github.com/vitejs/vite/pull/17728 and next one https://github.com/vitejs/vite/pull/17754 is also hopefully merged for 5.4.

ricardovanlaarhoven commented 1 month ago

When using sass-embedded override, having preprocessorMaxWorkers: true might have a negative effect. Can you try removing it? (cf. https://github.com/hi-ogawa/test-vite-sass/issues/1#issuecomment-2249076746)

Tried it a couple of times, results in about 12 seconds.

Which is also a huge time decrease. But less then the preprocessorMaxWorkers: true option.

userquin commented 1 month ago

@ricardovanlaarhoven can you update your repro to use latest vite 5.4.0-beta.0 and switch sass api to modern? I guess there is some sass cache, starting dev server and navigating takes only 3/4 seconds first time, next dev server restarts is faster:

  define: { 'process.env': {} },
  optimizeDeps: { exclude: ["vuetify"] },
  css: {
    preprocessorOptions: {
        sass: {
          api: 'modern'
        },
    },
    preprocessorMaxWorkers: true
  },

EDIT: removing preprocessorMaxWorkers is even faster (restarting dev server with root page takes 2/3 seconds, once loaded, navigating to test page is almost instant)

ricardovanlaarhoven commented 1 month ago

In my real life project, going to a second page just like my comment before https://github.com/vitejs/vite/issues/7719#issuecomment-2257880959

  1. with preprocessorMaxWorkers 7 sec
  2. with preprocessorMaxWorkers 9 sec
  3. without preprocessorMaxWorkers 12 sec
  4. with preprocessorMaxWorkers 40 sec
  5. without preprocessorMaxWorkers 4 sec
  6. without preprocessorMaxWorkers 3 sec
  7. without preprocessorMaxWorkers 2 sec
  8. without preprocessorMaxWorkers 2 sec

perhaps it has something to do with caching building up??

The start of the project itself seems better to, i just didnt measure it before and don't mind the startup being a bit slow, thats just once or twice a day.