mindreframer

mindreframer

A proof-of-concept integration of Vite.js (modern JS/assets bundler) with Phoenix + Liveview

Hey everyone, yesterday I have played around with Vite.js and it feels really nice for frontend dev in Phoenix. The setup is currently a bit involved, so I have documented the process here:

Please share your feedback if there are some questions / suggestions!

Most Liked

50kudos

50kudos

Hi, I switched from Snowpack to Vite recently, and it works great on my production. I’d like to provide my setup here which is, I think, the easiest way.

--- a/assets/package.json
+++ b/assets/package.json
@@ -3,8 +3,8 @@
   "scripts": {
+    "build": "vite build",
+    "watch": "vite build --watch --minify false --emptyOutDir false --clearScreen false --mode development"
   },
   "devDependencies": {
+    "vite": "^2.2.4"
   }
 }

--- /dev/null
+++ b/assets/vite.config.js
@@ -0,0 +1,19 @@
+export default {
+  publicDir: "./static",
+  build: {
+    target: "es2018",
+    minify: true,
+    outDir: "../priv/static",
+    emptyOutDir: true,
+    rollupOptions: {
+      input: ["js/app.js", "css/app.css"],
+      output: {
+        entryFileNames: "js/[name].js",
+        chunkFileNames: "js/[name].js",
+        assetFileNames: "[ext]/[name][extname]"
+      }
+    },
+    assetsInlineLimit: 0
+  }
+}

--- a/config/dev.exs
+++ b/config/dev.exs
@@ -23,7 +23,7 @@ config :fset, FsetWeb.Endpoint,
   watchers: [
-    node: ["whatever", "ever", cd: Path.expand("../assets", __DIR__)]
+    yarn: ["run", "watch", cd: Path.expand("../assets", __DIR__)]
   ]

--- a/Dockerfile
+++ b/Dockerfile
@@ -27,7 +27,7 @@ ENV NODE_ENV=production
 COPY lib lib
 COPY priv priv
 COPY assets assets
+RUN yarn --cwd ./assets run build
 RUN mix phx.digest
13
Post #2
thiagomajesk

thiagomajesk

Hi @50kudos! I tried Vite today for the first time with Phoenix 1.6 and your response was really helpful. In the end, I had to do a little bit more work, but It seems everything is working as expected. Here’s my setup…

package.json

{
  "repository": {},
  "description": " ",
  "scripts": {
    "build": "vite build",
    "watch": "vite build --watch --minify false --emptyOutDir false --clearScreen false --mode development"
  },
  "dependencies": {
    "@popperjs/core": "^2.10.1",
    "bootstrap": "^5.1.1",
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html",
    "phoenix_live_view": "file:../deps/phoenix_live_view"
  },
  "devDependencies": {
    "sass": "^1.42.1",
    "vite": "^2.5.10"
  }
}

Fixes problems with import resolution for “phoenix”, “phoenix_html” and “phoenix_live_view” and includes support for Sass.

vite.config.js

export default {
  publicDir: "./static",
  build: {
    target: "es2018",
    minify: true,
    outDir: "../priv/static",
    emptyOutDir: true,
    assetsInlineLimit: 0,
    rollupOptions: {
      input: ["js/app.js", "css/app.scss"],
      output: {
        entryFileNames: "js/[name].js",
        chunkFileNames: "js/[name].js",
        assetFileNames: "[ext]/[name][extname]"
      }
    },
  }
}

dev.exs

config :my_app, MyAppWeb.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 4000],
  debug_errors: true,
  code_reloader: true,
  check_origin: false,
  watchers: [
    npm: ["run", "watch", cd: Path.expand("../assets", __DIR__)]
  ]

mix.exs

"assets.deploy": ["cmd --cd assets npm run build", "phx.digest"]

endpoint.ex

plug Plug.Static,
    at: "/",
    from: :my_app,
    gzip: false,
    only: ~w(css js fonts images favicon.ico robots.txt)

Differs from the default esbuild config which outputs directly to priv/static/assets.

root.html.heex

<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/css/app.css")}/>
<script defer phx-track-static type="module" src={Routes.static_path(@conn, "/js/app.js")}></script>

Updates public path and adds type="module" to properly load the exposed javascript module.


