Benchmarking vite vs webpack

Benchmarking vite vs webpack
Photo by Ralfs Blumbergs / Unsplash

I still use Webpack for most of my projects, but Vite is rapidly becoming the standard build tool. As I mentioned in earlier posts, I still encounter a few issues with it:

Why I’m not switching to vite yet
Twisted paths While searching for a markdown based Static-Site Generation (SSG) to build a documentation page I came across vuepress, first I started with vuepress 1 but since I wanted to install it in my existing project (which uses vue 3 and vuepress 1 does not support vue 3) I
1 year later - state of vuepress and vite
In my post Why I’m not switching to vite yet almost exactly a year ago I explained why switching to vite was not an option to me yet. Some things have changed so let’s check again! vitepress and vuepress As stated on the website, they have settled on focussing on

But recently I had a nice opportunity to try vite, in my vue component library "hop" more specific in the documentation for it. You can read more about that here:

Vue Component Workbench
In my projects I’ve been using my vue/scss framework “hop”. Whenever I add new components or change something, it is quite helpful to have an instance of webpack running inside the hop project to see my changes in realtime instead of publishing a new version and updating the depending

Why?

I wanted to find out if I gain any advantage by switching to vite and this seemed like a good test project for this.

Switching to vite

Moving to Vite turned out to be easier than I expected:

  • Remove all of the Webpack dependencies (clean-webpack-plugin, copy-webpack-plugin, css-loader, file-loader, html-webpack-plugin, sass, sass-loader, source-map-loader, style-loader, vue-loader, webpack-cli, webpack-dev-server).
  • Install vite and @vitejs/plugin-vue.
  • Create a vite.config.js that matches my use case.

Special Cases and How to Handle Them

Vite in a Subfolder

My documentation lives in the docs folder, while my Webpack config was at the project root. With Vite I can simply run vite docs via an npm script. This tells Vite to treat docs as the root directory, so the build runs correctly.

Custom Distribution Folder

I want the production build to go to docs-dist. With both Webpack and Vite this is just a configuration setting, but because the output folder is outside the Vite root, I need to set emptyOutDir: true. Vite does not clean an out‑directory that lies outside its root by default.

Sass Package Import

Vite doesn’t understand the pkg: syntax that some Sass projects use. I removed the prefix, and it still worked fine in the Vite‑based docs and in other projects that use the same package.

Dynamic Imports

I had a dynamic import that looked like this:

 import("../components/" + props.of + ".vue").then((res) => {

Which conflicts with the rollup dynamic import limitations. But the solution was as easy as switching to the template syntax:

import(`../components/${props.of}.vue`).then((res) => {

Custom Plugins

In my docs I used custom Webpack plugins to automatically parse all Vue components in a folder and generate documentation. The plugin reads props, JSDoc comments, events, methods, and slots from the components and renders a comprehensive table on the docs site.

To make this work with Vite I had to rewrite the plugin to use the Rollup plugin syntax, which Vite supports. I let an LLM do the conversion for me, but vite introduced a new issue: with Webpack I generated a components.json file in the output directory and fetched it with JavaScript on the front end. Vite, however, does not write assets to disk by default, and adding an asset via a plugin confused the module tree.

The solution was to create a virtual module. By giving the module a special ID (e.g., virtual:components-json), Vite can serve it as if it were a real file, and the front‑end code can import it normally. This keeps the workflow similar to the Webpack setup while staying within Vite’s capabilities.

const virtualModuleId = "hopdocs:components";
const resolvedVirtualModuleId = "\0" + virtualModuleId;

return {
    name: "hopdocs-components",

    async buildEnd() {
        await generateComponents();
    },

    async handleHotUpdate({ file, server }) {
        if (file.startsWith(component_path) && file.endsWith(".vue")) {
            await generateComponents();
            server.ws.send({
                type: "full-reload",
                path: "*"
            });
            this.warn(`components.json updated due to change in ${file}`);
        }
    },

    resolveId(id) {
        if (id === virtualModuleId) {
            return resolvedVirtualModuleId;
        }
    },

    load(id) {
        if (id === resolvedVirtualModuleId) {
            return `export default ${JSON.stringify(componentsData)}`;
        }
    }
};

Which means we can simply import it directly instead of using fetch:

import components from "hopdocs:components";

🏁Benchmarking

Metric Vite Webpack
Project setup (fresh npm install) 2.604 s 🏆 3.639 s
Dev build startup 877.5 ms 🏆 2.067 s
Prod build 3.724 s 🏆 8.454 s
Prod build folder total size 1084.04 KB 🏆 1124.4 KB
Lighthouse – First Contentful Paint 1.1 s 0.2 s 🏆
Lighthouse – Largest Contentful Paint 1.2 s 🏆 1.3 s
Lighthouse – Total Blocking Time 0 ms 0 ms
Lighthouse – Speed Index 1.1 s 0.6 s 🏆
Dev console – DOMContentLoaded 99 ms 🏆 111 ms
Dev console – Finish 263 ms 🏆 437 ms
Dev console – Transferred 998 kB 🏆 1.1 MB

As you can see vite is better in almost every test, except for the First Contentful Paint (FCP) which is odd. I performed the Lighthouse and Dev console page load tests by simply serving the folder using npm http-server which might have impact on the speed.

After I deployed the project to my server (served via nginx) Lighthouse seemed quite happy though: