Skip to content

Commit d56642b

Browse files
SteffenDEjosevalim
andauthored
allow auto-symlinking node_modules for ColocatedJS (#3988)
* allow auto-symlinking node_modules for ColocatedJS and document node_modules handling Closes #3985. * relative symlinks, umbrella support * make node_modules_location a project config * make it work on Elixir 1.15 * fix * add test * don't link for dependencies * Update lib/phoenix_live_view/colocated_js.ex Co-authored-by: José Valim <jose.valim@dashbit.co> * always symlink --------- Co-authored-by: José Valim <jose.valim@dashbit.co>
1 parent e3910e1 commit d56642b

File tree

3 files changed

+89
-3
lines changed

3 files changed

+89
-3
lines changed

lib/phoenix_component.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3527,7 +3527,7 @@ defmodule Phoenix.Component do
35273527
> Because portals use `<template>` elements under the hood, you cannot query for elements
35283528
> inside of a portal when using `Phoenix.LiveViewTest.element/3` and other LiveViewTest functions.
35293529
>
3530-
> Instead, `Phoenix.LiveView.render/1` the portal element itself to an HTML string and do
3530+
> Instead, `Phoenix.LiveViewTest.render/1` the portal element itself to an HTML string and do
35313531
> assertions on those:
35323532
>
35333533
> ```heex

lib/phoenix_live_view/colocated_js.ex

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,26 @@ defmodule Phoenix.LiveView.ColocatedJS do
124124
esbuild configuration for new Phoenix 1.8 applications using `esbuild`'s [alias option](https://esbuild.github.io/api/#alias),
125125
as can be seen in the config snippet above (`--alias=@=.`).
126126
127+
If your `node_modules` location is not `assets/node_modules` or `node_modules`, you may need to
128+
configure the `:node_modules_path` option:
129+
130+
```elixir
131+
# mix.exs
132+
def project do
133+
[
134+
...
135+
compilers: [:phoenix_live_view] ++ Mix.compilers(),
136+
phoenix_live_view: [colocated_js: [node_modules_path: "assets/node_modules"]],
137+
...
138+
]
139+
end
140+
```
141+
142+
This example shows the default behavior.
143+
144+
Note: In contrast to `:target_directory`, the `:node_modules_path` is a project
145+
specific setting you need to set in your `mix.exs`.
146+
127147
## Options
128148
129149
Colocated JavaScript can be configured through the attributes of the `<script>` tag.
@@ -236,6 +256,7 @@ defmodule Phoenix.LiveView.ColocatedJS do
236256
clear_manifests!()
237257
files = clear_outdated_and_get_files!()
238258
write_new_manifests!(files)
259+
maybe_link_node_modules!()
239260
end
240261

241262
defp clear_manifests! do
@@ -359,15 +380,54 @@ defmodule Phoenix.LiveView.ColocatedJS do
359380
File.write!(Path.join(target_dir, manifest), content)
360381
end
361382

362-
defp settings do
383+
defp maybe_link_node_modules! do
384+
settings = project_settings()
385+
386+
case Keyword.get(settings, :node_modules_path, {:fallback, "assets/node_modules"}) do
387+
{:fallback, rel_path} ->
388+
location = Path.absname(rel_path)
389+
do_symlink(location)
390+
391+
path when is_binary(path) ->
392+
location = Path.absname(path)
393+
do_symlink(location)
394+
end
395+
end
396+
397+
defp relative_to_target(location) do
398+
if function_exported?(Path, :relative_to, 3) do
399+
apply(Path, :relative_to, [location, target_dir(), [force: true]])
400+
else
401+
Path.relative_to(location, target_dir())
402+
end
403+
end
404+
405+
defp do_symlink(node_modules_path) do
406+
relative_node_modules_path = relative_to_target(node_modules_path)
407+
408+
with {:error, reason} when reason != :eexist <-
409+
File.ln_s(relative_node_modules_path, Path.join(target_dir(), "node_modules")) do
410+
IO.warn(
411+
"Failed to symlink node_modules folder for Phoenix.LiveView.ColocatedJS: #{inspect(reason)}"
412+
)
413+
end
414+
end
415+
416+
defp global_settings do
363417
Application.get_env(:phoenix_live_view, :colocated_js, [])
364418
end
365419

420+
defp project_settings do
421+
Mix.Project.config()
422+
|> Keyword.get(:phoenix_live_view, [])
423+
|> Keyword.get(:colocated_js, [])
424+
end
425+
366426
defp target_dir do
367427
default = Path.join(Mix.Project.build_path(), "phoenix-colocated")
368428
app = to_string(Mix.Project.config()[:app])
369429

370-
settings()
430+
global_settings()
371431
|> Keyword.get(:target_directory, default)
372432
|> Path.join(app)
373433
end

test/phoenix_live_view/colocated_js_test.exs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,30 @@ defmodule Phoenix.LiveView.ColocatedJSTest do
218218
assert File.exists?(manifest)
219219
assert File.read!(manifest) == "export const hooks = {};\nexport default {};"
220220
end
221+
222+
test "symlinks node_modules folder if exists" do
223+
node_path = Path.expand("../../assets/node_modules", __DIR__)
224+
225+
if not File.exists?(node_path) do
226+
on_exit(fn -> File.rm_rf!(node_path) end)
227+
end
228+
229+
File.mkdir_p!(Path.join(node_path, "foo"))
230+
Phoenix.LiveView.ColocatedJS.compile()
231+
232+
symlink =
233+
Path.join(
234+
Mix.Project.build_path(),
235+
"phoenix-colocated/phoenix_live_view/node_modules"
236+
)
237+
238+
assert File.exists?(symlink)
239+
link = File.read_link!(symlink)
240+
241+
if function_exported?(Path, :relative_to, 3) do
242+
assert String.starts_with?(link, "../")
243+
end
244+
245+
assert "foo" in File.ls!(symlink)
246+
end
221247
end

0 commit comments

Comments
 (0)