PS.: It seems that Vite supports older browsers with a plugin but I didn’t seem to work for me.

Bonus: If you are using VS Code with ElixirLS and your *.heex files are not properly highlighted you can add the following setting to your user config: "files.associations": {"*.heex": "html-eex" }

thepeoplesbourgeois

thepeoplesbourgeois

Early on, I had the idea that I wanted separate CSS files for separate pages of my app*, and as a result of that, found so much success in just decoupling the styling from the bundling altogether, running brew install sass/sass/sass on my dev box, and then feeling the glorious, glorious lifting of a burden when I deleted so many CSS-based plugins from package.json and webpack.config.

Then Phoenix migrated everything over to esbuild, and rocky takeoff notwithstanding, I have everything running smoothly and snappily now.

# config/dev.exs
watchers: [
  sass: ~w[--watch --no-source-map assets/css/:priv/static/css],
  esbuild: {Esbuild, :install_and_run, [:default, ~w(--watch)]}
]

I’ve also got a mix task for wrapping all of my assets up in a nice, self-contained digest when it’s deploy time. I call it assets.deploy

defmodule Mix.Tasks.Assets.Deploy do
  use Mix.Task

  @shortdoc "Packages assets for the browser"

  @moduledoc """
  Packages assets for the browser.

  If the `DEV_MODE` environment variable is set, will not minify the CSS and JS,
  or create a sourcemap for either.
  """

  def run(_args) do
    dev_mode? = env("MIX_ENV") in [:dev, :test, nil]
    shell! "rm -rf priv/static/*"
    shell! "cp -R assets/static/* priv/static/"
    cmd "sass assets/css/app.scss:priv/static/css/app.css" <> (unless dev_mode?, do: " --source-map", else: "")
    cmd "mix esbuild default" <> (unless dev_mode?, do: " --minify --sourcemap", else: "")
    unless dev_mode? do
      cmd "mix phx.digest -o priv/digest"
      shell! "rm -rf priv/static/*"
      shell! "mv priv/digest/* priv/static/"
    end
  end

  defp cmd(command, collectible \\ IO.stream(:stdio, :line)) when is_binary(command) do
    [app | options] = String.split(command, ~r/\s+/)
    cmd(app, options, collectible)
  end
  defp cmd(app, options, collectible) do
    [app | options] |> IO.inspect
    System.cmd(app, options, into: collectible)
  end
  defp shell!(command, opts \\ [into: IO.stream()]) do
    command |> IO.inspect
    System.shell(command, opts)
  end

  defp env(var), do: System.get_env(var)
end

(the cmd, shell!, and env patterns are brought over from my actual deployment-deploying task, sort of a bespoke take on Capistrano for publishing to gigalixir through its’ git endpoint… possibly from a tty box idk life’s wild sometimes. I call it giggitty. But I’m way off topic now!

EDIT: * I did, of course, realize the awful idea that would’ve been, and now roll all of my CSS, with the comparatively gigantic phoenix.css taffy monster beneath my app.scss file and everything, into one, huge, abomination.

Where Next?

Popular in Discussions Top

WolfDan
After doing a port from a c++ library to my project in phoenix I’ve seen that I need a faster way to run this algorithm and I found this ...
New
owaisqayum
I have a sample string sentence = "Hello, world ... 123 *** ^%&amp;*())^% %%:&gt;" From this string, I want to only keep the integers, ...
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
eteeselink
Hi all, In the last days, two things happened: A blog post titled “They might never tell you it’s broken” made the rounds. It’s about ...
New
tomekowal
Hey guys! I want to create a toy project that shows a chart of temperature over time and updates every 5 seconds. I feel LiveView is per...
New
AstonJ
If so I (and hopefully others!) might have some tips for you :slight_smile: But first, please say which area you’re finding most challen...
New
wmnnd
The Go vs Elixir thread got me thinking: Would it be too hard to implement a simple mechanism for creating Go-style static app binaries f...
New
MarioFlach
Hello, I want to share a project I’ve been working on for a while: https://github.com/almightycouch/gitgud Background Some time ago I ...
New
axelson
Decided against including more info in the title, but the gist is that Plataformatec sponsored projects will continue with the assets bei...
New

Other popular topics Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54250 245
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement