4 | <%= @inner_content %>
5 |
6 |
--------------------------------------------------------------------------------
/lib/autocomplete.ex:
--------------------------------------------------------------------------------
1 | defmodule Autocomplete do
2 | @moduledoc """
3 | Autocomplete keeps the contexts that define your domain
4 | and business logic.
5 |
6 | Contexts are also responsible for managing your data, regardless
7 | if it comes from the database, an external API or others.
8 | """
9 | end
10 |
--------------------------------------------------------------------------------
/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :autocomplete, AutocompleteWeb.Endpoint,
6 | http: [port: 4002],
7 | server: false
8 |
9 | # Print only warnings and errors during test
10 | config :logger, level: :warn
11 |
--------------------------------------------------------------------------------
/test/autocomplete_web/views/layout_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.LayoutViewTest do
2 | use AutocompleteWeb.ConnCase, async: true
3 |
4 | # When testing helpers, you may want to import Phoenix.HTML and
5 | # use functions such as safe_to_string() to convert the helper
6 | # result into an HTML string.
7 | # import Phoenix.HTML
8 | end
9 |
--------------------------------------------------------------------------------
/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
1 | ## `msgid`s in this file come from POT (.pot) files.
2 | ##
3 | ## Do not add, change, or remove `msgid`s manually here as
4 | ## they're tied to the ones in the corresponding POT file
5 | ## (with the same domain).
6 | ##
7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge`
8 | ## to merge POT files into PO files.
9 | msgid ""
10 | msgstr ""
11 | "Language: en\n"
12 |
--------------------------------------------------------------------------------
/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This is a PO Template file.
2 | ##
3 | ## `msgid`s here are often extracted from source code.
4 | ## Add new translations manually only if they're dynamic
5 | ## translations that can't be statically extracted.
6 | ##
7 | ## Run `mix gettext.extract` to bring this file up to
8 | ## date. Leave `msgstr`s empty as changing them here has no
9 | ## effect: edit them in PO (`.po`) files instead.
10 |
11 |
--------------------------------------------------------------------------------
/test/autocomplete_web/live/page_live_test.exs:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.PageLiveTest do
2 | use AutocompleteWeb.ConnCase
3 |
4 | import Phoenix.LiveViewTest
5 |
6 | test "disconnected and connected render", %{conn: conn} do
7 | {:ok, page_live, disconnected_html} = live(conn, "/")
8 | assert disconnected_html =~ "Welcome to Phoenix!"
9 | assert render(page_live) =~ "Welcome to Phoenix!"
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/autocomplete_web/templates/layout/live.html.leex:
--------------------------------------------------------------------------------
1 |
2 |
<%= live_flash(@flash, :info) %>
5 |
6 |
<%= live_flash(@flash, :error) %>
9 |
10 | <%= @inner_content %>
11 |
12 |
--------------------------------------------------------------------------------
/lib/autocomplete/items.ex:
--------------------------------------------------------------------------------
1 | defmodule Autocomplete.Items do
2 | @items [
3 | %{id: 1, name: "Item 1", image_url: "images/items/1.png"},
4 | %{id: 2, name: "Item 2", image_url: "images/items/2.png"},
5 | %{id: 3, name: "Item 3", image_url: "images/items/3.png"},
6 | %{id: 4, name: "Item 4", image_url: "images/items/4.png"}
7 | ]
8 |
9 | def list_items() do
10 | @items
11 | end
12 |
13 | def get_item(id) do
14 | Enum.find(@items, &(&1.id === String.to_integer(id)))
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/test/autocomplete_web/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.ErrorViewTest do
2 | use AutocompleteWeb.ConnCase, async: true
3 |
4 | # Bring render/3 and render_to_string/3 for testing custom views
5 | import Phoenix.View
6 |
7 | test "renders 404.html" do
8 | assert render_to_string(AutocompleteWeb.ErrorView, "404.html", []) == "Not Found"
9 | end
10 |
11 | test "renders 500.html" do
12 | assert render_to_string(AutocompleteWeb.ErrorView, "500.html", []) == "Internal Server Error"
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/autocomplete_web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.ErrorView do
2 | use AutocompleteWeb, :view
3 |
4 | # If you want to customize a particular status code
5 | # for a certain format, you may uncomment below.
6 | # def render("500.html", _assigns) do
7 | # "Internal Server Error"
8 | # end
9 |
10 | # By default, Phoenix returns the status message from
11 | # the template name. For example, "404.html" becomes
12 | # "Not Found".
13 | def template_not_found(template, _assigns) do
14 | Phoenix.Controller.status_message_from_template(template)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/autocomplete_web/templates/layout/root.html.leex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= csrf_meta_tag() %>
8 | <%= live_title_tag assigns[:page_title] || "Autocomplete", suffix: " · Phoenix Framework" %>
9 | "/>
10 |
11 |
12 |
13 | <%= @inner_content %>
14 |
15 |
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Autocomplete
2 |
3 | This is the sample repository to my blog post on building a simple but powerful
4 | autocomplete using [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view) and [Alpine.js](https://github.com/alpinejs/alpine).
5 |
6 | ## How does it look?
7 |
8 | 
9 |
10 | ## Up & Running
11 |
12 | To start your Phoenix server:
13 |
14 | * Install dependencies with `mix deps.get`
15 | * Install Node.js dependencies with `npm install` inside the `assets` directory
16 | * Start Phoenix endpoint with `mix phx.server`
17 |
18 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
19 |
--------------------------------------------------------------------------------
/lib/autocomplete_web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.Gettext do
2 | @moduledoc """
3 | A module providing Internationalization with a gettext-based API.
4 |
5 | By using [Gettext](https://hexdocs.pm/gettext),
6 | your module gains a set of macros for translations, for example:
7 |
8 | import AutocompleteWeb.Gettext
9 |
10 | # Simple translation
11 | gettext("Here is the string to translate")
12 |
13 | # Plural translation
14 | ngettext("Here is the string to translate",
15 | "Here are the strings to translate",
16 | 3)
17 |
18 | # Domain-based translation
19 | dgettext("errors", "Here is the error message to translate")
20 |
21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
22 | """
23 | use Gettext, otp_app: :autocomplete
24 | end
25 |
--------------------------------------------------------------------------------
/assets/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {},
3 | "description": " ",
4 | "license": "MIT",
5 | "scripts": {
6 | "deploy": "webpack --mode production",
7 | "watch": "webpack --mode development --watch"
8 | },
9 | "dependencies": {
10 | "alpinejs": "^2.4.1",
11 | "nprogress": "^0.2.0",
12 | "phoenix": "file:../deps/phoenix",
13 | "phoenix_html": "file:../deps/phoenix_html",
14 | "phoenix_live_view": "file:../deps/phoenix_live_view"
15 | },
16 | "devDependencies": {
17 | "@babel/core": "^7.0.0",
18 | "@babel/preset-env": "^7.0.0",
19 | "babel-loader": "^8.0.0",
20 | "copy-webpack-plugin": "^5.1.1",
21 | "css-loader": "^3.4.2",
22 | "mini-css-extract-plugin": "^0.9.0",
23 | "node-sass": "^4.13.1",
24 | "optimize-css-assets-webpack-plugin": "^5.0.1",
25 | "sass-loader": "^8.0.2",
26 | "terser-webpack-plugin": "^2.3.2",
27 | "webpack": "4.41.5",
28 | "webpack-cli": "^3.3.2"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | autocomplete-*.tar
24 |
25 | # If NPM crashes, it generates a log, let's ignore it too.
26 | npm-debug.log
27 |
28 | # The directory NPM downloads your dependencies sources to.
29 | /assets/node_modules/
30 |
31 | # Since we are building assets from assets/,
32 | # we ignore priv/static. You may want to comment
33 | # this depending on your deployment strategy.
34 | /priv/static/
35 |
--------------------------------------------------------------------------------
/config/prod.secret.exs:
--------------------------------------------------------------------------------
1 | # In this file, we load production configuration and secrets
2 | # from environment variables. You can also hardcode secrets,
3 | # although such is generally not recommended and you have to
4 | # remember to add this file to your .gitignore.
5 | use Mix.Config
6 |
7 | secret_key_base =
8 | System.get_env("SECRET_KEY_BASE") ||
9 | raise """
10 | environment variable SECRET_KEY_BASE is missing.
11 | You can generate one by calling: mix phx.gen.secret
12 | """
13 |
14 | config :autocomplete, AutocompleteWeb.Endpoint,
15 | http: [
16 | port: String.to_integer(System.get_env("PORT") || "4000"),
17 | transport_options: [socket_opts: [:inet6]]
18 | ],
19 | secret_key_base: secret_key_base
20 |
21 | # ## Using releases (Elixir v1.9+)
22 | #
23 | # If you are doing OTP releases, you need to instruct Phoenix
24 | # to start each relevant endpoint:
25 | #
26 | # config :autocomplete, AutocompleteWeb.Endpoint, server: true
27 | #
28 | # Then you can assemble a release by calling `mix release`.
29 | # See `mix help release` for more information.
30 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 |
7 | # General application configuration
8 | use Mix.Config
9 |
10 | # Configures the endpoint
11 | config :autocomplete, AutocompleteWeb.Endpoint,
12 | url: [host: "localhost"],
13 | secret_key_base: "b3RMiLBDr/a0/6Tbw+AQTUt/sUYcDmm/pdNief24NRHYy0TcYCJubHUjB+Pjdd3X",
14 | render_errors: [view: AutocompleteWeb.ErrorView, accepts: ~w(html json), layout: false],
15 | pubsub_server: Autocomplete.PubSub,
16 | live_view: [signing_salt: "lQm5SOgQ"]
17 |
18 | # Configures Elixir's Logger
19 | config :logger, :console,
20 | format: "$time $metadata[$level] $message\n",
21 | metadata: [:request_id]
22 |
23 | # Use Jason for JSON parsing in Phoenix
24 | config :phoenix, :json_library, Jason
25 |
26 | # Import environment specific config. This must remain at the bottom
27 | # of this file so it overrides the configuration defined above.
28 | import_config "#{Mix.env()}.exs"
29 |
--------------------------------------------------------------------------------
/test/support/channel_case.ex:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.ChannelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | channel tests.
5 |
6 | Such tests rely on `Phoenix.ChannelTest` and also
7 | import other functionality to make it easier
8 | to build common data structures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | we enable the SQL sandbox, so changes done to the database
12 | are reverted at the end of every test. If you are using
13 | PostgreSQL, you can even run database tests asynchronously
14 | by setting `use AutocompleteWeb.ChannelCase, async: true`, although
15 | this option is not recommended for other databases.
16 | """
17 |
18 | use ExUnit.CaseTemplate
19 |
20 | using do
21 | quote do
22 | # Import conveniences for testing with channels
23 | import Phoenix.ChannelTest
24 | import AutocompleteWeb.ChannelCase
25 |
26 | # The default endpoint for testing
27 | @endpoint AutocompleteWeb.Endpoint
28 | end
29 | end
30 |
31 | setup _tags do
32 | :ok
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/autocomplete/application.ex:
--------------------------------------------------------------------------------
1 | defmodule Autocomplete.Application do
2 | # See https://hexdocs.pm/elixir/Application.html
3 | # for more information on OTP Applications
4 | @moduledoc false
5 |
6 | use Application
7 |
8 | def start(_type, _args) do
9 | children = [
10 | # Start the Telemetry supervisor
11 | AutocompleteWeb.Telemetry,
12 | # Start the PubSub system
13 | {Phoenix.PubSub, name: Autocomplete.PubSub},
14 | # Start the Endpoint (http/https)
15 | AutocompleteWeb.Endpoint
16 | # Start a worker by calling: Autocomplete.Worker.start_link(arg)
17 | # {Autocomplete.Worker, arg}
18 | ]
19 |
20 | # See https://hexdocs.pm/elixir/Supervisor.html
21 | # for other strategies and supported options
22 | opts = [strategy: :one_for_one, name: Autocomplete.Supervisor]
23 | Supervisor.start_link(children, opts)
24 | end
25 |
26 | # Tell Phoenix to update the endpoint configuration
27 | # whenever the application is updated.
28 | def config_change(changed, _new, removed) do
29 | AutocompleteWeb.Endpoint.config_change(changed, removed)
30 | :ok
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | import other functionality to make it easier
8 | to build common data structures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | we enable the SQL sandbox, so changes done to the database
12 | are reverted at the end of every test. If you are using
13 | PostgreSQL, you can even run database tests asynchronously
14 | by setting `use AutocompleteWeb.ConnCase, async: true`, although
15 | this option is not recommended for other databases.
16 | """
17 |
18 | use ExUnit.CaseTemplate
19 |
20 | using do
21 | quote do
22 | # Import conveniences for testing with connections
23 | import Plug.Conn
24 | import Phoenix.ConnTest
25 | import AutocompleteWeb.ConnCase
26 |
27 | alias AutocompleteWeb.Router.Helpers, as: Routes
28 |
29 | # The default endpoint for testing
30 | @endpoint AutocompleteWeb.Endpoint
31 | end
32 | end
33 |
34 | setup _tags do
35 | {:ok, conn: Phoenix.ConnTest.build_conn()}
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/autocomplete_web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.UserSocket do
2 | use Phoenix.Socket
3 |
4 | ## Channels
5 | # channel "room:*", AutocompleteWeb.RoomChannel
6 |
7 | # Socket params are passed from the client and can
8 | # be used to verify and authenticate a user. After
9 | # verification, you can put default assigns into
10 | # the socket that will be set for all channels, ie
11 | #
12 | # {:ok, assign(socket, :user_id, verified_user_id)}
13 | #
14 | # To deny connection, return `:error`.
15 | #
16 | # See `Phoenix.Token` documentation for examples in
17 | # performing token verification on connect.
18 | @impl true
19 | def connect(_params, socket, _connect_info) do
20 | {:ok, socket}
21 | end
22 |
23 | # Socket id's are topics that allow you to identify all sockets for a given user:
24 | #
25 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}"
26 | #
27 | # Would allow you to broadcast a "disconnect" event and terminate
28 | # all active sockets and channels for a given user:
29 | #
30 | # AutocompleteWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
31 | #
32 | # Returning `nil` makes this socket anonymous.
33 | @impl true
34 | def id(_socket), do: nil
35 | end
36 |
--------------------------------------------------------------------------------
/lib/autocomplete_web/live/page_live.ex:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.PageLive do
2 | use AutocompleteWeb, :live_view
3 |
4 | @impl true
5 | def mount(_params, _session, socket) do
6 | {:ok, assign(socket, query: "", results: %{})}
7 | end
8 |
9 | @impl true
10 | def handle_event("suggest", %{"q" => query}, socket) do
11 | {:noreply, assign(socket, results: search(query), query: query)}
12 | end
13 |
14 | @impl true
15 | def handle_event("search", %{"q" => query}, socket) do
16 | case search(query) do
17 | %{^query => vsn} ->
18 | {:noreply, redirect(socket, external: "https://hexdocs.pm/#{query}/#{vsn}")}
19 |
20 | _ ->
21 | {:noreply,
22 | socket
23 | |> put_flash(:error, "No dependencies found matching \"#{query}\"")
24 | |> assign(results: %{}, query: query)}
25 | end
26 | end
27 |
28 | defp search(query) do
29 | if not AutocompleteWeb.Endpoint.config(:code_reloader) do
30 | raise "action disabled when not in development"
31 | end
32 |
33 | for {app, desc, vsn} <- Application.started_applications(),
34 | app = to_string(app),
35 | String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
36 | into: %{},
37 | do: {app, vsn}
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/assets/css/autocomplete.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 24px 0;
3 | }
4 |
5 | .autocomplete {
6 | position: relative;
7 | }
8 |
9 | .suggestions {
10 | position: absolute;
11 | top: 72px;
12 | width: 100%;
13 | border-radius: 6px;
14 | background-color: white;
15 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
16 |
17 | overflow: auto;
18 | max-height: 250px;
19 |
20 | ul {
21 | list-style-type: none;
22 | padding: 0;
23 | margin: 0;
24 | }
25 |
26 | .item {
27 | display: flex;
28 | align-items: center;
29 | padding: 12px 15px;
30 | margin: 0;
31 |
32 | img {
33 | border-radius: 9999px;
34 | width: 50px;
35 | height: 50px;
36 | margin-right: 12px;
37 | }
38 |
39 | &:hover {
40 | cursor: pointer;
41 | }
42 |
43 | &.focus {
44 | background-color: lightblue;
45 | }
46 | }
47 | }
48 |
49 | .selected {
50 | ul {
51 | list-style-type: none;
52 | padding: 0;
53 | margin: 0;
54 | }
55 |
56 | .item {
57 | display: flex;
58 | padding: 12px 15px;
59 | margin: 0;
60 | border-radius: 6px;
61 | margin-bottom: 12px;
62 | border: 1px solid lightgray;
63 |
64 | img {
65 | border-radius: 9999px;
66 | width: 50px;
67 | height: 50px;
68 | margin-right: 12px;
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/autocomplete_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.Router do
2 | use AutocompleteWeb, :router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_live_flash
8 | plug :put_root_layout, {AutocompleteWeb.LayoutView, :root}
9 | plug :protect_from_forgery
10 | plug :put_secure_browser_headers
11 | end
12 |
13 | pipeline :api do
14 | plug :accepts, ["json"]
15 | end
16 |
17 | scope "/", AutocompleteWeb do
18 | pipe_through :browser
19 |
20 | live "/", ItemSearchLive, :index
21 | end
22 |
23 | # Other scopes may use custom stacks.
24 | # scope "/api", AutocompleteWeb do
25 | # pipe_through :api
26 | # end
27 |
28 | # Enables LiveDashboard only for development
29 | #
30 | # If you want to use the LiveDashboard in production, you should put
31 | # it behind authentication and allow only admins to access it.
32 | # If your application does not have an admins-only section yet,
33 | # you can use Plug.BasicAuth to set up some basic authentication
34 | # as long as you are also using SSL (which you should anyway).
35 | if Mix.env() in [:dev, :test] do
36 | import Phoenix.LiveDashboard.Router
37 |
38 | scope "/" do
39 | pipe_through :browser
40 | live_dashboard "/dashboard", metrics: AutocompleteWeb.Telemetry
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/autocomplete_web/live/item_search_live.ex:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.ItemSearchLive do
2 | use AutocompleteWeb, :live_view
3 |
4 | alias Autocomplete.Items
5 |
6 | def mount(_params, _session, socket) do
7 | items = Items.list_items()
8 |
9 | {:ok, assign(socket, suggestions: items, selected: [])}
10 | end
11 |
12 | def handle_event("suggest", %{"search" => search}, socket) do
13 | suggestions = Items.list_items() |> suggest(search)
14 |
15 | {:noreply, assign(socket, suggestions: suggestions)}
16 | end
17 |
18 | def handle_event("submit", params, socket), do: {:noreply, socket}
19 |
20 | def handle_event("select", %{"id" => id}, socket) do
21 | item = Items.get_item(id)
22 | selected = Enum.uniq_by([item] ++ socket.assigns.selected, & &1.id)
23 | suggestions = filter_selected(socket.assigns.suggestions, selected)
24 |
25 | socket =
26 | assign(socket,
27 | selected: selected,
28 | suggestions: suggestions
29 | )
30 |
31 | {:noreply, socket}
32 | end
33 |
34 | defp suggest(items, search) do
35 | Enum.filter(items, fn i ->
36 | i.name
37 | |> String.downcase()
38 | |> String.contains?(String.downcase(search))
39 | end)
40 | end
41 |
42 | defp filter_selected(items, selected) do
43 | Enum.filter(items, fn i -> !Enum.any?(selected, fn s -> i.id == s.id end) end)
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/assets/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const glob = require('glob');
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
4 | const TerserPlugin = require('terser-webpack-plugin');
5 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
6 | const CopyWebpackPlugin = require('copy-webpack-plugin');
7 |
8 | module.exports = (env, options) => {
9 | const devMode = options.mode !== 'production';
10 |
11 | return {
12 | optimization: {
13 | minimizer: [
14 | new TerserPlugin({ cache: true, parallel: true, sourceMap: devMode }),
15 | new OptimizeCSSAssetsPlugin({})
16 | ]
17 | },
18 | entry: {
19 | 'app': glob.sync('./vendor/**/*.js').concat(['./js/app.js'])
20 | },
21 | output: {
22 | filename: '[name].js',
23 | path: path.resolve(__dirname, '../priv/static/js'),
24 | publicPath: '/js/'
25 | },
26 | devtool: devMode ? 'source-map' : undefined,
27 | module: {
28 | rules: [
29 | {
30 | test: /\.js$/,
31 | exclude: /node_modules/,
32 | use: {
33 | loader: 'babel-loader'
34 | }
35 | },
36 | {
37 | test: /\.[s]?css$/,
38 | use: [
39 | MiniCssExtractPlugin.loader,
40 | 'css-loader',
41 | 'sass-loader',
42 | ],
43 | }
44 | ]
45 | },
46 | plugins: [
47 | new MiniCssExtractPlugin({ filename: '../css/app.css' }),
48 | new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
49 | ]
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/lib/autocomplete_web/telemetry.ex:
--------------------------------------------------------------------------------
1 | defmodule AutocompleteWeb.Telemetry do
2 | use Supervisor
3 | import Telemetry.Metrics
4 |
5 | def start_link(arg) do
6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
7 | end
8 |
9 | @impl true
10 | def init(_arg) do
11 | children = [
12 | # Telemetry poller will execute the given period measurements
13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
15 | # Add reporters as children of your supervision tree.
16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
17 | ]
18 |
19 | Supervisor.init(children, strategy: :one_for_one)
20 | end
21 |
22 | def metrics do
23 | [
24 | # Phoenix Metrics
25 | summary("phoenix.endpoint.stop.duration",
26 | unit: {:native, :millisecond}
27 | ),
28 | summary("phoenix.router_dispatch.stop.duration",
29 | tags: [:route],
30 | unit: {:native, :millisecond}
31 | ),
32 |
33 | # VM Metrics
34 | summary("vm.memory.total", unit: {:byte, :kilobyte}),
35 | summary("vm.total_run_queue_lengths.total"),
36 | summary("vm.total_run_queue_lengths.cpu"),
37 | summary("vm.total_run_queue_lengths.io")
38 | ]
39 | end
40 |
41 | defp periodic_measurements do
42 | [
43 | # A module, function and arguments to be invoked periodically.
44 | # This function must call :telemetry.execute/3 and a metric must be added above.
45 | # {AutocompleteWeb, :count_users, []}
46 | ]
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/autocomplete_web/live/page_live.html.leex:
--------------------------------------------------------------------------------
1 |
2 |
<%= gettext "Welcome to %{name}!", name: "Phoenix" %>