Benchmarking vite vs webpack
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:
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:
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:
