diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex index f4bdca798f..d88b912a8c 100644 --- a/installer/lib/mix/tasks/phx.new.ex +++ b/installer/lib/mix/tasks/phx.new.ex @@ -38,7 +38,7 @@ defmodule Mix.Tasks.Phx.New do Please check the adapter docs for more information and requirements. Defaults to "bandit". - * `--no-assets` - equivalent to `--no-esbuild` and `--no-tailwind` + * `--no-assets` - equivalent to `--no-esbuild`, `--no-tailwind`, and disables Volt * `--no-dashboard` - do not include Phoenix.LiveDashboard @@ -63,6 +63,8 @@ defmodule Mix.Tasks.Phx.New do are left-in as reference for the subsequent styling of your layout and components + * `--volt` - use Volt instead of esbuild and tailwind for assets + * `--binary-id` - use `binary_id` as primary key type in Ecto schemas * `--verbose` - use verbose output @@ -158,6 +160,7 @@ defmodule Mix.Tasks.Phx.New do assets: :boolean, esbuild: :boolean, tailwind: :boolean, + volt: :boolean, ecto: :boolean, app: :string, module: :string, @@ -266,6 +269,7 @@ defmodule Mix.Tasks.Phx.New do defp validate_project(%Project{opts: opts} = project, path) do check_app_name!(project.app, !!opts[:app]) + check_volt_options!(opts) check_directory_existence!(Map.fetch!(project, path)) check_module_name_validity!(project.root_mod) check_module_name_availability!(project.root_mod) @@ -297,17 +301,20 @@ defmodule Mix.Tasks.Phx.New do if mix_step == [] do builders = Keyword.fetch!(project.binding, :asset_builders) + installable_builders = Enum.reject(builders, &(&1 == :volt)) - if builders != [] do + if installable_builders != [] do Mix.shell().info([:green, "* running ", :reset, "mix assets.setup"]) # First compile only builders so we can install in parallel # TODO: Once we require Erlang/OTP 28, jason may no longer be required - cmd(project, "mix deps.compile jason #{Enum.join(builders, " ")}", log: false) + cmd(project, "mix deps.compile jason #{Enum.join(installable_builders, " ")}", + log: false + ) end tasks = - Enum.map(builders, fn builder -> + Enum.map(installable_builders, fn builder -> cmd = "mix do loadpaths --no-compile --no-listeners + #{builder}.install" Task.async(fn -> cmd(project, cmd, log: false, cd: project.web_path) end) end) @@ -340,6 +347,13 @@ defmodule Mix.Tasks.Phx.New do defp maybe_cd(path, func), do: path && File.cd!(path, func) + defp check_volt_options!(opts) do + if opts[:assets] != false && opts[:volt] && + (opts[:esbuild] == false || opts[:tailwind] == false) do + Mix.raise("--volt cannot be combined with --no-esbuild or --no-tailwind") + end + end + defp install_mix(project, install?) do if install? do cmd(project, "mix deps.get") diff --git a/installer/lib/phx_new/generator.ex b/installer/lib/phx_new/generator.ex index 66bdd01fbd..f2b12f718d 100644 --- a/installer/lib/phx_new/generator.ex +++ b/installer/lib/phx_new/generator.ex @@ -244,8 +244,9 @@ defmodule Phx.New.Generator do dashboard = Keyword.get(opts, :dashboard, true) gettext = Keyword.get(opts, :gettext, true) assets = Keyword.get(opts, :assets, true) - esbuild = Keyword.get(opts, :esbuild, assets) - tailwind = Keyword.get(opts, :tailwind, assets) + volt = assets and Keyword.get(opts, :volt, false) + esbuild = not volt and Keyword.get(opts, :esbuild, assets) + tailwind = not volt and Keyword.get(opts, :tailwind, assets) mailer = Keyword.get(opts, :mailer, true) dev = Keyword.get(opts, :dev, false) from_elixir_install = Keyword.get(opts, :from_elixir_install, false) @@ -282,6 +283,26 @@ defmodule Phx.New.Generator do :error -> adapter_config end + asset_builders = + if volt, + do: [:volt], + else: Enum.filter([tailwind && :tailwind, esbuild && :esbuild], & &1) + + {assets_setup, assets_build, assets_deploy} = + if volt do + volt_profile = if project.in_umbrella?, do: " #{project.web_app}", else: "" + + {[], + ["compile", "volt.build#{volt_profile} --tailwind"], + ["volt.build#{volt_profile} --tailwind", "phx.digest"]} + else + { + Enum.map(asset_builders, &"#{&1}.install --if-missing"), + ["compile" | Enum.map(asset_builders, &"#{&1} #{project.web_app}")], + Enum.map(asset_builders, &"#{&1} #{project.web_app} --minify") ++ ["phx.digest"] + } + end + binding = [ app_name: project.app, app_module: inspect(project.app_mod), @@ -301,9 +322,13 @@ defmodule Phx.New.Generator do signing_salt: random_string(8), lv_signing_salt: random_string(8), in_umbrella: project.in_umbrella?, - asset_builders: Enum.filter([tailwind && :tailwind, esbuild && :esbuild], & &1), - javascript: esbuild, - css: tailwind, + asset_builders: asset_builders, + assets_setup: assets_setup, + assets_build: assets_build, + assets_deploy: assets_deploy, + javascript: volt or esbuild, + css: volt or tailwind, + volt: volt, mailer: mailer, ecto: ecto, html: html, diff --git a/installer/lib/phx_new/interactive.ex b/installer/lib/phx_new/interactive.ex index 0df7c46f8d..6048bd94b1 100644 --- a/installer/lib/phx_new/interactive.ex +++ b/installer/lib/phx_new/interactive.ex @@ -15,6 +15,12 @@ defmodule Phx.New.Interactive do {"api", "API-only"} ] + @asset_options [ + {"esbuild", "Esbuild + Tailwind"}, + {"volt", "Volt"}, + {"none", "None"} + ] + def run do catch_abort(fn -> info([:green, "\nInitialize your Phoenix project (press Ctrl+C to abort)\n", :reset]) @@ -29,7 +35,7 @@ defmodule Phx.New.Interactive do yes?("Use binary_id as primary key type?", false) end - %{html: html, live: live, assets: assets} = prompt_web() + %{html: html, live: live, assets: assets, volt: volt} = prompt_web() dashboard = yes?("Include LiveDashboard (monitoring)?") mailer = yes?("Include Swoosh (mailer)?") gettext = yes?("Include Gettext (i18n)?") @@ -43,7 +49,8 @@ defmodule Phx.New.Interactive do dashboard: dashboard, mailer: mailer, gettext: gettext, - assets: assets + assets: assets, + volt: volt ] |> maybe_put_database(database) @@ -78,13 +85,19 @@ defmodule Phx.New.Interactive do defp prompt_web do case prompt_choice("Web interface?", @web_options, "live") do - "api" -> %{html: false, live: false, assets: false} - "html" -> %{html: true, live: false, assets: prompt_assets?()} - "live" -> %{html: true, live: true, assets: prompt_assets?()} + "api" -> %{html: false, live: false, assets: false, volt: false} + "html" -> Map.merge(%{html: true, live: false}, prompt_assets()) + "live" -> Map.merge(%{html: true, live: true}, prompt_assets()) end end - defp prompt_assets?, do: yes?("Include Esbuild + Tailwind?") + defp prompt_assets do + case prompt_choice("Assets?", @asset_options, "esbuild") do + "esbuild" -> %{assets: true, volt: false} + "volt" -> %{assets: true, volt: true} + "none" -> %{assets: false, volt: false} + end + end defp prompt_choice(question, choices, default) do info("\n#{question}\n") @@ -146,12 +159,14 @@ defmodule Phx.New.Interactive do end defp web_summary(opts) do - case {opts[:html], opts[:live], opts[:assets]} do - {true, true, true} -> "LiveView" - {true, true, false} -> "LiveView (no Esbuild, no Tailwind)" - {true, false, true} -> "HTML" - {true, false, false} -> "HTML (no Esbuild, no Tailwind)" - {false, _, _} -> "API-only" + case {opts[:html], opts[:live], opts[:assets], opts[:volt]} do + {true, true, true, true} -> "LiveView (Volt)" + {true, true, true, _} -> "LiveView" + {true, true, false, _} -> "LiveView (no Esbuild, no Tailwind)" + {true, false, true, true} -> "HTML (Volt)" + {true, false, true, _} -> "HTML" + {true, false, false, _} -> "HTML (no Esbuild, no Tailwind)" + {false, _, _, _} -> "API-only" end end @@ -166,7 +181,8 @@ defmodule Phx.New.Interactive do {!opts[:dashboard], "--no-dashboard"}, {!opts[:mailer], "--no-mailer"}, {!opts[:gettext], "--no-gettext"}, - {!opts[:assets], "--no-assets"} + {!opts[:assets], "--no-assets"}, + {opts[:volt], "--volt"} ], condition, do: flag diff --git a/installer/templates/phx_assets/app.js.eex b/installer/templates/phx_assets/app.js.eex index f22e06639f..ffe0bfa4cd 100644 --- a/installer/templates/phx_assets/app.js.eex +++ b/installer/templates/phx_assets/app.js.eex @@ -14,7 +14,7 @@ // // import "some-package" // -// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file. +// If you have dependencies that try to import CSS, <%= if @volt, do: "Volt", else: "esbuild" %> will generate a separate `app.css` file. // To load it, simply add a second `` to your `root.html.heex` file. <%= if @html do %> // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. @@ -52,7 +52,7 @@ import "phoenix_html" // 1. stream server logs to the browser console // 2. click on elements to jump to their definitions in your code editor // -<%= @live_comment %>if (process.env.NODE_ENV === "development") { +<%= @live_comment %>if (<%= if @volt, do: "import.meta.env.DEV", else: "process.env.NODE_ENV === \"development\"" %>) { <%= @live_comment %> window.addEventListener("phx:live_reload:attached", ({detail: reloader}) => { <%= @live_comment %> // Enable server log streaming to client. <%= @live_comment %> // Disable with reloader.disableServerLogs() diff --git a/installer/templates/phx_assets/tsconfig.json.eex b/installer/templates/phx_assets/tsconfig.json.eex index a9401b623c..870c7a5d1c 100644 --- a/installer/templates/phx_assets/tsconfig.json.eex +++ b/installer/templates/phx_assets/tsconfig.json.eex @@ -1,8 +1,8 @@ // This file is needed on most editors to enable the intelligent autocompletion // of LiveView's JavaScript API methods. You can safely delete it if you don't need it. // -// Note: This file assumes a basic esbuild setup without node_modules. -// We include a generic paths alias to deps to mimic how esbuild resolves +// Note: This file assumes a basic <%= if @volt, do: "Volt", else: "esbuild" %> setup without node_modules. +// We include a generic paths alias to deps to mimic how <%= if @volt, do: "Volt", else: "esbuild" %> resolves // the Phoenix and LiveView JavaScript assets. // If you have a package.json in your project, you should remove the // paths configuration and instead add the phoenix dependencies to the diff --git a/installer/templates/phx_single/config/config.exs.eex b/installer/templates/phx_single/config/config.exs.eex index 11496cdbc4..db862d56d7 100644 --- a/installer/templates/phx_single/config/config.exs.eex +++ b/installer/templates/phx_single/config/config.exs.eex @@ -31,7 +31,23 @@ config :<%= @app_name %>, <%= @endpoint_module %>, # # For production it's recommended to configure a different adapter # at the `config/runtime.exs`. -config :<%= @app_name %>, <%= @app_module %>.Mailer, adapter: Swoosh.Adapters.Local<% end %><%= if @javascript do %> +config :<%= @app_name %>, <%= @app_module %>.Mailer, adapter: Swoosh.Adapters.Local<% end %><%= if @volt do %> + +# Configure Volt +config :volt, + entry: "assets/js/app.js", + outdir: "priv/static/assets", + target: :es2022, + hash: false, + sourcemap: :hidden, + resolve_dirs: [Path.expand("../deps", __DIR__), Mix.Project.build_path()], + tailwind: [ + css: "assets/css/app.css", + sources: [ + %{base: "lib/", pattern: "**/*.{ex,heex,eex}"}, + %{base: "assets/", pattern: "**/*.{js,ts,jsx,tsx}"} + ] + ]<% else %><%= if @javascript do %> # Configure esbuild (the version is required) config :esbuild, @@ -52,7 +68,7 @@ config :tailwind, --output=priv/static/assets/css/app.css ), cd: Path.expand("..<%= if @in_umbrella, do: "/apps/#{@app_name}" %>", __DIR__), - ]<% end %> + ]<% end %><% end %> # Configure Elixir's Logger config :logger, :default_formatter, diff --git a/installer/templates/phx_single/config/dev.exs.eex b/installer/templates/phx_single/config/dev.exs.eex index 165a158c7d..bfd245aae7 100644 --- a/installer/templates/phx_single/config/dev.exs.eex +++ b/installer/templates/phx_single/config/dev.exs.eex @@ -18,10 +18,16 @@ config :<%= @app_name %>, <%= @endpoint_module %>,<%= if @inside_docker_env? do code_reloader: true, debug_errors: true, secret_key_base: "<%= @secret_key_base_dev %>", - watchers: <%= if @javascript or @css do %>[<%= if @javascript do %> + watchers: <%= if @volt do %>[ + volt: {Mix.Tasks.Volt.Dev, :run, [~w(--tailwind)]} + ]<% else %><%= if @javascript or @css do %>[<%= if @javascript do %> esbuild: {Esbuild, :install_and_run, [:<%= @app_name %>, ~w(--sourcemap=inline --watch)]}<%= if @css, do: "," %><% end %><%= if @css do %> tailwind: {Tailwind, :install_and_run, [:<%= @app_name %>, ~w(--watch)]}<% end %> - ]<% else %>[]<% end %> + ]<% else %>[]<% end %><% end %><%= if @volt do %> + +config :volt, :server, + prefix: "/assets", + watch_dirs: ["lib/"]<% end %> # ## SSL Support # diff --git a/installer/templates/phx_single/formatter.exs.eex b/installer/templates/phx_single/formatter.exs.eex index 71188d1921..c334a0f38d 100644 --- a/installer/templates/phx_single/formatter.exs.eex +++ b/installer/templates/phx_single/formatter.exs.eex @@ -1,6 +1,6 @@ [ import_deps: [<%= if @ecto do %>:ecto, :ecto_sql, <% end %>:phoenix],<%= if @ecto do %> - subdirectories: ["priv/*/migrations"],<% end %><%= if @html do %> - plugins: [Phoenix.LiveView.HTMLFormatter],<% end %> - inputs: [<%= if @html do %>"*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"<% else %>"*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"<% end %><%= if @ecto do %>, "priv/*/seeds.exs"<% end %>] + subdirectories: ["priv/*/migrations"],<% end %><%= if @html or @volt do %> + plugins: [<%= Enum.join(Enum.filter([@html && "Phoenix.LiveView.HTMLFormatter", @volt && "Volt.Formatter"], & &1), ", ") %>],<% end %> + inputs: [<%= if @html do %>"*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"<% else %>"*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"<% end %><%= if @ecto do %>, "priv/*/seeds.exs"<% end %><%= if @volt do %>, "assets/**/*.{js,ts,jsx,tsx}"<% end %>] ] diff --git a/installer/templates/phx_single/mix.exs.eex b/installer/templates/phx_single/mix.exs.eex index abbaa1b405..0ab4284999 100644 --- a/installer/templates/phx_single/mix.exs.eex +++ b/installer/templates/phx_single/mix.exs.eex @@ -52,9 +52,10 @@ defmodule <%= @app_module %>.MixProject do {:phoenix_live_reload, "~> 1.2", only: :dev}, {:phoenix_live_view, "~> 1.1.0"}, {:lazy_html, ">= 0.1.0", only: :test},<% end %><%= if @dashboard do %> - {:phoenix_live_dashboard, "~> 0.8.3"},<% end %><%= if @javascript do %> + {:phoenix_live_dashboard, "~> 0.8.3"},<% end %><%= if @volt do %> + {:volt, "~> 0.11.0"},<% else %><%= if @javascript do %> {:esbuild, "~> 0.10", runtime: Mix.env() == :dev},<% end %><%= if @css do %> - {:tailwind, "~> 0.3", runtime: Mix.env() == :dev}, + {:tailwind, "~> 0.3", runtime: Mix.env() == :dev},<% end %><% end %><%= if @css do %> {:heroicons, github: "tailwindlabs/heroicons", tag: "v2.2.0", @@ -85,11 +86,9 @@ defmodule <%= @app_module %>.MixProject do "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.reset": ["ecto.drop", "ecto.setup"], test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]<% end %><%= if @asset_builders != [] do %>, - "assets.setup": <%= inspect Enum.map(@asset_builders, &"#{&1}.install --if-missing") %>, - "assets.build": <%= inspect ["compile" | Enum.map(@asset_builders, &"#{&1} #{@app_name}")] %>, - "assets.deploy": [ -<%= Enum.map(@asset_builders, &" \"#{&1} #{@app_name} --minify\",\n") ++ [" \"phx.digest\""] %> - ]<% end %>, + "assets.setup": <%= inspect @assets_setup %>, + "assets.build": <%= inspect @assets_build %>, + "assets.deploy": <%= inspect @assets_deploy %><% end %>, precommit: ["compile --warnings-as-errors", "deps.unlock --unused", "format", "test"] ] end diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs.eex b/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs.eex index dc0792d165..db99a98883 100644 --- a/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs.eex +++ b/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs.eex @@ -15,7 +15,23 @@ config :<%= @web_app_name %>, <%= @endpoint_module %>, layout: false ], pubsub_server: <%= @app_module %>.PubSub, - live_view: [signing_salt: "<%= @lv_signing_salt %>"]<%= if @javascript do %> + live_view: [signing_salt: "<%= @lv_signing_salt %>"]<%= if @volt do %> + +# Configure Volt +config :volt, :<%= @web_app_name %>, + entry: "assets/js/app.js", + outdir: "priv/static/assets", + target: :es2022, + hash: false, + sourcemap: :hidden, + resolve_dirs: [Path.expand("../deps", __DIR__), Mix.Project.build_path()], + tailwind: [ + css: "assets/css/app.css", + sources: [ + %{base: "lib/", pattern: "**/*.{ex,heex,eex}"}, + %{base: "assets/", pattern: "**/*.{js,ts,jsx,tsx}"} + ] + ]<% else %><%= if @javascript do %> # Configure esbuild (the version is required) config :esbuild, @@ -36,4 +52,4 @@ config :tailwind, --output=priv/static/assets/css/app.css ), cd: Path.expand("../apps/<%= @web_app_name %>", __DIR__) - ]<% end %> + ]<% end %><% end %> diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs.eex b/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs.eex index 8c01968cde..7cc421876f 100644 --- a/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs.eex +++ b/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs.eex @@ -18,10 +18,12 @@ config :<%= @web_app_name %>, <%= @endpoint_module %>,<%= if @inside_docker_env? code_reloader: true, debug_errors: true, secret_key_base: "<%= @secret_key_base_dev %>", - watchers: <%= if @javascript or @css do %>[<%= if @javascript do %> + watchers: <%= if @volt do %>[ + volt: {Mix.Tasks.Volt.Dev, :run, [~w(<%= @web_app_name %> --tailwind)]} + ]<% else %><%= if @javascript or @css do %>[<%= if @javascript do %> esbuild: {Esbuild, :install_and_run, [:<%= @web_app_name %>, ~w(--sourcemap=inline --watch)]}<%= if @css, do: "," %><% end %><%= if @css do %> tailwind: {Tailwind, :install_and_run, [:<%= @web_app_name %>, ~w(--watch)]}<% end %> - ]<% else %>[]<% end %> + ]<% else %>[]<% end %><% end %> # ## SSL Support # diff --git a/installer/templates/phx_umbrella/apps/app_name_web/formatter.exs.eex b/installer/templates/phx_umbrella/apps/app_name_web/formatter.exs.eex index 4ea75e02d1..083a2692a2 100644 --- a/installer/templates/phx_umbrella/apps/app_name_web/formatter.exs.eex +++ b/installer/templates/phx_umbrella/apps/app_name_web/formatter.exs.eex @@ -1,5 +1,5 @@ [ - import_deps: [:phoenix],<%= if @html do %> - plugins: [Phoenix.LiveView.HTMLFormatter],<% end %> - inputs: [<%= if @html do %>"*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"<% else %>"*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"<% end %>] + import_deps: [:phoenix],<%= if @html or @volt do %> + plugins: [<%= Enum.join(Enum.filter([@html && "Phoenix.LiveView.HTMLFormatter", @volt && "Volt.Formatter"], & &1), ", ") %>],<% end %> + inputs: [<%= if @html do %>"*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"<% else %>"*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"<% end %><%= if @volt do %>, "assets/**/*.{js,ts,jsx,tsx}"<% end %>] ] diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs.eex b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs.eex index 842b551ce3..9ff0279e2b 100644 --- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs.eex +++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs.eex @@ -44,9 +44,10 @@ defmodule <%= @web_namespace %>.MixProject do {:phoenix_live_reload, "~> 1.2", only: :dev}, {:phoenix_live_view, "~> 1.1.0"}, {:lazy_html, ">= 0.1.0", only: :test},<% end %><%= if @dashboard do %> - {:phoenix_live_dashboard, "~> 0.8.3"},<% end %><%= if @javascript do %> + {:phoenix_live_dashboard, "~> 0.8.3"},<% end %><%= if @volt do %> + {:volt, "~> 0.11.0"},<% else %><%= if @javascript do %> {:esbuild, "~> 0.10", runtime: Mix.env() == :dev},<% end %><%= if @css do %> - {:tailwind, "~> 0.3", runtime: Mix.env() == :dev}, + {:tailwind, "~> 0.3", runtime: Mix.env() == :dev},<% end %><% end %><%= if @css do %> {:heroicons, github: "tailwindlabs/heroicons", tag: "v2.2.0", @@ -70,11 +71,9 @@ defmodule <%= @web_namespace %>.MixProject do [ setup: ["deps.get"<%= if @asset_builders != [] do %>, "assets.setup", "assets.build"<% end %>]<%= if @ecto do %>, test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]<% end %><%= if @asset_builders != [] do %>, - "assets.setup": <%= inspect Enum.map(@asset_builders, &"#{&1}.install --if-missing") %>, - "assets.build": <%= inspect ["compile" | Enum.map(@asset_builders, &"#{&1} #{@web_app_name}")] %>, - "assets.deploy": [ -<%= Enum.map(@asset_builders, &" \"#{&1} #{@web_app_name} --minify\",\n") ++ [" \"phx.digest\""] %> - ]<% end %> + "assets.setup": <%= inspect @assets_setup %>, + "assets.build": <%= inspect @assets_build %>, + "assets.deploy": <%= inspect @assets_deploy %><% end %> ] end end diff --git a/installer/templates/phx_web/components/layouts/root.html.heex.eex b/installer/templates/phx_web/components/layouts/root.html.heex.eex index e2ac3f4587..a077ff1c79 100644 --- a/installer/templates/phx_web/components/layouts/root.html.heex.eex +++ b/installer/templates/phx_web/components/layouts/root.html.heex.eex @@ -6,9 +6,11 @@ <.live_title default="<%= @app_module %>" suffix=" ยท Phoenix Framework" phx-no-format>{assigns[:page_title]} <%= if not @css do %> - <% end %> + <% end %><%= if @volt do %> + <% else %> <%= if @css do %> + <% end %><%= if @css do %>