diff --git a/README.md b/README.md index f9fe2af..4295f73 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,8 @@ Make sure you have it installed in production too. You might be using `node` in You can make sure you have `node` installed by running `node --version` in your project directory. +You can also use **Bun** instead of Node.js/npm for installing dependencies and running Vite (see [Installation](#installation)); production SSR still uses Node.js. + If you don't want SSR, you can disable it by not setting `NodeJS.Supervisor` in `application.ex`. More on that in the [SSR](#ssr-server-side-rendering) section of this document. ## Installation @@ -91,7 +93,7 @@ LiveSvelte uses [Vite](https://vite.dev/) for building and [phoenix_vite](https: ### Option A — Igniter installer (recommended) -Requires Phoenix 1.8+ and Node.js 19+. +Requires Phoenix 1.8+ and Node.js 19+ (or Bun for package management and builds). #### New project @@ -142,11 +144,12 @@ With phoenix_vite, the layout uses `PhoenixVite.Components.assets` and the endpo Visit `http://localhost:4000/svelte_demo` to confirm the demo Svelte component is working. -Add `--bun` to use Bun instead of npm/npx: +#### Using Bun -```bash -mix igniter.install live_svelte --bun -``` +Use the `--bun` flag to use Bun instead of npm/npx: + +- **Existing project:** `mix igniter.install live_svelte --bun` +- **New project:** `mix igniter.new my_app --with phx.new --install live_svelte --bun` --- @@ -154,6 +157,8 @@ mix igniter.install live_svelte --bun Use this if you prefer not to use Igniter, or need full control over the configuration. +To use **Bun** instead of npm when installing manually, add the `bun` dependency, set `config :phoenix_vite, PhoenixVite.Bun, ...`, and use `phoenix_vite.bun` in mix aliases and `PhoenixVite.Bun` in the Vite watcher. See the [Installation guide](https://hexdocs.pm/live_svelte/installation.html) for details. + **1.** Add dependencies to `mix.exs`: ```elixir diff --git a/guides/deployment.md b/guides/deployment.md index 1fd6669..ce6c22d 100644 --- a/guides/deployment.md +++ b/guides/deployment.md @@ -4,7 +4,7 @@ Deploying a LiveSvelte application requires Node.js on the server for SSR (serve ## Requirements -- **Node.js 19+** on the production server (for `LiveSvelte.SSR.NodeJS`) +- **Node.js 19+** on the production server (for `LiveSvelte.SSR.NodeJS`). If you use Bun for local builds, the production server still needs Node.js 19+ for SSR (the SSR bundle is run by Node.js). - Standard Phoenix/Elixir deployment tooling (releases, Docker, etc.) ## Build Steps diff --git a/guides/installation.md b/guides/installation.md index 9b81e42..3852fc6 100644 --- a/guides/installation.md +++ b/guides/installation.md @@ -4,7 +4,7 @@ LiveSvelte uses [Vite](https://vitejs.dev/) for both client and SSR builds, repl ## Prerequisites -- **Node.js 19+** — required for SSR (server-side rendering) +- **Node.js 19+ or Bun** — for package management and Vite builds. Production SSR (when using `LiveSvelte.SSR.NodeJS`) still requires Node.js 19+ on the server. - **Elixir 1.17+** - **Phoenix 1.8+** — required for the Igniter installer - **Igniter** — the installation scaffolding tool @@ -31,11 +31,14 @@ For a **new** project with LiveSvelte pre-installed: mix igniter.new my_app --with phx.new --install live_svelte ``` -Use the `--bun` flag to use `bun` instead of `npm`: +#### Using Bun -```bash -mix igniter.install live_svelte --bun -``` +To use **Bun** instead of Node.js/npm for package management and Vite: + +- **Existing project:** `mix igniter.install live_svelte --bun` +- **New project:** `mix igniter.new my_app --with phx.new --install live_svelte --bun` + +After install, `mix assets.setup` and `mix assets.build` use Bun (e.g. `bun install`, phoenix_vite's Bun task) instead of npm. ### Step 3: Install JS dependencies and build @@ -54,7 +57,7 @@ Running `mix igniter.install live_svelte` makes the following changes to your pr **`package.json`** (at project root) — the installer moves it from `assets/` and adds: - `live_svelte`, `phoenix_vite: "file:./deps/phoenix_vite"` (dev), and Svelte-related deps -**`config/config.exs`** — adds `config :phoenix_vite, PhoenixVite.Npm, ...` +**`config/config.exs`** — adds `config :phoenix_vite, PhoenixVite.Npm, ...` (when using `--bun`, the installer configures `PhoenixVite.Bun` and Bun-based aliases instead). **`assets/vite.config.mjs`** — adds the Svelte plugin, `liveSveltePlugin`, and `ssr: { noExternal: ... }`. A single config is used for both client and SSR builds (no separate `vite.ssr.config.js`); the SSR build is run via `phoenix_vite.npm vite build --ssr js/server.js --outDir ../priv/svelte`. @@ -143,6 +146,19 @@ If you must install manually (e.g. Phoenix < 1.8), the overall steps mirror the 7. Configure SSR in `config/` 8. **For instant Svelte/CSS HMR with phoenix_vite:** In `config/dev.exs`, add to your endpoint `static_url: [host: "localhost", port: 5173]` and in `watchers` add `vite: {PhoenixVite.Npm, :run, [:vite, ~w(dev)]}`. Without these, the layout serves built assets and changes to Svelte components will not hot-reload. +#### Using Bun with manual installation + +To use **Bun** instead of npm when installing manually: + +1. Add the optional dependency in `mix.exs`: `{:bun, ">= 1.5.1 and < 2.0.0-0"}` +2. In `config/config.exs`, use `config :phoenix_vite, PhoenixVite.Bun, ...` (with the same options you would use for `PhoenixVite.Npm`; see [phoenix_vite](https://hexdocs.pm/phoenix_vite) for the Bun API). +3. In `mix.exs` aliases, use `phoenix_vite.bun` instead of `phoenix_vite.npm`: + - `"assets.setup": ["phoenix_vite.bun assets install", "tailwind.install --if-missing"]` + - `"assets.build"`: same two `phoenix_vite.bun vite build ...` commands as in the npm version. +4. In `config/dev.exs` watchers, use `vite: {PhoenixVite.Bun, :run, [:vite, ~w(dev)]}`. + +Production SSR still uses Node.js (`LiveSvelte.SSR.NodeJS`); the server must have Node.js 19+ installed. + ## Disabling SSR If you don't need server-side rendering, disable it globally: diff --git a/guides/introduction.md b/guides/introduction.md index 1bcbd40..4782132 100644 --- a/guides/introduction.md +++ b/guides/introduction.md @@ -49,4 +49,4 @@ Plain LiveView may be sufficient when: | Phoenix | 1.7+ (1.8+ for Igniter installer) | | Phoenix LiveView | 1.0+ | | Elixir | 1.17+ | -| Node.js | 19+ (for SSR) | +| Node.js or Bun | Node.js 19+ for SSR; Bun or Node.js 19+ for tooling | diff --git a/guides/new_phoenix_app_without_ecto.md b/guides/new_phoenix_app_without_ecto.md new file mode 100644 index 0000000..0360da4 --- /dev/null +++ b/guides/new_phoenix_app_without_ecto.md @@ -0,0 +1,192 @@ +# New Phoenix project without Ecto (Igniter + LiveSvelte) + +Step-by-step guide to create a new Phoenix app **without Ecto** inside the `live_svelte` folder, using the Igniter installer and the local LiveSvelte library. + +## Prerequisites + +- Elixir 1.17+ +- Node.js 19+ (or Bun) +- Phoenix 1.8+ (pulled in by Igniter) + +## Step 1: Install the Igniter archive + +From any directory: + +```bash +mix archive.install hex igniter_new +``` + +## Step 2: Create the new Phoenix app inside `live_svelte` + +Go to the `live_svelte` directory and run Igniter with `phx.new` and `--no-ecto`: + +```bash +cd /home/gevera/Projects/elixir/phx_live/live_svelte +mix igniter.new my_app --with phx.new --with-args="--no-ecto" --install live_svelte +``` + +Replace `my_app` with your app name (e.g. `alan_app`). This creates `live_svelte/my_app/` with Phoenix (no Ecto) and LiveSvelte pre-installed. + +To use **Bun** instead of npm: + +```bash +mix igniter.new my_app --with phx.new --with-args="--no-ecto" --install live_svelte --bun +``` + +## Step 3: Use the local LiveSvelte library (development) + +The installer adds LiveSvelte from Hex. To use the **local** LiveSvelte in this repo instead: + +1. **Edit the new app’s `mix.exs`** + Find the `live_svelte` dependency and change it to a path dependency: + + ```elixir + # Change from: + # {:live_svelte, "~> 0.17.x"}, + # To: + {:live_svelte, path: ".."}, + ``` + +2. **Edit the new app’s `package.json`** + Point `live_svelte` and Phoenix deps to the local/relative paths (same layout as `example_project` and the fixed `alan_app`): + + - `live_svelte`: use `"file:.."` (parent directory = LiveSvelte root). + - Phoenix-related deps: use `"file:./deps/..."` (Mix puts them in `my_app/deps/`). + + Example: + + ```json + "dependencies": { + "live_svelte": "file:..", + "phoenix": "file:./deps/phoenix", + "phoenix_html": "file:./deps/phoenix_html", + "phoenix_live_view": "file:./deps/phoenix_live_view", + "topbar": "^3.0.0" + }, + "devDependencies": { + "phoenix_vite": "file:./deps/phoenix_vite", + ... + } + ``` + + If your `package.json` has `file:../deps/...`, change those to `file:./deps/...` and `live_svelte` to `file:..`. + +## Step 4: Install dependencies and run + +From the **new app** directory: + +```bash +cd my_app +mix deps.get +mix assets.setup +mix phx.server +``` + +Open `http://localhost:4000` and visit `/svelte_demo` to confirm LiveSvelte works. + +## Summary + +| Step | Command / action | +|------|-------------------| +| 1 | `mix archive.install hex igniter_new` | +| 2 | `cd live_svelte` then `mix igniter.new my_app --with phx.new --with-args="--no-ecto" --install live_svelte` | +| 3 | In `my_app`: set `{:live_svelte, path: ".."}` in `mix.exs` and fix `package.json` to `live_svelte: "file:.."` and Phoenix deps to `file:./deps/...` | +| 4 | `cd my_app`, `mix deps.get`, `mix assets.setup`, `mix phx.server` | + +Optional: add `--bun` to the `igniter.new` command to use Bun instead of npm. + +--- + +## If `igniter.new` fails with "dependencies were not loaded" + +Igniter can fail with: + +```text +** (RuntimeError) cannot retrieve dependencies information because dependencies were not loaded. +Please invoke one of "deps.loadpaths", "loadpaths", or "compile" Mix task +``` + +That happens when it creates the new app and then tries to compile before the new project’s deps are loaded. The app directory (e.g. `my_app/`) is still created by `phx.new`; only the LiveSvelte install step didn’t run. You can finish setup by hand. + +### Recover the existing app (e.g. `my_app`) + +1. **Go into the new app and load deps:** + + ```bash + cd live_svelte/my_app + mix deps.get + mix compile + ``` + +2. **Add LiveSvelte and Phoenix Vite to `mix.exs`** in the `deps/0` list (and keep `igniter` if it’s there): + + ```elixir + {:phoenix_vite, "~> 0.4"}, + {:live_svelte, path: ".."} + ``` + +3. **Fetch deps and run the LiveSvelte installer:** + + ```bash + mix deps.get + mix igniter.install live_svelte + ``` + + Use `--bun` if you want Bun: `mix igniter.install live_svelte --bun`. + +4. **Fix `package.json` for local LiveSvelte** (same as Step 3 in the main flow): + + - `"live_svelte": "file:.."` + - Phoenix deps: `"file:./deps/phoenix"`, `"file:./deps/phoenix_html"`, `"file:./deps/phoenix_live_view"` + - In devDependencies: `"phoenix_vite": "file:./deps/phoenix_vite"` + +5. **Install assets and run:** + + ```bash + mix assets.setup + mix phx.server + ``` + +--- + +## Alternative: create with `phx.new` then install LiveSvelte + +To avoid `igniter.new` entirely (e.g. if the "dependencies were not loaded" error keeps happening): + +1. **Create a Phoenix app without Ecto** from the `live_svelte` directory: + + ```bash + cd live_svelte + mix phx.new my_app --no-ecto + ``` + + (You need the `phx_new` archive: `mix archive.install hex phx_new`.) + +2. **Add Igniter and LiveSvelte** in the new app’s `mix.exs` (in `deps/0`): + + ```elixir + {:igniter, "~> 0.6", only: [:dev, :test]}, + {:phoenix_vite, "~> 0.4"}, + {:live_svelte, path: ".."} + ``` + +3. **Install deps and run the LiveSvelte installer:** + + ```bash + cd my_app + mix deps.get + mix igniter.install live_svelte + ``` + + Use `mix igniter.install live_svelte --bun` for Bun. + +4. **Fix `package.json`** so the app uses the local LiveSvelte and local Phoenix deps (same as Step 3 above: `live_svelte` → `"file:.."`, Phoenix deps → `"file:./deps/..."`). + +5. **Setup assets and run:** + + ```bash + mix assets.setup + mix phx.server + ``` + +This way you never run `igniter.new`; you use `phx.new` and then `igniter.install live_svelte`. diff --git a/guides/troubleshooting.md b/guides/troubleshooting.md index 6117160..d735d0f 100644 --- a/guides/troubleshooting.md +++ b/guides/troubleshooting.md @@ -60,7 +60,7 @@ This injects Svelte component CSS directly into the JS bundle instead of extract **Cause:** The `liveSveltePlugin` is missing from one or both Vite configs. -**Fix:** Ensure `liveSveltePlugin()` is in `vite.config.mjs` and that `ssr: { noExternal: ... }` is set. The same config is used for both client and SSR builds (via `phoenix_vite.npm vite build --ssr js/server.js ...`). +**Fix:** Ensure `liveSveltePlugin()` is in `vite.config.mjs` and that `ssr: { noExternal: ... }` is set. The same config is used for both client and SSR builds (via `phoenix_vite.npm vite build --ssr js/server.js ...`). If you use Bun, the same steps apply with `phoenix_vite.bun` and `PhoenixVite.Bun`. ```js // assets/vite.config.mjs @@ -236,4 +236,4 @@ config :my_app, MyAppWeb.Endpoint, ] ``` -Without these, the layout serves built assets from `priv/static` and Svelte/CSS changes do not hot-reload. +(If you use Bun, use `PhoenixVite.Bun` and `phoenix_vite.bun` in aliases instead.) Without these, the layout serves built assets from `priv/static` and Svelte/CSS changes do not hot-reload. diff --git a/lib/mix/tasks/live_svelte.install.ex b/lib/mix/tasks/live_svelte.install.ex index 56a6ce2..1614d37 100644 --- a/lib/mix/tasks/live_svelte.install.ex +++ b/lib/mix/tasks/live_svelte.install.ex @@ -68,7 +68,7 @@ defmodule Mix.Tasks.LiveSvelte.Install do "config.exs", :phoenix_vite, [PhoenixVite.Npm, :assets], - {:code, Sourceror.parse_string!(~s|[args: [], cd: __DIR__]|)} + {:code, Sourceror.parse_string!(~s|[args: [], cd: Path.expand("..", __DIR__)]|)} ) end @@ -370,7 +370,7 @@ defmodule Mix.Tasks.LiveSvelte.Install do content, ~r/([ \t]*)(children = \[)/, "\\1node_js_children =\n" <> - "\\1 if Application.compile_env(:live_svelte, :ssr_module, nil) == LiveSvelte.SSR.NodeJS do\n" <> + "\\1 if Application.get_env(:live_svelte, :ssr_module, nil) == LiveSvelte.SSR.NodeJS do\n" <> "\\1 [{NodeJS.Supervisor, [path: LiveSvelte.SSR.NodeJS.server_path(), pool_size: 4]}]\n" <> "\\1 else\n" <> "\\1 []\n" <> diff --git a/test/live_svelte/install_test.exs b/test/live_svelte/install_test.exs index 582d874..868c378 100644 --- a/test/live_svelte/install_test.exs +++ b/test/live_svelte/install_test.exs @@ -146,12 +146,12 @@ defmodule Mix.Tasks.LiveSvelte.InstallTest do assert content =~ "NodeJS.Supervisor" end - test "NodeJS.Supervisor is wrapped in compile_env guard, not unconditional" do + test "NodeJS.Supervisor is wrapped in get_env guard, not unconditional" do result = run_installer() content = file_content(result, "lib/test/application.ex") - assert content =~ "Application.compile_env(:live_svelte, :ssr_module", - "NodeJS.Supervisor must be wrapped in a compile_env guard so it only starts in prod" + assert content =~ "Application.get_env(:live_svelte, :ssr_module", + "NodeJS.Supervisor must be wrapped in a get_env guard so it only starts in prod" refute content =~ ~r/children\s*=\s*\[\s*\{NodeJS\.Supervisor/, "NodeJS.Supervisor must not be a bare unconditional entry in the children list"