将博客、文档和其他静态页面添加到 Phoenix 应用程序。该库无缝集成到您的路由器中,并内置支持使用 frontmatter 渲染 markdown、语法突出显示、编译时缓存等。
def deps do
[
{ :phoenix_pages , "~> 0.1" }
]
end
安装到 Phoenix 应用程序的推荐方法是将其添加到lib/myapp_web.ex
中的router
函数中,并将myapp
替换为应用程序的名称:
def router do
quote do
use Phoenix.Router , helpers: false
use PhoenixPages , otp_app: :myapp
# ...
end
end
现在您可以使用pages/4
宏添加新路由:
scope "/" , MyAppWeb do
pipe_through :browser
get "/" , PageController , :home
pages "/:page" , PageController , :show , from: "priv/pages/**/*.md"
end
这将从priv/pages
读取所有 Markdown 文件,并为每个文件创建一个新的 GET 路由。 :page
段将替换为相对于基目录的路径和文件名(不带扩展名)(请参阅定义路径)。
您还需要将:show
处理程序添加到lib/myapp_web/controllers/page_controller.ex
:
defmodule MyAppWeb.PageController do
use MyAppWeb , :controller
# ...
def show ( conn , _params ) do
render ( conn , "show.html" )
end
end
最后,在lib/myapp_web/controllers/page_html/show.html.heex
添加一个模板。页面渲染的 markdown 将在inner_content
分配中可用:
< main >
<%= @inner_content % >
</ main >
就是这样!现在尝试在priv/pages/hello.md
创建文件并访问/hello
。
为了防止mix format
向pages
宏添加括号(类似于其他 Phoenix Router 宏),请将:phoenix_pages
添加到.formatter.exs
:
[
import_deps: [ :ecto , :ecto_sql , :phoenix , :phoenix_pages ]
]
Frontmatter 允许使用 YAML 格式将特定于页面的变量包含在 Markdown 文件的顶部。如果您要设置 frontmatter 变量(可选),它们必须是文件中的第一个变量,并且必须设置在三虚线之间:
---
title : Hello World
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.
要指定每个页面中期望哪些 frontmatter 值,请设置attrs
选项:
pages "/:page" , PageController , :show ,
from: "priv/pages/**/*.md" ,
attrs: [ :title , author: nil ]
Atom 值将被视为必需,如果任何页面中缺少原子值,则会引发编译错误。键值必须位于列表的最后,并且通过定义默认值将被视为可选。属性列表中未定义的任何 frontmatter 值都将被默默丢弃。
有效的属性值将在分配中可用:
< main >
< h1 > <%= @title % > </ h1 >
< h2 :if = {@author} > <%= @author % > </ h2 >
<%= @inner_content % >
</ main >
Phoenix Pages 使用 Makeup 项目进行语法突出显示。要启用,请将特定语言的词法分析器添加到项目依赖项中。 Phoenix Pages 将获取新的依赖项并开始突出显示您的代码块,无需任何进一步的配置。默认情况下不包含词法分析器。
`{:makeup_c, "~> 0.0"}`
`{:makeup_diff, "~> 0.0"}`
`{:makeup_elixir, "~> 0.0"}`
`{:makeup_erlang, "~> 0.0"}`
`{:makeup_graphql, "~> 0.0"}`
`{:makeup_eex, "~> 0.0"}`
`{:makeup_html, "~> 0.0"}`
`{:makeup_js, "~> 0.0"}`
`{:makeup_json, "~> 0.0"}`
`{:makeup_rust, "~> 0.0"}`
`{:makeup_sql, "~> 0.0"}`
如果您选择的语言不受支持,请考虑编写一个新的 Makeup 词法分析器来为社区做出贡献。否则,您可以通过在render_options
中设置code_class_prefix: "language-"
和syntax_highlighting: false
来使用基于 JS 的语法高亮工具,例如highlight.js。
接下来,将下面列出的主题导入到您的 CSS 包中。执行此操作的具体细节很大程度上取决于您的 CSS 配置,但下面包含一些示例。在大多数情况下,您需要将phoenix_pages/css/monokai.css
(或您选择的任何主题)导入到您的包中,并确保deps
作为供应商目录包含在内。
使用 ESBuild 安装程序,将env
选项添加到config/config.exs
:
config :esbuild ,
version: "0.17.18" ,
default: [
cd: Path . expand ( "../assets" , __DIR__ ) ,
env: % { "NODE_PATH" => Path . expand ( "../deps" , __DIR__ ) } ,
args: ~w ( --bundle --outdir=../priv/static/assets js/app.js )
]
然后在app.js
中:
import "phoenix_pages/css/monokai.css" ;
使用 Sass 安装程序,将--load-path
标志添加到config/config.exs
:
config :dart_sass ,
version: "1.62.0" ,
default: [
cd: Path . expand ( "../assets" , __DIR__ ) ,
args: ~w ( --load-path=../deps css/app.scss ../priv/static/assets/app.css )
]
然后在app.scss
中:
@import " phoenix_pages/css/monokai " ;
按照此处所述安装postcss-import
插件,并将以下内容添加到assets/postcss.config.js
中:
module . exports = {
plugins : {
"postcss-import" : { }
}
}
然后在app.css
中:
@import "../../deps/phoenix_pages/css/monokai" ;
要创建一个包含所有其他页面链接的索引页面,请创建一个普通的 GET 路由并在路由器中使用id
选项以及get_pages/1
和get_pages!/1
:
get "/blog" , BlogController , :index
pages "/blog/:page" , BlogController , :show ,
id: :blog ,
from: "priv/blog/**/*.md" ,
attrs: [ :title , :author , :date ]
defmodule MyAppWeb.BlogController do
use MyAppWeb , :controller
def index ( conn , _params ) do
pages = MyAppWeb.Router . get_pages! ( :blog )
conn
|> assign ( :pages , pages )
|> render ( "index.html" )
end
def show ( conn , _params ) do
render ( conn , "show.html" )
end
end
<.link :for={page < - @pages} navigate = {page.path} >
<%= page.assigns.title % >
</.link>
所有页面文件在编译期间都会被读取和缓存,因此get_pages
函数实际上不会从文件系统中读取任何内容,这使得它们的性能非常好。
从get_pages
函数返回的页面将按文件名排序。如果您想在编译期间而不是在每次页面加载时在控制器中指定不同的顺序,请使用sort
选项:
pages "/blog/:page" , BlogController , :show ,
id: :blog ,
from: "priv/blog/**/*.md" ,
attrs: [ :title , :author , :date ] ,
sort: { :date , :desc }
来自 frontmatter 的任何属性值都可以定义为排序值。
定义页面路径时,编译期间每个生成的页面的:page
段将被替换为从**
和*
派生的值。这与常规路由中的段不同,常规路由中的段在运行时解析为控制器函数的params
属性。
例如,假设您有以下文件结构:
┌── priv/
│ ┌── pages/
│ │ ┌── foo.md
│ │ ├── bar/
│ │ │ ┌── baz.md
在路由器中定义pages "/:page", from: "priv/pages/**/*.md"
将创建两条路由: get "/foo"
和get "/bar/baz"
。您甚至可以将:page
段放在路径中的其他位置,例如/blog/:page
,它将按预期创建get "/blog/foo"
和get "/blog/bar/baz"
。
对于复杂的场景,您可以选择使用捕获组变量而不是:page
段。
假设您具有与上面相同的文件结构,但不希望baz
路径嵌套在/bar
下。您可以使用$2
而不是:page
来定义pages "/$2", from: "priv/pages/**/*.md"
。这将创建两条路由: get "/foo"
和get "/bar"
。
捕获组变量将按顺序包含**
和*
块的值,从$1
开始。请记住, **
将匹配所有文件以及零个或多个目录和子目录,而*
将匹配文件名末尾、下一个点或下一个斜杠之前的任意数量的字符。
有关通配符模式的更多信息,请查看 Path.wildcard/2。
除了可自定义的 Markdown 选项外,Markdown 渲染默认还支持 IAL 属性。这意味着您可以使用语法{:attr}
将 HTML 属性添加到任何块级元素。
例如,要创建<h1 class="foobar">Header</h1>
的渲染输出:
# Header{:.foobar}
属性可以是以下之一:
{:#id}
定义 ID{:.className}
定义类名{:name=value}
、 {:name="value"}
或{:name='value'}
定义任何其他属性要定义多个属性,请用空格分隔它们: {:#id name=value}
。
如果您在运行mix phx.server
时添加、删除或更改页面,它们将自动在缓存中替换,并且无需重新启动即可生效。要在页面更改时实时重新加载,请添加到config/dev.exs
中端点配置的模式列表:
config :myapp , MyAppWeb.Endpoint ,
live_reload: [
patterns: [
# ...
~r " priv/pages/.*(md)$ " ,
# ...
]
]