Bundle all vendor JavaScript files locally instead of loading from CDNs

This change eliminates all external CDN dependencies by bundling JavaScript
libraries locally using esbuild. All vendor files are now served from the
application's own assets, improving reliability and privacy.

Changes:
- Created assets/js/vendor.js to bundle all external libraries
- Added vendor bundle configuration to esbuild in config files
- Updated all templates to use local vendor.js instead of CDN scripts
- Created download-vendor-files.sh script to fetch vendor libraries
- Added vendor files to .gitignore (they're downloaded during setup)
- Updated CSS imports to include vendor stylesheets
- Added vendor build step to assets.deploy mix task

Bundled libraries:
- Leaflet 1.9.4 (mapping library)
- Leaflet.heat 0.2.0 (heatmap plugin)
- Leaflet.markercluster 1.5.3 (marker clustering)
- OverlappingMarkerSpiderfier 0.2.6 (overlapping marker handling)
- Chart.js 4.5.0 (charting library)
- chartjs-adapter-date-fns 3.0.0 (date adapter for Chart.js)

All functionality tested and working correctly with local bundles.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Graham McIntire 2025-07-21 09:52:39 -05:00
parent a8e7b17ee1
commit 7dc6c616a2
No known key found for this signature in database
11 changed files with 148 additions and 59 deletions

4
.gitignore vendored
View file

@ -25,6 +25,10 @@ aprsme-*.tar
# Ignore assets that are produced by build tools.
/priv/static/assets/
# Ignore vendor JavaScript/CSS files (downloaded from CDNs)
/assets/vendor/*.js
/assets/vendor/*.css
# Ignore digested assets cache.
/priv/static/cache_manifest.json

View file

@ -3,6 +3,11 @@
@source "../js";
@source "../../lib/aprsme_web";
/* Import vendor CSS files */
@import "../vendor/leaflet.css";
@import "../vendor/MarkerCluster.css";
@import "../vendor/MarkerCluster.Default.css";
/* A Tailwind plugin that makes "hero-#{ICON}" classes available.
The heroicons installation itself is managed by your mix.exs */
@plugin "../vendor/heroicons";

43
assets/download-vendor-files.sh Executable file
View file

@ -0,0 +1,43 @@
#!/bin/bash
# Script to download vendor JavaScript and CSS files
# Run this from the project root: ./assets/download-vendor-files.sh
set -e
echo "Downloading vendor files..."
# Ensure vendor directory exists
mkdir -p assets/vendor
cd assets/vendor
# Download JavaScript files
echo "Downloading Leaflet..."
curl -L -o leaflet.js https://unpkg.com/leaflet@1.9.4/dist/leaflet.js
echo "Downloading Leaflet Heat..."
curl -L -o leaflet-heat.js https://cdn.jsdelivr.net/npm/leaflet.heat@0.2.0/dist/leaflet-heat.js
echo "Downloading Chart.js..."
curl -L -o chart.umd.js https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.js
echo "Downloading Chart.js date adapter..."
curl -L -o chartjs-adapter-date-fns.bundle.min.js https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js
echo "Downloading Leaflet MarkerCluster..."
curl -L -o leaflet.markercluster.js https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js
echo "Downloading OverlappingMarkerSpiderfier..."
curl -L -o oms.min.js https://cdnjs.cloudflare.com/ajax/libs/OverlappingMarkerSpiderfier-Leaflet/0.2.6/oms.min.js
# Download CSS files
echo "Downloading Leaflet CSS..."
curl -L -o leaflet.css https://unpkg.com/leaflet@1.9.4/dist/leaflet.css
echo "Downloading MarkerCluster CSS..."
curl -L -o MarkerCluster.css https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css
curl -L -o MarkerCluster.Default.css https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css
echo "All vendor files downloaded successfully!"
echo "Run 'mix esbuild vendor' to build the vendor bundle."

23
assets/js/vendor.js Normal file
View file

@ -0,0 +1,23 @@
// Vendor libraries bundle
// This file loads all external libraries and makes them available globally
// Load Leaflet first (required by plugins)
import '../vendor/leaflet.js';
// Load Leaflet plugins (they expect window.L to exist)
import '../vendor/leaflet-heat.js';
import '../vendor/leaflet.markercluster.js';
import '../vendor/oms.min.js';
// Load Chart.js
import '../vendor/chart.umd.js';
// Load Chart.js adapter (requires Chart to be loaded first)
import '../vendor/chartjs-adapter-date-fns.bundle.min.js';
// Ensure global availability
if (typeof window !== 'undefined') {
// Leaflet should already be on window.L from the library
// Chart should already be on window.Chart from the library
console.log('Vendor libraries loaded: Leaflet', !!window.L, 'Chart.js', !!window.Chart);
}

56
assets/vendor/README.md vendored Normal file
View file

@ -0,0 +1,56 @@
# Vendor JavaScript Libraries
This directory contains third-party JavaScript and CSS libraries that are bundled locally instead of loading from CDNs.
## Required Files
The following files need to be downloaded:
### JavaScript Files
- `leaflet.js` - from https://unpkg.com/leaflet@1.9.4/dist/leaflet.js
- `leaflet-heat.js` - from https://cdn.jsdelivr.net/npm/leaflet.heat@0.2.0/dist/leaflet-heat.js
- `chart.umd.js` - from https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.js
- `chartjs-adapter-date-fns.bundle.min.js` - from https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js
- `leaflet.markercluster.js` - from https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js
- `oms.min.js` - from https://cdnjs.cloudflare.com/ajax/libs/OverlappingMarkerSpiderfier-Leaflet/0.2.6/oms.min.js
### CSS Files
- `leaflet.css` - from https://unpkg.com/leaflet@1.9.4/dist/leaflet.css
- `MarkerCluster.css` - from https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css
- `MarkerCluster.Default.css` - from https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css
## Download Script
To download all vendor files, run:
```bash
# From the project root
cd assets/vendor
# Download JavaScript files
curl -L -o leaflet.js https://unpkg.com/leaflet@1.9.4/dist/leaflet.js
curl -L -o leaflet-heat.js https://cdn.jsdelivr.net/npm/leaflet.heat@0.2.0/dist/leaflet-heat.js
curl -L -o chart.umd.js https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.js
curl -L -o chartjs-adapter-date-fns.bundle.min.js https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js
curl -L -o leaflet.markercluster.js https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js
curl -L -o oms.min.js https://cdnjs.cloudflare.com/ajax/libs/OverlappingMarkerSpiderfier-Leaflet/0.2.6/oms.min.js
# Download CSS files
curl -L -o leaflet.css https://unpkg.com/leaflet@1.9.4/dist/leaflet.css
curl -L -o MarkerCluster.css https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css
curl -L -o MarkerCluster.Default.css https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css
```
## Building
The vendor files are bundled using esbuild:
```bash
# Build vendor bundle
mix esbuild vendor
# Build with minification (for production)
mix esbuild vendor --minify
```
The vendor bundle is automatically included in the asset deployment pipeline.

View file

@ -85,6 +85,12 @@ config :esbuild,
~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --external:chart.js/auto --define:global.L=window.L),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
],
vendor: [
args:
~w(js/vendor.js --bundle --target=es2017 --outdir=../priv/static/assets --minify),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
config :gettext, :plural_forms, GettextPseudolocalize.Plural

View file

@ -53,6 +53,12 @@ config :aprsme, AprsmeWeb.Endpoint,
:default,
~w(--sourcemap=inline --watch --loader:.ts=ts)
]},
esbuild_vendor:
{Esbuild, :install_and_run,
[
:vendor,
~w(--sourcemap=inline --watch)
]},
tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
]

