{"id":359243,"date":"2022-01-11T07:38:06","date_gmt":"2022-01-11T15:38:06","guid":{"rendered":"https:\/\/css-tricks.com\/?p=359243"},"modified":"2022-01-11T07:38:09","modified_gmt":"2022-01-11T15:38:09","slug":"adding-vite-to-your-existing-web-app","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/adding-vite-to-your-existing-web-app\/","title":{"rendered":"Adding Vite to Your Existing Web App"},"content":{"rendered":"\n

Vite<\/a> (pronounced \u201cveet\u201d) is a newish JavaScript bundler. It comes batteries-included, requires almost no configuration to be useful, and includes plenty of configuration options. Oh\u2014and it\u2019s fast. Incredibly fast.<\/p>\n\n\n\n

This post will walk through the process of converting an existing project to Vite. We\u2019ll cover things like aliases, shimming webpack\u2019s dotenv handling, and server proxying. In other words, we\u2019re looking at how to move a project from its existing bundler to Vite. If you\u2019re looking instead to start a fresh project, you\u2019ll want to jump to their documentation<\/a>.<\/p>\n\n\n\n

Long story, short: the CLI will ask for your framework of choice\u2014React, Preact, Svelte, Vue, Vanilla, or even lit-html\u2014and whether you want TypeScript, then give you a fully functioning project.<\/p>\n\n\n\n\n\n\n\n

Scaffold first! If you are interested in learning about integrating Vite into a legacy project, I\u2019d still<\/em> recommend scaffolding an empty project and poking around it a bit. At times, I\u2019ll be pasting some clumps of code, but most of that comes straight from the default Vite template.<\/p>\n\n\n

Our use case<\/h3>\n\n\n

What we\u2019re looking at is based on my own experience migrating the webpack build of my booklist project<\/a> (repo<\/a>). There isn\u2019t anything particularly special about this project, but it\u2019s fairly big and old, and leaned hard on webpack. So, in that sense, it\u2019s a good opportunity to see some of Vite\u2019s more useful configuration options in action as we migrate to it.<\/p>\n\n\n

What we won\u2019t<\/em> need<\/h3>\n\n\n

One of the most compelling reasons to reach for Vite is that it already does a lot right out of the box, incorporating many of the responsibilities from other frameworks<\/a> so there are fewer dependencies and a more established baseline for configurations and conventions.<\/p>\n\n\n\n

So, instead of starting by calling out what we need to get started, let\u2019s go over all the common webpack things we don\u2019t need<\/em> because Vite gives them to us for free.<\/p>\n\n\n

Static asset loading<\/h4>\n\n\n

We usually need to add something like this in webpack:<\/p>\n\n\n\n

{\n  test: \/\\.(png|jpg|gif|svg|eot|woff|woff2|ttf)$\/,\n  use: [\n    {\n      loader: \"file-loader\"\n    }\n  ]\n}<\/code><\/pre>\n\n\n\n

This takes any references to font files, images, SVG files, etc., and copies them over to your dist folder so they can be referenced from your new bundles. This comes standard in Vite.<\/p>\n\n\n

Styles<\/h4>\n\n\n

I say “styles\u201d as opposed to \u201ccss\u201d intentionally here because, with webpack, you might have something like this:<\/p>\n\n\n\n

{\n  test: \/\\.s?css$\/,\n  use: [MiniCssExtractPlugin.loader, \"css-loader\", \"sass-loader\"]\n},\n\n\/\/ later\n\nnew MiniCssExtractPlugin({ filename: \"[name]-[contenthash].css\" }),<\/code><\/pre>\n\n\n\n

\u2026which allows the application to import CSS or<\/em> SCSS files. You\u2019ll grow tired of hearing me say this, but Vite supports this out of the box. Just be sure to install Sass itself into your project, and Vite will handle the rest.<\/p>\n\n\n

Transpilation \/ TypeScript<\/h4>\n\n\n

It\u2019s likely your code is using TypeScript, and or non-standard JavaScript features, like JSX. If that\u2019s the case, you\u2019ll need to transpile your code to remove those things and produce plain old JavaScript that a browser (or JavaScript parser) can understand. In webpack that would look something like this:<\/p>\n\n\n\n

{\n  test: \/\\.(t|j)sx?$\/,\n  exclude: \/node_modules\/,\n  loader: \"babel-loader\"\n},<\/code><\/pre>\n\n\n\n

\u2026with a corresponding Babel configuration to specify the appropriate plugins which, for me, looked like this:<\/p>\n\n\n\n

{\n  \"presets\": [\"@babel\/preset-typescript\"],\n  \"plugins\": [\n    \"@babel\/plugin-proposal-class-properties\",\n    \"@babel\/plugin-syntax-dynamic-import\",\n    \"@babel\/plugin-proposal-optional-chaining\",\n    \"@babel\/plugin-proposal-nullish-coalescing-operator\"\n  ]\n}<\/code><\/pre>\n\n\n\n

While I could have probably stopped using those first two plugins years ago, it doesn\u2019t really matter since, as I\u2019m sure you\u2019ve guessed, Vite does this all for us. It takes your code, removes any TypeScript and JSX, and produces code supported by modern browsers.<\/p>\n\n\n\n

If you\u2019d like to support older browsers (and I\u2019m not saying you should), then there\u2019s a plugin for that<\/a>.<\/p>\n\n\n

node_modules<\/code><\/h4>\n\n\n

Surprisingly, webpack requires you to tell it to resolve imports from node_modules<\/code>, which we do with this:<\/p>\n\n\n\n

resolve: {\n  modules: [path.resolve(\".\/node_modules\")]\n}<\/code><\/pre>\n\n\n\n

As expected, Vite already does this.<\/p>\n\n\n

Production mode<\/h4>\n\n\n

One of the common things we do in webpack is distinguish between production and development environments by manually passing a mode<\/code> property, like this:<\/p>\n\n\n\n

mode: isProd ? \"production\" : \"development\",<\/code><\/pre>\n\n\n\n

\u2026which we normally surmise with something like this:<\/p>\n\n\n\n

const isProd = process.env.NODE_ENV == \"production\";<\/code><\/pre>\n\n\n\n

And, of course, we set that environment variable via our build process.<\/p>\n\n\n\n

Vite handles this a bit differently and gives us different commands to run for development builds versus those for production, which we\u2019ll get into shortly.<\/p>\n\n\n

File extensions<\/h4>\n\n\n

At the risk of belaboring the point, I\u2019ll quickly note that Vite also doesn\u2019t require you to specify every file extension you\u2019re using.<\/p>\n\n\n\n

resolve: {\n  extensions: [\".ts\", \".tsx\", \".js\"],\n}<\/code><\/pre>\n\n\n\n

Just set up the right kind of Vite project, and you\u2019re good to go.<\/p>\n\n\n

Rollup plugins are compatible!<\/h3>\n\n\n

This is such a key point I wanted to call it out in its own section. If you still wind up with some webpack plugins you need to replace in your Vite app when you finish this blog post, then try to find an equivalent Rollup plugin and use that.<\/em> You read that correctly: Rollup plugins are already (or usually, at least) compatible with Vite. Some Rollup plugins, of course, do things<\/strong> that are incompatible with how Vite works\u2014but in general, they should just work.<\/p>\n\n\n\n

For more info, check out the docs<\/a>.<\/p>\n\n\n

Your first Vite project<\/h3>\n\n\n

Remember, we\u2019re moving an existing legacy webpack project to Vite. If you\u2019re building something new, it\u2019s better to start a new Vite project<\/a> and go from there. That said, the initial code I\u2019m showing you is basically copied right from what Vite scaffolds from a fresh project anyway, so taking a moment to scaffold a new project might also a good idea for you to compare processes.<\/p>\n\n\n

The HTML entry point<\/h4>\n\n\n

Yeah, you read that right. Rather than putting HTML integration behind a plugin, like webpack does, Vite is HTML first. It expects an HTML file with a script tag to your JavaScript entrypoint, and generates everything from there.<\/p>\n\n\n\n

Here\u2019s the HTML file (which Vite expects to be called index.html<\/code>) we\u2019re starting with:<\/p>\n\n\n\n

<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" \/>\n    <title>The GOAT of web apps<\/title>\n  <\/head>\n  <body>\n    <div id=\"home\"><\/div>\n    <script type=\"module\" src=\"\/reactStartup.tsx\"><\/script>\n  <\/body>\n<\/html><\/code><\/pre>\n\n\n\n

Note that the <script><\/code> tag points to \/reactStartup.tsx<\/code>. Adjust that to your own entry as needed.<\/p>\n\n\n\n

Let\u2019s install a few things, like a React plugin:<\/p>\n\n\n\n

npm i vite @vitejs\/plugin-react @types\/node<\/code><\/pre>\n\n\n\n

We also create the following vite.config.ts<\/code> right next to the index.html<\/code> file in the project directory.<\/p>\n\n\n\n

import { defineConfig } from \"vite\";\nimport react from \"@vitejs\/plugin-react\";\n\nexport default defineConfig({\n  plugins: [react()]\n});<\/code><\/pre>\n\n\n\n

Lastly, let\u2019s add a few new npm scripts:<\/p>\n\n\n\n

\"dev\": \"vite\",\n\"build\": \"vite build\",\n\"preview\": \"vite preview\",<\/code><\/pre>\n\n\n\n

Now, let\u2019s start Vite\u2019s development server with npm run dev<\/code>. It\u2019s incredibly fast, and incrementally builds whatever it needs to, based on what\u2019s requested.<\/p>\n\n\n\n

But, unfortunately, it fails. At least for right now.<\/p>\n\n\n\n

\"Screenshot<\/figure>\n\n\n\n

We\u2019ll get to how to set up aliases in a moment, but for now, let\u2019s instead modify our reactStartup<\/code> file (or whatever your entry file is called) as follows:<\/p>\n\n\n\n

import React from \"react\";\nimport { render } from \"react-dom\";\n\nrender(\n  <div>\n    <h1>Hi there<\/h1>\n  <\/div>,\n  document.getElementById(\"home\")\n);<\/code><\/pre>\n\n\n\n

Now we can run it our npm run dev<\/code> command and browse to localhost:3000<\/code>.<\/p>\n\n\n\n

\n
\n
\"Screenshot<\/figure>\n<\/div>\n\n\n\n
\n
\"Screenshot<\/figure>\n<\/div>\n<\/div>\n\n\n

Hot module reloading (HMR)<\/h4>\n\n\n

Now that the development server is running, try modifying your source code. The output should<\/em> update almost immediately via Vite\u2019s HMR. This is one of Vite\u2019s nicest features. It makes the development experience so much nicer when changes seem to reflect immediately rather than having to wait, or even trigger them ourselves.<\/p>\n\n\n\n

The rest of this post will go over all the things I had to do to get my own app to build and run with Vite. I hope some of them are relevant for you!<\/p>\n\n\n

Aliases<\/h4>\n\n\n

It\u2019s not uncommon for webpack-based projects to have some config like this:<\/p>\n\n\n\n

resolve: {\n  alias: {\n    jscolor: \"util\/jscolor.js\"\n  },\n  modules: [path.resolve(\".\/\"), path.resolve(\".\/node_modules\")]\n}<\/code><\/pre>\n\n\n\n

This sets up an alias to jscolor<\/code> at the provided path, and tells webpack to look both in the root folder (.\/<\/code>) and in node_modules<\/code> when resolving imports. This allows us to have imports like this:<\/p>\n\n\n\n

import { thing } from \"util\/helpers\/foo\"<\/code><\/pre>\n\n\n\n

\u2026anywhere in our component tree, assuming there\u2019s a util<\/code> folder at the very top.<\/p>\n\n\n\n

Vite doesn\u2019t allow you to provide an entire folder for resolution like this, but it does allow you to specify aliases, which follow the same rules as the @rollup\/plugin-alias<\/a>:<\/p>\n\n\n\n

import { defineConfig } from \"vite\";\nimport react from \"@vitejs\/plugin-react\";\n\nimport path from \"path\";\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      jscolor: path.resolve(\".\/util\/jscolor.js\"),\n      app: path.resolve(\".\/app\"),\n      css: path.resolve(\".\/css\"),\n      util: path.resolve(\".\/util\")\n    }\n  },\n  plugins: [react()]\n});<\/code><\/pre>\n\n\n\n

We\u2019ve added a resolve.alias<\/code> section, including entries for everything we need to alias. Our jscolor<\/code> util is set to the relevant module, and we have aliases for our top-level directories. Now we can import from app\/<\/code>, css*\/*<\/code>, and util\/<\/code> from any component, anywhere.<\/p>\n\n\n\n

Note that these aliases only apply to the root of the import, e.g. util\/foo<\/code>. If you have some other<\/em> util folder deeper in your tree, and you reference it with this:<\/p>\n\n\n\n

import { thing } from \".\/helpers\/util\";<\/code><\/pre>\n\n\n\n

\u2026then the alias above will not<\/em> mess that up. This distinction is not well documented, but you can see it in the Rollup alias plugin<\/a>. Vite\u2019s alias matches that same behavior.<\/p>\n\n\n

Environment variables<\/h4>\n\n\n

Vite, of course, supports environment variables<\/a>. It reads config values out of your .env<\/code> files in development, or process.env<\/code>, and injects them into your code. Unfortunately, things work a bit differently than what you might be used to. First, it does not replace process.env.FOO<\/code> but rather import.meta.env.FOO<\/code>. Not only that, but it only replaces variables prefixed with VITE_<\/code> by default. So, import.meta.env.VITE_FOO<\/code> would actually be replaced, but not my original FOO<\/code>. This prefix can be configured, but not set to empty string.<\/p>\n\n\n\n

For a legacy project, you could grep and replace all your environment variables to use import.meta.env<\/code>, then add a VITE_<\/code> prefix, update your .env<\/code> files, and update the environment variables in whatever CI\/CD system you use. Or you can configure the more classic behavior of replacing process.env.ANYTHING<\/code> with values from a .env<\/code> file in development, or the real process.env<\/code> value in production.<\/p>\n\n\n\n

Here\u2019s how. Vite\u2019s define<\/code><\/a> feature is basically what we need. This registers global variables during development, and does raw text replacement for production. We need to set things up so that we manually read our .env<\/code> file in development mode, and the process.env<\/code> object in production mode, and then add the appropriate define<\/code> entries.<\/p>\n\n\n\n

Let\u2019s build that all into a Vite plugin. First, run npm i dotenv<\/code>. <\/p>\n\n\n\n

Now let’s look at the code for the plugin:<\/p>\n\n\n\n

import dotenv from \"dotenv\";\n\nconst isProd = process.env.NODE_ENV === \"production\";\nconst envVarSource = isProd ? process.env : dotenv.config().parsed;\n\nexport const dotEnvReplacement = () => {\n  const replacements = Object.entries(envVarSource).reduce((obj, [key, val]) => {\n    obj[`process.env.${key}`] = `\"${val}\"`;\n    return obj;\n  }, {});\n\n  return {\n    name: \"dotenv-replacement\",\n    config(obj) {\n      obj.define = obj.define || {};\n      Object.assign(obj.define, replacements);\n    }\n  };\n};<\/code><\/pre>\n\n\n\n

Vite sets process.env.NODE_ENV<\/code> for us, so all we need to do is check that to see which mode we\u2019re in.<\/p>\n\n\n\n

Now we get the actual environment variables. If we\u2019re in production, we grab process.env<\/code> itself. If we\u2019re in dev, we ask dotenv to grab our .env<\/code> file, parse it, and get back an object with all the values.<\/p>\n\n\n\n

Our plugin is a function that returns a Vite plugin object. We inject our environment values into a new object that has process.env.<\/code> in front of the value, and then we return our actual plugin object. There is a number of hooks available to use. Here, though, we only need the config<\/code> hook, which allows us to modify the current config object. We add a define<\/code> entry if none exists, then add all our values.<\/p>\n\n\n\n

But before moving forward, I want to note that the Vite\u2019s environment variables limitations we are working around exist for a reason. The code above is how bundlers are frequently<\/em> configured, but that still means any random value in process.env<\/code> is stuck into your source code if that key exists. There are potential security concerns there, so please keep that in mind.<\/p>\n\n\n

Server proxy<\/h4>\n\n\n

What does your deployed web application look like? If all it\u2019s doing is serving JavaScript\/CSS\/HTML\u2014with literally everything happening via separate services located elsewhere\u2014then good! You\u2019re effectively done. What I\u2019ve shown you should be all you need. Vite\u2019s development server will serve your assets as needed, which pings all your services just like they did before.<\/p>\n\n\n\n

But what if your web app is small enough that you have some services running right on your web server? For the project I\u2019m converting, I have a GraphQL endpoint running on my web server. For development, I start my Express<\/a> server, which previously knew how to serve the assets that webpack generated. I also start a webpack watch task to generate those assets.<\/p>\n\n\n\n

But with Vite shipping its own dev server, we need to start that Express server (on a separate port than what Vite uses) and then proxy calls to \/graphql<\/code> over to there<\/em>:<\/p>\n\n\n\n

server: {\n  proxy: {\n    \"\/graphql\": \"http:\/\/localhost:3001\"\n  }\n} <\/code><\/pre>\n\n\n\n

This tells Vite that any requests for \/graphql<\/code> should be sent to http:\/\/localhost:3001\/graphql<\/code>.<\/p>\n\n\n\n

Note that we do not<\/strong> set the proxy to http:\/\/localhost:3001\/graphql<\/code> in the config. Instead, we set it to http:\/\/localhost:3001<\/code> and rely on Vite to add the \/graphql<\/code> part (as well any any query arguments) to the path.<\/p>\n\n\n

Building libs<\/h3>\n\n\n

As a quick bonus section, let\u2019s briefly discuss building libraries. For example, what if all you want to build is a JavaScript file, e.g. a library like Redux. There\u2019s no associated HTML file, so you\u2019ll first need to tell Vite what to make:<\/p>\n\n\n\n

build: {\n  outDir: \".\/public\",\n  lib: {\n    entry: \".\/src\/index.ts\",\n    formats: [\"cjs\"],\n    fileName: \"my-bundle.js\"\n  }\n}<\/code><\/pre>\n\n\n\n

Tell Vite where to put the generated bundle, what to call it, and what formats to build. Note that I\u2019m using CommonJS here instead of ES modules since the ES modules do not minify (as of this writing) due to concerns that it could break tree-shaking.<\/p>\n\n\n\n

You’d run this build with vite build<\/code>. To start a watch and have the library rebuild on change, you’d run <\/p>\n\n\n\n

vite build --watch<\/code>.<\/p>\n\n\n

Wrapping up<\/h3>\n\n\n

Vite is an incredibly exciting tool. Not only does it take the pain, and tears out of bundling web apps, but it greatly improves the performance of doing so in the process. It ships with a blazingly fast development server that ships with hot module reloading and supports all major JavaScript frameworks. If you do web development\u2014whether it\u2019s for fun, it\u2019s your job, or both!\u2014I can\u2019t recommend it strongly enough.<\/p>\n","protected":false},"excerpt":{"rendered":"

Vite (pronounced \u201cveet\u201d) is a newish JavaScript bundler. It comes batteries-included, requires almost no configuration to be useful, and includes […]<\/p>\n","protected":false},"author":250838,"featured_media":359254,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"Adding Vite to Your Existing Web App by @adamrackis","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"_share_on_mastodon":"0","_share_on_mastodon_status":"%title% %permalink%"},"categories":[4],"tags":[18951,680],"class_list":["post-359243","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-articles","tag-vite","tag-webpack"],"acf":{"show_toc":"No"},"share_on_mastodon":{"url":"","error":""},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/12\/webpack-vite.png?fit=1200%2C600&ssl=1","jetpack-related-posts":[{"id":337673,"url":"https:\/\/css-tricks.com\/the-deno-company\/","url_meta":{"origin":359243,"position":0},"title":"The Deno Company","author":"Chris Coyier","date":"April 2, 2021","format":false,"excerpt":"I'm sure a lot of you are paying attention to Deno anyway, the next-gen JavaScript-on-the-sever project from Node creator Ryan Dahl, especially after dropping all these candid regrets about what happened in Node. But perhaps you're paying more attention now that Deno has taken some seed investment and will be\u2026","rel":"","context":"In "Articles"","block_context":{"text":"Articles","link":"https:\/\/css-tricks.com\/category\/articles\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/04\/demo.jpg?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/04\/demo.jpg?fit=1200%2C600&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/04\/demo.jpg?fit=1200%2C600&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/04\/demo.jpg?fit=1200%2C600&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/04\/demo.jpg?fit=1200%2C600&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":358520,"url":"https:\/\/css-tricks.com\/some-notes-on-using-esbuild\/","url_meta":{"origin":359243,"position":1},"title":"Some notes on using esbuild","author":"Chris Coyier","date":"December 9, 2021","format":false,"excerpt":"This is a fantastic article from Julia Evans about duking it out with modern front-end tooling. Julia has made a bunch of Vue projects and typically uses no build process at all: \u00a0I usually have an\u00a0index.html\u00a0file, a\u00a0script.js\u00a0file, and then I do a\u00a0