From ab50fefb1351e9e8707d6ec3f64b578cf02f0406 Mon Sep 17 00:00:00 2001 From: Rodolfo Carvalho Date: Thu, 27 Nov 2025 20:36:05 +0100 Subject: [PATCH] Bundle app.js as ESM module for new projects Update the `mix phx.new` installer to configure `esbuild` to output ESM modules by default in new Phoenix projects. ESM modules enable tree shaking, code splitting, and enforce JavaScript strict mode, which can be beneficial for reducing bundle sizes, and catching common coding errors earlier. In the JavaScript ecosystem, modern bundlers like Vite output ESM modules by default. If a developer discovers later that they strictly need to support a non-module environment (like a third-party embed), it is trivial to go back to an IIFE output target. --- guides/asset_management.md | 2 +- guides/howto/custom_error_pages.md | 2 +- guides/real_time/channels.md | 2 +- installer/templates/phx_single/config/config.exs | 12 ++++++++++-- .../phx_umbrella/apps/app_name_web/config/config.exs | 12 ++++++++++-- .../phx_web/components/layouts/root.html.heex | 2 +- installer/test/phx_new_test.exs | 3 ++- installer/test/phx_new_umbrella_test.exs | 3 ++- 8 files changed, 28 insertions(+), 10 deletions(-) diff --git a/guides/asset_management.md b/guides/asset_management.md index 77c3a763cd..9d9c27979a 100644 --- a/guides/asset_management.md +++ b/guides/asset_management.md @@ -94,7 +94,7 @@ If you need to reference other directories, you need to update the arguments abo If you import a Node package that depends on additional fonts or images, you might find them to fail to load. This is because they are referenced in the JS or CSS but by default Esbuild will not touch or process referenced files. You can add arguments to esbuild in `config/config.exs` to ensure that the referenced resources are copied to the output folder. The following example would copy all referenced font files to the output folder and prefix the paths with `/assets/`: ```elixir -args: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --public-path=/assets/ --loader:.woff=copy --loader:.ttf=copy --loader:.eot=copy --loader:.woff2=copy), +args: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --public-path=/assets/ --loader:.woff=copy --loader:.ttf=copy --loader:.eot=copy --loader:.woff2=copy), ``` For more information, see [the esbuild documentation](https://esbuild.github.io/content-types/#copy). diff --git a/guides/howto/custom_error_pages.md b/guides/howto/custom_error_pages.md index a1a965f360..9b922dc429 100644 --- a/guides/howto/custom_error_pages.md +++ b/guides/howto/custom_error_pages.md @@ -90,7 +90,7 @@ Phoenix generates an `ErrorHTML` for us, but it doesn't give us a `lib/hello_web Welcome to Phoenix! - +
diff --git a/guides/real_time/channels.md b/guides/real_time/channels.md index 4fb2e303a8..ac1c5a4a86 100644 --- a/guides/real_time/channels.md +++ b/guides/real_time/channels.md @@ -432,7 +432,7 @@ Next, we need to pass this token to JavaScript. We can do so inside a script tag ```heex - + ``` ### Step 4 - Pass the Token to the Socket Constructor and Verify diff --git a/installer/templates/phx_single/config/config.exs b/installer/templates/phx_single/config/config.exs index 11496cdbc4..b9a256f647 100644 --- a/installer/templates/phx_single/config/config.exs +++ b/installer/templates/phx_single/config/config.exs @@ -37,8 +37,16 @@ config :<%= @app_name %>, <%= @app_module %>.Mailer, adapter: Swoosh.Adapters.Lo config :esbuild, version: "0.25.4", <%= @app_name %>: [ - args: - ~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.), + args: ~w( + js/app.js + --bundle + --format=esm + --target=es2022 + --outdir=../priv/static/assets/js + --external:/fonts/* + --external:/images/* + --alias:@=. + ), cd: Path.expand("..<%= if @in_umbrella, do: "/apps/#{@app_name}" %>/assets", __DIR__), env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]} ]<% end %><%= if @css do %> diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs index dc0792d165..48060b5b3e 100644 --- a/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs +++ b/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs @@ -21,8 +21,16 @@ config :<%= @web_app_name %>, <%= @endpoint_module %>, config :esbuild, version: "0.25.4", <%= @web_app_name %>: [ - args: - ~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.), + args: ~w( + js/app.js + --bundle + --format=esm + --target=es2022 + --outdir=../priv/static/assets/js + --external:/fonts/* + --external:/images/* + --alias:@=. + ), cd: Path.expand("../apps/<%= @web_app_name %>/assets", __DIR__), env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]} ]<% end %><%= if @css do %> diff --git a/installer/templates/phx_web/components/layouts/root.html.heex b/installer/templates/phx_web/components/layouts/root.html.heex index 1a57c4899b..6c8d739518 100644 --- a/installer/templates/phx_web/components/layouts/root.html.heex +++ b/installer/templates/phx_web/components/layouts/root.html.heex @@ -9,7 +9,7 @@ <%= if not @css do %> <% end %> - <%= if @css do %>