View file

@ -26,41 +26,8 @@
{assigns[:page_title] || "Aprs"}
</.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} />
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
<script
defer
phx-track-static
type="text/javascript"
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
data-source-map="false"
>
</script>
<%!-- Load Leaflet.heat after Leaflet is loaded --%>
<script
defer
phx-track-static
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/leaflet.heat@0.2.0/dist/leaflet-heat.js"
>
</script>
<script
defer
phx-track-static
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.js"
>
</script>
<script
defer
phx-track-static
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"
>
<%!-- Vendor libraries are now bundled locally --%>
<script defer phx-track-static type="text/javascript" src={~p"/assets/vendor.js"}>
</script>
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script>

View file

@ -774,24 +774,7 @@ defmodule AprsmeWeb.MapLive.Index do
@impl true
def render(assigns) do
~H"""
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css" />
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""
>
</script>
<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/OverlappingMarkerSpiderfier-Leaflet/0.2.6/oms.min.js">
</script>
<%!-- All vendor libraries are now loaded from vendor.js bundle --%>
<style>
#aprs-map {

View file

@ -230,12 +230,7 @@
<canvas></canvas>
</div>
<% end %>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.js">
</script>
<script
src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"
>
</script>
<%!-- Chart.js and adapter are now loaded from vendor.js bundle --%>
</div>
</div>
</div>

View file

@ -136,6 +136,7 @@ defmodule Aprsme.MixProject do
"assets.deploy": [
"tailwind default --minify",
"esbuild default --minify",
"esbuild vendor --minify",
"phx.digest"
]
]