Today I Learned [TIL]


I feel like everywhere I look I’m seeing the super fancy Vite logo. I thought it would be fun to take a look at Vite for my first time today and share my experience with it. Vite is a front-end build tool that comes out-of-the-box with a dev server and hot module reloading. I don’t know a lot beyond it yet, other than the claims of an “optimized build” and “rich features” from their GitHub.

Honestly, my hope going into this is that it can be like Webpack, but with a lot less headache. I primarily work with React and Elm for the front-end, so I would like to get both of those working with hot module replacement. With that in mind, let’s take a look at that gorgeous logo and get started!

Vite logo
The Vite Logo.

๐Ÿ‘ฉโ€๐Ÿ”ฌ Development

The Getting Started page on their website tells us to run the following command:

npm create vite@latest

Upon running this command, we are prompted with a choice to pick our framework. I’m going to pick React to get things going quickly. After this I choose to use React without TypeScript, because I’m feeling a little wild today ๐Ÿ….

Selecting the front-end framework with Vite
Selecting the front-end framework with Vite.

And… done? I’ll run a cd first-class-vite and then run npm i. After packages are installed, we see that Vite came with three scripts in package.json: dev, build, preview. Assuming dev is the one we’ll use to dev, let’s run that. At this point we’re shown a message saying the Vite app is running at localhost:3000. Go there and take a look!

The Vite + React default page
The Vite + React default page.

With a few less steps than create-react-app, we have a React project up and running. This one even comes with Hot-Module Replacement. Sweet! At this point, I’m going to change out the default app for my own. Wordle is super popular right now, which reminds me of the game Mastermind that I played as a kid. I wrote up a similar game I’ll be calling Mistermind. I did everything using local react state with useState hooks. I’ll push this up to this GitHub repo so you can check it out!

Elm

I adore Elm. I needed to get that off of my chest. It’s great and I need to be able to use it with Vite, if I’m going to use it! Luckily, this repo, hmsk/vite-plugin-elm promises to provide Elm support. I’ll start by running the install command.

npm i -D vite-plugin-elm

After running this, I’ll update vite.config.js to include the new elm plugin. This leaves the vite config looking as follows:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import elm from 'vite-plugin-elm';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), elm()],
});

At this point, I would like to use an Elm to hold some of the app state for the app. This is a bit contrived, since I’m only going to be moving past guesses/turns in there. If you want to learn more about using React & Elm in tandem, I just wrote a post about it! In our main.jsx, I’ll import the Elm component I created and pass the ports to the React app below.

const appState = Elm.AppState.init({
  node: document.getElementById('elm-root'),
});

ReactDOM.render(
  <App ports={appState.ports} />,
  document.getElementById('root')
);

Then back in the React component, we’ll hook it up and save. Now for the moment of truth. We have to stop and run npm run dev again to pick up the plugin changes. After that we have a clean console and can go back to localhost:3000.

Mistermind demo with React app and Elm holding state.

Sweet! Without any more work than installing and turning on the plugins, we have React and Elm working in tandem. And with that, let’s take a turn towards deployment.

๐Ÿš€ Build & Deployment

First, let’s just build the static site using the npm run build command included with Vite. After running this, a dist folder is created containing the static assets for our Vite app. Nice. ๐Ÿ˜Œ

npm run build

The Vite website has a guide on deploying to Github pages, which is what I’m shooting to do. However, rather than run a deploy script, I’m just going to run the build and set GitHub pages to point to the /docs folder in settings. You can see this up and running here! Although this worked pretty much right out-of-the-box, I did hit one snag with the base URL. Even though they mention it, I forgot to set the base option in my Vite config.

I’m hosting the app at abshierjoel.github.io/mistermind. Knowing that, I only need to set the base option to that directory to get that to work. To do that, open up vite.config.js one more time and update the defineConfig call to look like this:

export default defineConfig({
  base: '/mistermind/',
  plugins: [react(), elm()],
});

