Fix Docker build JavaScript import errors with vendor bundle

Fixed the esbuild errors during Docker build where npm packages couldn't
be resolved. The issue was caused by node_modules being excluded from
the Docker build context in .dockerignore.

Implemented a proper vendor bundle strategy following Phoenix 1.7+ patterns:

- Created vendor.js that bundles all npm dependencies (Leaflet, Chart.js,
  topbar) and their CSS files into a single pre-built bundle
- Added vendor esbuild profile in config.exs with proper CSS/image loaders
- Updated mix.exs to build vendor bundle before app bundle in assets.deploy
- Modified JavaScript imports to use globally loaded libraries from vendor
  bundle instead of importing from node_modules
- Added vendor.js script tag to root.html.heex before app.js

This approach avoids Docker build failures by pre-bundling all vendor
dependencies, eliminating the need for node_modules during production builds.

🤖 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 12:32:39 -05:00
parent 53ce452cdf
commit 63a87f3be1
No known key found for this signature in database
7 changed files with 45 additions and 19 deletions

View file

@ -20,7 +20,8 @@ import "phoenix_html";
// Establish Phoenix Socket and LiveView configuration.
import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
import topbar from "topbar";
// topbar is loaded globally from vendor bundle
const topbar = window.topbar;
// Sentry initialization happens via the loader script in the HTML
// Configure additional Sentry settings if needed

View file

@ -1,13 +1,10 @@
// Import Chart.js and date adapter
import Chart from 'chart.js/auto';
import 'chartjs-adapter-date-fns';
// Chart.js and date adapter are loaded globally from vendor bundle
const Chart = window.Chart;
import type { ChartConfiguration, ChartType } from 'chart.js';
import type { WeatherChartDataset, YAxisOptions } from '../types/chart-types';
import type { HandleEventFunction } from '../types/events';
// Make Chart globally available for legacy code
window.Chart = Chart;
// Declare global Chart object
declare global {
interface Window {

View file

@ -12,19 +12,10 @@ import type { HeatLayer, MarkerClusterGroup, OverlappingMarkerSpiderfier, Marker
import type { APRSMarker } from './types/marker-extensions';
import type { BaseEventPayload } from './types/events';
// Import Leaflet and plugins
import L from 'leaflet';
import 'leaflet.heat';
import 'leaflet.markercluster';
import 'overlapping-marker-spiderfier-leaflet';
// Leaflet and plugins are loaded globally from vendor bundle
const L = window.L;
// Import CSS files
import 'leaflet/dist/leaflet.css';
import 'leaflet.markercluster/dist/MarkerCluster.css';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
// Make Leaflet globally available for legacy code
window.L = L;
// CSS files are bundled in vendor.js
// The OverlappingMarkerSpiderfier is attached to window by the UMD module
declare global {

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

@ -0,0 +1,28 @@
// Vendor bundle for all third-party dependencies
// This file bundles all npm packages to avoid resolution issues during Docker builds
// Import CSS files first
import 'leaflet/dist/leaflet.css';
import 'leaflet.markercluster/dist/MarkerCluster.css';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
// Export Leaflet and plugins
import * as L from 'leaflet';
import 'leaflet.heat';
import 'leaflet.markercluster';
import 'overlapping-marker-spiderfier-leaflet';
// Export Chart.js and adapter
import Chart from 'chart.js/auto';
import 'chartjs-adapter-date-fns';
// Export topbar
import topbar from 'topbar';
// Make libraries available globally
window.L = L;
window.Chart = Chart;
window.topbar = topbar;
// Export for ES modules
export { L, Chart, topbar };

View file

@ -85,6 +85,12 @@ config :esbuild,
~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --loader:.css=css --loader:.png=file --loader:.svg=file),
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 --loader:.css=css --loader:.png=file --loader:.svg=file),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
config :gettext, :plural_forms, GettextPseudolocalize.Plural

View file

@ -26,6 +26,8 @@
{assigns[:page_title] || "Aprs"}
</.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} />
<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>
<script>

View file

@ -135,6 +135,7 @@ defmodule Aprsme.MixProject do
test: ["gleam_compile", "ecto.create --quiet", "ecto.migrate --quiet", "test"],
"assets.deploy": [
"tailwind default --minify",
"esbuild vendor",
"esbuild default --minify",
"phx.digest"
]