{"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 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 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 We usually need to add something like this in webpack:<\/p>\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 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 \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 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 \u2026with a corresponding Babel configuration to specify the appropriate plugins which, for me, looked like this:<\/p>\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 Surprisingly, webpack requires you to tell it to resolve imports from As expected, Vite already does this.<\/p>\n\n\n One of the common things we do in webpack is distinguish between production and development environments by manually passing a \u2026which we normally surmise with something like this:<\/p>\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 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 Just set up the right kind of Vite project, and you\u2019re good to go.<\/p>\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 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 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 Note that the Let\u2019s install a few things, like a React plugin:<\/p>\n\n\n\n We also create the following Lastly, let\u2019s add a few new npm scripts:<\/p>\n\n\n\n Now, let\u2019s start Vite\u2019s development server with But, unfortunately, it fails. At least for right now.<\/p>\n\n\n\n We\u2019ll get to how to set up aliases in a moment, but for now, let\u2019s instead modify our Now we can run it our 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 It\u2019s not uncommon for webpack-based projects to have some config like this:<\/p>\n\n\n\n This sets up an alias to \u2026anywhere in our component tree, assuming there\u2019s a 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 We\u2019ve added a Note that these aliases only apply to the root of the import, e.g. \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 Vite, of course, supports environment variables<\/a>. It reads config values out of your For a legacy project, you could grep and replace all your environment variables to use Our use case<\/h3>\n\n\n
What we won\u2019t<\/em> need<\/h3>\n\n\n
Static asset loading<\/h4>\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\nStyles<\/h4>\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\nTranspilation \/ TypeScript<\/h4>\n\n\n
{\n test: \/\\.(t|j)sx?$\/,\n exclude: \/node_modules\/,\n loader: \"babel-loader\"\n},<\/code><\/pre>\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\nnode_modules<\/code><\/h4>\n\n\nnode_modules<\/code>, which we do with this:<\/p>\n\n\n\nresolve: {\n modules: [path.resolve(\".\/node_modules\")]\n}<\/code><\/pre>\n\n\n\nProduction mode<\/h4>\n\n\n
mode<\/code> property, like this:<\/p>\n\n\n\nmode: isProd ? \"production\" : \"development\",<\/code><\/pre>\n\n\n\nconst isProd = process.env.NODE_ENV == \"production\";<\/code><\/pre>\n\n\n\nFile extensions<\/h4>\n\n\n
resolve: {\n extensions: [\".ts\", \".tsx\", \".js\"],\n}<\/code><\/pre>\n\n\n\nRollup plugins are compatible!<\/h3>\n\n\n
Your first Vite project<\/h3>\n\n\n
The HTML entry point<\/h4>\n\n\n
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<script><\/code> tag points to \/reactStartup.tsx<\/code>. Adjust that to your own entry as needed.<\/p>\n\n\n\nnpm i vite @vitejs\/plugin-react @types\/node<\/code><\/pre>\n\n\n\nvite.config.ts<\/code> right next to the index.html<\/code> file in the project directory.<\/p>\n\n\n\nimport { defineConfig } from \"vite\";\nimport react from \"@vitejs\/plugin-react\";\n\nexport default defineConfig({\n plugins: [react()]\n});<\/code><\/pre>\n\n\n\n\"dev\": \"vite\",\n\"build\": \"vite build\",\n\"preview\": \"vite preview\",<\/code><\/pre>\n\n\n\nnpm run dev<\/code>. It\u2019s incredibly fast, and incrementally builds whatever it needs to, based on what\u2019s requested.<\/p>\n\n\n\n
<\/figure>\n\n\n\nreactStartup<\/code> file (or whatever your entry file is called) as follows:<\/p>\n\n\n\nimport 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\nnpm run dev<\/code> command and browse to localhost:3000<\/code>.<\/p>\n\n\n\n
<\/figure>\n<\/div>\n\n\n\n
<\/figure>\n<\/div>\n<\/div>\n\n\nHot module reloading (HMR)<\/h4>\n\n\n
Aliases<\/h4>\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\njscolor<\/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\nimport { thing } from \"util\/helpers\/foo\"<\/code><\/pre>\n\n\n\nutil<\/code> folder at the very top.<\/p>\n\n\n\nimport { 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\nresolve.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\nutil\/foo<\/code>. If you have some other<\/em> util folder deeper in your tree, and you reference it with this:<\/p>\n\n\n\nimport { thing } from \".\/helpers\/util\";<\/code><\/pre>\n\n\n\nEnvironment variables<\/h4>\n\n\n
.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\nimport.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