๐ŸŽ‰ Conclusion

In conclusion, Vite is sick. Obviously this has been a fairly simple example of a static site, but I find myself creating a lot of those. But after this, I’ll definitely be spending more time with Vite to see if it can fully replace Webpack for me and save time configuring. I do hope my first swing at this has been helpful to anyone else wanting to take a look at what Vite has to offer. I’m excited to learn more, so if you’re a little further along on your Vite journey than I am, I’d love to hear from you down below.

Thanks for reading,

– Joel Abshier


This week I had received a code review from a teammate to add a @spec to a start_link function I wrote for a GenServer. My start_link function will take a keyword list, try to get the values :first_name and :last_name with defaults, and then call GenServer.start_link/2 with __MODULE__ as the module and those values as the initial state. This GenServer looks like this:

defmodule JoelServer do
  use GenServer
  
  def start_link(opts \\ []) do
    first_name = Keyword.get(opts, :first_name, "Joel")
    last_name = Keyword.get(opts, :last_name, "Abshier")
    initial_state = %{
      first_name: first_name,
      last_name: last_name  
    }
    
    GenServer.start_link(__MODULE__, initial_state, opts)
  end
  
  def init(state) do
    {:ok, state}
  end
end

What I would like to do is add a @spec to describe how to use this function. Luckily, in Elixir we have Dialyzer to make this easy. Dialyzer is a static analysis tool within Erlang/Elixir. One feature of Dialyzer is to suggest @spec annotations for a function. Let’s see what it thinks for this:

@spec start_link(keyword) :: :ignore | {:error, any} | {:ok, pid}
def start_link(opts \\ []) do
  ...
end

While that’s correct, a more specific @spec is valuable to us. Let’s look at how we can do that.

๐Ÿ“จ Arguments / Input Type

Let’s take a look at the keyword list we’re passing in. It technically doesn’t matter to us which options are passed in, in addition to :first_name and :last_name, however if the spec were to define those specific keywords, we could catch any issues and/or typos while writing the code. Let’s define a private type that lists our keywords. First, add the typep definition and then update the @spec accordingly.

@typep start_opt :: {:first_name, String.t()} | {:last_name, String.t()}
@spec start_link(opts :: [start_opt]) :: :ignore | {:error, any} | {:ok, pid}
def start_link(opts \\ []) do
  ...
end

Now if you try to call JoelServer.start_link(title: "Mr.") you’ll get a Dialyxir warning saying something like this:

([{title, <<77,114,46,32>>}]) breaks the contract (options::[start_opt()])

That’s pretty cool! Now the next time someone on my team has to use this GenServer, they’ll be told exactly how to use it while calling it. But what about the return type?

๐Ÿ“ฌ Output / Return Type

Now for the reason you’re here. A co-worker pointed me toward a type in the GenServer docs on_start/0. We can use this to replace the list of return types at :ignore | {:error, any} | {:ok, pid}. This lets us define our start_link with the return type as Elixir’s GenServer.start_link.

@spec start_link(opts :: [start_opt]) :: GenServer.on_start()

I love this, rather than managing our own list of types for each GenServer I define. If you’re working with a Supervisor, we are also given Supervisor.on_start/0. If you want to allow any options allowed by a GenServer or Supervisor, you’ll also find options/0, option/0, init_option/0, and so on.

๐Ÿฐ That’s all the GenServer we have today, folks

Anyhow, this might be pretty basic, if you’re familiar with Elixir. But I thought it was exciting to be told about these and wanted to share it. A lot of Elixir packages, especially the core library, have excellent hexdocs, so I’m going to make a habit of paying closer attention to the @specs on them for when I can take advantage of types like this.

I’ll probably be writing some more shorter posts like this. It’s a bit basic, but I’ll just make a Today I Learned category here on the blog, so you can check those out.

If you have any short tips like this I’d love to hear about them. I’m new to the language, so I’d love to learn! Thanks for reading! ๐Ÿงช

– Joel Abshier