Adding assets/vendor to NODE_PATH for vendored JS dependencies

Even for JS-minimal Phoenix apps, there is often a need to add external JS dependencies. The simplest way is to load the script by adding a link to the relevant CDN in the <head>. Another way is to vendor that same asset by committing the UMD artifact into the repository. This is what Phoenix already does for the topbar dependency it uses.

Another approach I found is to not only vendor it, but to instruct esbuild to look into that same vendor directory when it does module resolution. This allows one to specify a module name instead of the relative path. This is particularly useful when one needs to vendor TWO dependencies where one depends on the other.

All one needs to do is to modify config.exs to have an additional NODE_PATH directory. E.g.,

config :esbuild,
  version: "0.17.11",
  default: [
    args:
      ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => "#{Path.expand("../deps", __DIR__)}:#{Path.expand("../assets/vendor", __DIR__)}"}

Then drop the relevant files in assets/vendor. These compiled UMD artifacts can be gotten from e.g., cdnjs.com. In my case, I wanted to add tippy which also depends on @poppyjs/core. I fetched both of those from unpkg.com and saved those into that directory. The final tree looking like:

assets/
  vendor/
    @poppyjs/
      core.js
    tippy.js
    topbar.js

Then I could use tippy as expected in my app.js:

import tippy from "tippy";

I opened a proposal on the phoenix-core mailing list to have this be configured by default for new applications, as I think it is the most clear way to add external dependencies before one needs to step up to something more structured like NPM and such.

https://groups.google.com/g/phoenix-core/c/nIAHr1U4g8k

Cheers,