My previous post walked through my progress with swapping Webapck for Vite using the Middleman external pipeline. I was able to quickly see great performance improvements, reduced dev server and build times and a decent drop in the number of JavaScript dependencies. But there were still some issues and I knew I could improve things. Especially after I discovered Vite Ruby.
What was it that I saw in Vite Ruby that I thought could really help? It was the DevServerProxy which inherits from Rack::Proxy
and relays asset requests to the Vite development server which is exactly what I thought I might need to write myself in order to make Vite work in development mode with no bundling. The best thing was that this was only a small part of it, with potentially less configuration required, helpers to make it work with Middleman and improved performance with module preloading imports within the bundled JavaScript assets.
Vite Ruby is the core library but there was also a Padrino Integration which looked like it would help with Middleman and bring with it tag helpers, hot module reloading and smart output with module preloading as mentioned above. The Middleman helper methods are all built upon Padrino helpers so it looked like a good bet that this integration would work well.
The first thing to do is add the new Gems
gem "vite_padrino"
gem "vite_ruby"
and then bundle install
. Then run the Vite Ruby installer bundle exec vite install
which sets up all the necessary configuration as well as creating a demo JavaScript file.
The next thing to do was to get Middleman using the Vite Ruby DevServerProxy and the Padrino tag helpers
require "vite_ruby"
require "vite_padrino/tag_helpers"
...
configure :development do
use ViteRuby::DevServerProxy, ssl_verify_none: true
end
helpers VitePadrino::TagHelpers
I also had to swap all of my instances of javascript_include_tag
, stylesheet_link_tag
and asset_path
with vite_javascript_tag
, vite_stylesheet_tag
and vite_asset_path
respectively. Also, to make the hot module reloading work you simply have to add <%= vite_client_tag %>
somewhere so that it will be picked up on all pages.
Vite Ruby is similar in approach to Webpacker with convention over configuration and all entry point assets are placed in frontend/entrypoints
. You can read in my previous post how I changed the configuration of my assets to use glob imports from within the asset files themselves rather than using Webpack, as well as swapping from SCSS to PostCSS so I performed the same setup again for this test. Therefore it was pretty simple to move my assets to the new frontend/entrypoints
folder and all the other required assets under the frontend
folder.
Vite Ruby automatically creates a config/vite.json
file as part of the installer and mine used the default settings except for adding "watchAdditionalPaths": ["components/**/*"]
so the dev server would automatically reload the page when I make changes in my assets in the components folder which is where I have my view components.
{
"all": {
"publicDir": "source",
"sourceCodeDir": "frontend",
"watchAdditionalPaths": ["components/**/*"]
},
"development": {
"autoBuild": true,
"publicOutputDir": "vite-dev",
"port": 3036
},
"test": {
"autoBuild": true,
"publicOutputDir": "vite-test"
}
}
My vite.config.js
is stripped down compared to the last post because of the Vite Ruby conventions so I don't need the input and output part of the configuration
import esbuild from "rollup-plugin-esbuild"
import FullReload from 'vite-plugin-full-reload'
import { defineConfig } from "vite"
import RubyPlugin from "vite-plugin-ruby"
export default defineConfig({
build: {
brotliSize: false,
emptyOutDir: true,
minify: "esbuild",
rollupOptions: {
output: {
format: "es",
manualChunks: {
game_vendor: ["crypto-es"]
}
}
}
},
plugins: [
esbuild({
target: [
"chrome64",
"edge79",
"firefox62",
"safari11.1",
]
}),
FullReload(["source/**/*"], { delay: 1000 }),
RubyPlugin(),
]
})
The main difference you'll see from the last post is the addition of vite-plugin-ruby
which I've added and setup to reload the page on every file change within the source
folder which is really handy.
With Vite Ruby setup I could remove all the Webpack configuration like I demonstrated in the previous post. I could also remove the Middleman external pipeline from config.rb
as well as config[:css_dir]
and config[:js_dir]
and finally activate :asset_hash
because all the hashing was now handled by Vite. Removing activate :asset_hash
eliminated the bug I had in the previous post with the asset hashed path which was another bonus. Finally, my dev
and build
scripts could be removed from package.json
.
I now have to run 2 commands to get things working in development
which normally can be a bit of a pain but I use overmind and setup my Procfile.dev
like so
vite: bin/vite dev
web: bundle exec middleman serve
and can run both servers with the one command overmind s
but can still easily step into byebug for example by typing overmind connect web
.
With that all set I ran overmind s
and I immediately came across and error to do with asset_path
which was part of the Vite Padrino tag helpers. It appears they are being passed only 1 argument in Vite Padrino whereas they should be passed a minimum of 2. It turns out the type wasn't being passed which would be something like :js
, :css
, etc. This was easy enough to quickly fix in my Middleman helpers by overriding asset_path
before passing onto super
def asset_path(*args)
if args.size == 1
super(File.extname(args[0]).delete(".").to_sym, args[0])
else
super(*args)
end
end
and this sorted things out. Everything was working now and I felt it was a big improvement on where I ended up with the previous post. The next thing was to make some tweaks to the build.
I had to add some additional config for my Middleman build on Netlify. Before running the middleman build command I had to run rake vite:clobber && bin/vite build
to build the assets. I noticed that Vite was building in dev mode when I did this but this was fixed by passing in a RACK_ENV=production
environment variable and this made Vite build in production mode.
You'll notice that I was having to create my assets from scratch on every build even if they weren't changing. I knew in Netlify there was a way to cache files between builds which I reckoned I could take advantage of to improve the speed of the build even further.
To make this work easily, I added a new package yarn add netlify-plugin-cache --dev
and added the following to the end of my netlify.toml
file
[[plugins]]
package = "netlify-plugin-cache"
[plugins.inputs]
# Optional (but highly recommended). Defaults to [".cache"].
paths = ["source/vite"]
which would instruct Netlify to cache the contents of the source/vite
folder between builds. I also added a new section to my config/vite.json
file
{
"all": {
"buildCacheDir": "source/vite/last-build",
...
which is where the information would be saved about the last build so Vite would know whether or not to rebuild the assets. Now I could remove rake vite:clobber
from my build command and on the majority of my builds, Netlify simply pulls the assets from its cache rather than having to build from scratch, unless there is a change in the assets.
In the last post I gave the performance improvements using Vite over my Webpack setup and I'll do the same comparison below between Vite Ruby and Webpack.
Dependencies | 1053 |
---|---|
Dev server time | 4.18s |
Netlify build time | 7.4s |
Netlify cache size | 199.1MB |
Javascript files | |
main.js | 15.8kb |
components.js | 10.5kb |
game.js | 1kb |
controller.js | 8.6kb |
game_vendor.js | 4.8kb |
Again, there looks to be good improvements vs using Webpack
and vs what I ended up with in the previous post
I'm very happy with the setup I have now and I achieved every one of the aims I had for making this change. The configuration is much simpler, I have fewer dependencies, the dev server is faster and the build is faster. As a bonus, I'm also shipping less JavaScript and my Netlify cache is smaller which can help improve overall build time even further because there is less to download and extract. My full end to end Netlify build and deploy time for this small site of 163 files is now roughly around 30s which is pretty darn good!