Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# The directory Mix downloads your dependencies sources to.
/deps/
/node_modules/
/packages/*/node_modules/

# Where third-party dependencies like ExDoc output generated docs.
/doc/
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ Markdown can use the same local components:

See the [`.astral` Templates guide](https://hexdocs.pm/astral/astral-templates.html) and [Pages and Layouts guide](https://hexdocs.pm/astral/pages-and-layouts.html).

## Editor support

Astral ships a thin VS Code extension package for `.astral` files. It registers the file type, highlights setup blocks as Elixir, and delegates template highlighting to the Phoenix HEEx grammar. Generated starter projects recommend:

- `elixir-volt.astral-vscode` for `.astral` file registration and snippets.
- `phoenixframework.phoenix` for HEEx syntax highlighting.
- `elixir-lsp.elixir-ls` for Elixir project support.

Longer-term language intelligence is planned as an Elixir/GenLSP server launched from the user's Mix project so it can reuse Astral, Phoenix/HEEx, Elixir, and Volt semantics.

## Content collections

Define typed content collections in Elixir:
Expand Down
20 changes: 19 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Astral's long-term goal is to become an Elixir-native static and hybrid site fra
- **Elixir-first configuration.** Site config is real Elixir returning structs and DSL data, not app-env state or a JavaScript config clone.
- **HEEx-first templates.** `.astral` uses Phoenix/HEEx semantics instead of inventing a JSX-like component language.
- **Parser-backed processing.** Use real parsers for Markdown, HEEx-like templates, HTML-like markup, JavaScript/TypeScript, CSS, XML/SVG, and frontmatter.
- **Elixir-native editor intelligence.** Long-term `.astral` language intelligence should run in the user's Mix project through an Elixir/GenLSP server, reusing Astral, Phoenix/HEEx, Elixir, and Volt semantics instead of reimplementing them in TypeScript.
- **Value before abstraction.** Add hooks and APIs when they unlock user-facing capabilities, not just to mirror another framework's names.

## Implemented foundation
Expand Down Expand Up @@ -113,7 +114,8 @@ Goal: make Astral easy to try for real sites.
- `mix astral.new --template docs`.
- `mix astral.new --template marketing`.
- Search integration for docs/blog templates.
- Syntax highlighting defaults.
- Syntax highlighting defaults for rendered Markdown code blocks.
- Workspace recommendation for the official Astral editor extension in generated starter projects.
- Font examples and, if warranted, a small Elixir-native font helper for local fonts/preload links.
- Related posts examples.
- Redirects.
Expand Down Expand Up @@ -163,6 +165,22 @@ Goal: add optional navigation enhancement without changing the static-first defa
- Decide whether Astral needs a client router, or whether browser-native cross-document view transitions plus userland scripts are enough.
- If a client router is added, design Elixir/HEEx-native APIs for transition names, animation policy, link opt-outs, lifecycle events, and island persistence.

### v0.8 — Editor tooling and language server

Goal: make `.astral` authoring feel first-class without moving site semantics out of Elixir.

- Official VS Code/OpenVSX extension package for `.astral` files, with TextMate grammar, language configuration, file icon, and starter snippets.
- Keep the editor extension thin: file registration, syntax-highlighting fallback, snippets, and LSP process launch.
- Elixir `mix astral.lsp --stdio` task backed by GenLSP, running inside the user's Mix project so it can use the project's Astral, Phoenix/LiveView, Elixir, Volt, and dependency versions.
- Reuse Astral's existing template parser and Phoenix/HEEx parsing for diagnostics instead of maintaining a second TypeScript parser.
- Reuse Elixir tooling where possible for frontmatter/setup-block intelligence, hover, completions, definitions, and diagnostics.
- `.astral` diagnostics for template parse errors, setup block errors, unknown local components, missing layouts, invalid dynamic route declarations, and asset/component references where the project can resolve them cheaply.
- Completions for local components, assigns, slots, layouts, collections, routes, island directives, and common `.astral` snippets.
- Go-to-definition for local components, layouts, pages/routes, content entries, assets, and setup-block Elixir symbols where upstream Elixir tooling can provide locations.
- Document-symbol and outline support for setup blocks, headings, components, style/script blocks, and significant template nodes.
- Formatting strategy that delegates to existing Elixir/HEEx/Volt formatters where possible rather than inventing a separate formatter.
- Longer-term non-VS-Code support through a `tree-sitter-astral` package if Neovim, Helix, Zed, or other tree-sitter-first editors need richer native highlighting.

### v1.0 — Stable Astro-class foundation

- Stable config DSL.
Expand Down
7 changes: 7 additions & 0 deletions examples/basic/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"recommendations": [
"elixir-volt.astral-vscode",
"phoenixframework.phoenix",
"elixir-lsp.elixir-ls"
]
}
10 changes: 9 additions & 1 deletion guides/features/editor-and-typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ Astral projects use ordinary Elixir tooling for site code and Volt tooling for b

## Editors

Use your normal Elixir editor setup for `.ex`, `.exs`, Markdown, and HEEx-style `.astral` templates. `.astral` files use Phoenix HEEx syntax, but Astral does not yet ship a dedicated language-server extension for `.astral` files.
Use your normal Elixir editor setup for `.ex`, `.exs`, Markdown, and HEEx-style `.astral` templates.

For VS Code-compatible editors, Astral provides a thin `.astral` extension package. It registers `.astral` files, highlights setup blocks as Elixir, delegates template highlighting to Phoenix HEEx, and includes small snippets. Astral starter projects include `.vscode/extensions.json` recommendations for:

- `elixir-volt.astral-vscode` — `.astral` language registration, wrapper grammar, and snippets.
- `phoenixframework.phoenix` — HEEx syntax highlighting reused by `.astral` templates.
- `elixir-lsp.elixir-ls` — Elixir project support.

Astral does not yet ship a dedicated language server. The planned direction is an Elixir-native server launched from the user's Mix project, such as `mix astral.lsp --stdio`, so editor intelligence can reuse the project's Astral, Phoenix/HEEx, Elixir, and Volt versions.

For browser code, use the editor support for your chosen frontend files:

Expand Down
4 changes: 2 additions & 2 deletions lib/astral/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ defmodule Astral.Config do
defmacro islands(do: block), do: put_opts_ast(islands: islands_block_to_opts(block))

@doc false
defmacro layouts(), do: put_opts_ast(layouts: "layouts")
defmacro layouts, do: put_opts_ast(layouts: "layouts")

@doc false
defmacro layouts(do: block),
Expand All @@ -124,7 +124,7 @@ defmodule Astral.Config do
do: put_opts_ast([layouts: path] ++ layout_block_to_opts(block))

@doc false
defmacro assets(), do: put_opts_ast(assets: "assets")
defmacro assets, do: put_opts_ast(assets: "assets")

@doc false
defmacro assets(do: block), do: put_opts_ast([assets: "assets"] ++ asset_block_to_opts(block))
Expand Down
42 changes: 24 additions & 18 deletions lib/astral/discovery.ex
Original file line number Diff line number Diff line change
Expand Up @@ -98,29 +98,35 @@ defmodule Astral.Discovery do

defp route_path_pages(path, content, file_route, site) do
if Astral.Template.template?(path) do
case Astral.Template.setup_binding_file(path, page_discovery_assigns(site), site.config) do
{:ok, binding} ->
binding
|> route_paths_from_binding()
|> case do
{:ok, nil} ->
{:error, {:unmatched_dynamic_route, path, file_route.pattern.source}}

{:ok, route_paths} ->
build_route_path_pages(path, content, file_route, route_paths, site.config)

{:error, reason} ->
{:error, {:dynamic_route_paths_failed, path, reason}}
end

{:error, reason} ->
{:error, {:dynamic_route_paths_failed, path, reason}}
end
route_path_pages_from_setup(path, content, file_route, site)
else
{:error, {:unmatched_dynamic_route, path, file_route.pattern.source}}
end
end

defp route_path_pages_from_setup(path, content, file_route, site) do
case Astral.Template.setup_binding_file(path, page_discovery_assigns(site), site.config) do
{:ok, binding} ->
build_route_path_pages_from_binding(path, content, file_route, binding, site.config)

{:error, reason} ->
{:error, {:dynamic_route_paths_failed, path, reason}}
end
end

defp build_route_path_pages_from_binding(path, content, file_route, binding, config) do
case route_paths_from_binding(binding) do
{:ok, nil} ->
{:error, {:unmatched_dynamic_route, path, file_route.pattern.source}}

{:ok, route_paths} ->
build_route_path_pages(path, content, file_route, route_paths, config)

{:error, reason} ->
{:error, {:dynamic_route_paths_failed, path, reason}}
end
end

defp entry_dynamic_pages(path, content, file_route, entries, config) do
entries
|> Map.values()
Expand Down
2 changes: 1 addition & 1 deletion lib/astral/template.ex
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ defmodule Astral.Template do
try do
binding =
with_current_source(source.path, fn ->
apply(module, :__astral_setup__, [assigns_map(assigns)])
module.__astral_setup__(assigns_map(assigns))
end)

{:ok, binding}
Expand Down
19 changes: 16 additions & 3 deletions lib/mix/tasks/astral.install.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ if Code.ensure_loaded?(Igniter) do
mix astral.install

The installer creates Astral config, starter pages, EEx layouts, TypeScript
assets, public files, TypeScript configuration, and Volt formatter/linter
configuration.
assets, public files, TypeScript configuration, VS Code extension
recommendations, and Volt formatter/linter configuration.
"""

use Igniter.Mix.Task
Expand Down Expand Up @@ -143,7 +143,8 @@ if Code.ensure_loaded?(Igniter) do
{"assets/app.ts", app_ts()},
{"assets/styles.css", styles_css()},
{"public/robots.txt", robots_txt()},
{"tsconfig.json", tsconfig()}
{"tsconfig.json", tsconfig()},
{".vscode/extensions.json", vscode_extensions()}
]
end

Expand Down Expand Up @@ -313,6 +314,18 @@ if Code.ensure_loaded?(Igniter) do
"""
end

defp vscode_extensions do
"""
{
"recommendations": [
"elixir-volt.astral-vscode",
"phoenixframework.phoenix",
"elixir-lsp.elixir-ls"
]
}
"""
end

defp formatter do
"""
[
Expand Down
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ defmodule Astral.MixProject do
LICENSE
examples/basic/.formatter.exs
examples/basic/.gitignore
examples/basic/.vscode/extensions.json
examples/basic/README.md
examples/basic/assets/app.ts
examples/basic/assets/env.d.ts
Expand Down
6 changes: 6 additions & 0 deletions packages/vscode/.vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/
test/
*.vsix
.vscode/
.DS_Store
npm-debug.log*
21 changes: 21 additions & 0 deletions packages/vscode/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Danila Poyarkov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
52 changes: 52 additions & 0 deletions packages/vscode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Astral for Visual Studio Code

Language support for Astral `.astral` templates.

## Features

- Registers `.astral` files with VS Code.
- Highlights Astral setup blocks (`--- ... ---`) as Elixir.
- Delegates template highlighting to Phoenix HEEx grammar.
- Includes small snippets for setup blocks and local component calls.

This package is intentionally thin. Astral templates are HEEx-first, so the extension reuses existing Elixir and Phoenix editor support instead of maintaining a second HEEx grammar.

## Dependencies

The extension depends on the official Phoenix extension for HEEx highlighting:

- `phoenixframework.phoenix`

It also recommends ElixirLS for Elixir support:

- `elixir-lsp.elixir-ls`

## Roadmap

Future language intelligence should come from an Elixir-native language server launched from the user's Mix project, for example:

```sh
mix astral.lsp --stdio
```

That server can reuse the project's installed Astral, Phoenix/HEEx, Elixir, and Volt semantics.

## Development

Run headless TextMate grammar tests:

```sh
npm --prefix packages/vscode run test:grammar
```

Update snapshots:

```sh
npm --prefix packages/vscode run update-grammar-snapshots
```

Package a local VSIX:

```sh
npm --prefix packages/vscode run package
```
31 changes: 31 additions & 0 deletions packages/vscode/languages/astral-language-configuration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"comments": {
"blockComment": ["<!--", "-->"]
},
"brackets": [["<", ">"], ["{", "}"], ["(", ")"], ["[", "]"]],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "'", "close": "'" },
{ "open": "\"", "close": "\"" },
{ "open": "`", "close": "`" },
{ "open": "<!--", "close": " -->", "notIn": ["comment", "string"] },
{ "open": "<%!--", "close": " --%>", "notIn": ["comment", "string"] }
],
"surroundingPairs": [
{ "open": "'", "close": "'" },
{ "open": "\"", "close": "\"" },
{ "open": "`", "close": "`" },
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "<", "close": ">" }
],
"folding": {
"markers": {
"start": "^\\s*<!--\\s*#region\\b.*-->",
"end": "^\\s*<!--\\s*#endregion\\b.*-->"
}
}
}
Loading