Phoenix.LiveView.ColocatedJS (Phoenix LiveView v1.1.2)
View SourceA special HEEx :type
that extracts any JavaScript code from a co-located
<script>
tag at compile time.
Note: To use ColocatedJS
, you need to run Phoenix 1.8+.
Colocated JavaScript is a more generalized version of Phoenix.LiveView.ColocatedHook
.
In fact, colocated hooks are built on top of ColocatedJS
.
You can use ColocatedJS
to define any JavaScript code (Web Components, global event listeners, etc.)
that do not necessarily need the functionalities of hooks, for example:
<script :type={Phoenix.LiveView.ColocatedJS} name="MyWebComponent">
export default class MyWebComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = "Hello, world!";
}
}
</script>
Then, in your app.js
file, you could import it like this:
import colocated from "phoenix-colocated/my_app";
customElements.define("my-web-component", colocated.MyWebComponent);
In this example, you don't actually need to have special code for the web component
inside your app.js
file, since you could also directly call customElements.define
inside the colocated JavaScript. However, this example shows how you can access the
exported values inside your bundle.
A note on dependencies and umbrella projects
For each application that uses colocated JavaScript, a separate directory is created
inside the phoenix-colocated
folder. This allows to have clear separation between
hooks and code of dependencies, but also applications inside umbrella projects.
While dependencies would typically still bundle their own hooks and colocated JavaScript into a separate file before publishing, simple hooks or code snippets that do not require access to third-party libraries can also be directly imported into your own bundle. If a library requires this, it should be stated in its documentation.
Internals
While compiling the template, colocated JavaScript is extracted into a special folder inside the
Mix.Project.build_path()
, called phoenix-colocated
. This is customizable, as we'll see below,
but it is important that it is a directory that is not tracked by version control, because the
components are the source of truth for the code. Also, the directory is shared between applications
(this also applies to applications in umbrella projects), so it should typically also be a shared
directory not specific to a single application.
The colocated JS directory follows this structure:
_build/$MIX_ENV/phoenix-colocated/
_build/$MIX_ENV/phoenix-colocated/my_app/
_build/$MIX_ENV/phoenix-colocated/my_app/index.js
_build/$MIX_ENV/phoenix-colocated/my_app/MyAppWeb.DemoLive/line_HASH.js
_build/$MIX_ENV/phoenix-colocated/my_dependency/MyDependency.Module/line_HASH.js
...
Each application has its own folder. Inside, each module also gets its own folder, which allows us to track and clean up outdated code.
To use colocated JS from your app.js
, your bundler needs to be configured to resolve the
phoenix-colocated
folder. For new Phoenix applications, this configuration is already included
in the esbuild configuration inside config.exs
:
config :esbuild,
...
my_app: [
args:
~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.),
cd: Path.expand("../assets", __DIR__),
env: %{
"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]
}
]
The important part here is the NODE_PATH
environment variable, which tells esbuild to also look
for packages inside the deps
folder, as well as the Mix.Project.build_path()
, which resolves to
_build/$MIX_ENV
. If you use a different bundler, you'll need to configure it accordingly. If it is not
possible to configure the NODE_PATH
, you can also change the folder to which LiveView writes colocated
JavaScript by setting the :target_directory
option in your config.exs
:
config :phoenix_live_view, :colocated_js,
target_directory: Path.expand("../assets/node_modules/phoenix-colocated", __DIR__)
An alternative approach could be to symlink the phoenix-colocated
folder into your node_modules
folder.
Tip
If you remove or modify the contents of the :target_directory
folder, you can use
mix clean --all
and mix compile
to regenerate all colocated JavaScript.
Warning!
LiveView assumes full ownership over the configured :target_directory
. When
compiling, it will delete any files and folders inside the :target_directory
,
that it does not associate with a colocated JavaScript module or manifest.
Imports in colocated JS
The colocated JS files are fully handled by your bundler. For Phoenix apps, this is typically
esbuild
. Because colocated JS is extracted to a folder outside the regular assets
folder,
special care is necessary when you need to import other files inside the colocated JS:
import { someFunction } from "some-dependency";
import somethingElse from "@/vendor/vendored-file";
While dependencies from node_modules
should work out of the box, you cannot simply refer to your
assets/vendor
folder using a relative path. Instead, your bundler needs to be configured to handle
an alias like @
to resolve to your local assets
folder. This is configured by default in the
esbuild configuration for new Phoenix 1.8 applications using esbuild
's alias option,
as can be seen in the config snippet above (--alias=@=.
).
Options
Colocated JavaScript can be configured through the attributes of the <script>
tag.
The supported attributes are:
name
- The name under which the default export of the script is available when importing the manifest. If omitted, the file will be imported for side effects only.key
- A custom key to use for the export. This is used byPhoenix.LiveView.ColocatedHook
to export all hooks under the namedhooks
export (export { ... as hooks }
). For example, you could set this toweb_components
for each colocated script that defines a web component and then import all of them asimport { web_components } from "phoenix-colocated/my_app"
. Defaults to:default
, which means the export will be available under the manifest'sdefault
export. This needs to be a valid JavaScript identifier. When given, aname
is required as well.extension
- a custom extension to use when writing the extracted file. The default isjs
.manifest
- a custom manifest file to use instead of the defaultindex.js
. For example,web_components.ts
. If you change the manifest, you will need to change the path of your JavaScript imports accordingly.