prettier-edge
v1.0.0
Published
Prettier plugin for formatting AdonisJS Edge templates.
Maintainers
Readme
prettier-plugin-edge
A Prettier plugin for AdonisJS Edge templates.
One pass formats your HTML, your Edge tags and interpolations, the JavaScript inside them, and the embedded <script> / <style> blocks.
@if(showFooter)
<footer class="border-t border-gray-200">
<div class="container mx-auto py-8">
@each(section in sections)
<h3>{{section.title}}</h3>
<ul>
@each(item in section.items)
<li><a href="{{item.href}}">{{item.label}}</a></li>
@end
</ul>
@end
</div>
</footer>
@end@if(showFooter)
<footer class="border-t border-gray-200">
<div class="container mx-auto py-8">
@each(section in sections)
<h3>{{ section.title }}</h3>
<ul>
@each(item in section.items)
<li><a href="{{ item.href }}">{{ item.label }}</a></li>
@end
</ul>
@end
</div>
</footer>
@endInstall
npm install --save-dev prettier prettier-plugin-edgePrettier auto-detects the plugin for .edge files when it is a project dependency — no .prettierrc changes required.
npx prettier --write "resources/views/**/*.edge"What it formats
| | |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| HTML | Tag and attribute layout, indentation, attribute wrapping at printWidth, self-closing void elements. |
| Edge interpolations | {{ … }}, {{{ … }}}, @{{ … }}, @{{{ … }}}, {{-- … --}}. |
| Edge tags | Block (@if, @each, @component, @slot, @pushTo, @can, @error, …), inline (@let, @include, @inject, @vite, …), void (@!card(...)), branches (@elseif, @else), raw (@raw … @end). |
| Components-as-tags | @card(...) … @end and @!card({...}) with arbitrarily nested object and array arguments. |
| Embedded <script> | Prettier's JavaScript formatter. |
| Embedded <style> | Prettier's CSS formatter. |
| JavaScript in {{ … }} | Prettier's JavaScript formatter, with printWidth adjusted for the host indent. |
| Alpine.js / Livewire attrs | @click="…", @click.prevent="…", @toggle-mobile-nav.window="…" — recognised as HTML attributes, not Edge tags. |
| Preserved verbatim | @raw … @end bodies, <pre>, <textarea>, <!DOCTYPE …>, <?xml … ?>, <![CDATA[ … ]]>, conditional comments, HTML entities. |
Worked examples
<button {{
$props
.merge({ type: 'button', class: ['btn', 'btn-primary'] })
.toAttrs()
}}>
{{{ await $slots.main() }}}
</button>The {{ … }} body is formatted with Prettier's JavaScript formatter, and the printer glues <button {{ and }}> around the indented expression so the layout reads naturally.
@!input.control({
type: 'email',
name: 'email',
required: true,
placeholder: 'Subscribe to our newsletter',
})Object and array literals at the end of a @tag(...) argument list collapse onto the closing ) rather than wrapping it onto its own line.
<div
class="container mx-auto px-5 lg:px-10 py-24 lg:border-x border-woodsmoke-900 relative"
>
…
</div>The plugin never wraps inside a class="…" value — Tailwind users keep their utility ordering intact.
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L2 7l10 5 10-5-10-5z" fill="currentColor" />
<circle cx="12" cy="12" r="3" />
<g transform="translate(4,4)">
<rect width="16" height="16" rx="2" />
</g>
</svg>SVG containers and leaves are treated as block-level so they always sit on their own lines, with a space before /> when attributes fit.
Options
Example .prettierrc:
{
"printWidth": 100,
"singleQuote": true,
"bracketSameLine": true,
"edgeAttributeQuotes": "preserve",
"edgeBlankLinesInBlocks": 2
}See docs/adr/0008-options-surface.md for the rationale and the list of Prettier options deliberately ignored.
Editor integration
Most Prettier IDE integrations pick the plugin up automatically once it is installed. In VS Code the standard Prettier extension is enough — point editor.defaultFormatter at it for .edge files.
// .vscode/settings.json
{
"[edge]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
}Status
Early release. Validated against two production AdonisJS sites — 256 of 256 templates round-trip cleanly. Files that fail to parse are written back unchanged, never corrupted.
The plugin owns its HTML printer end-to-end; it does not delegate to Prettier's html parser. HTML semantics — entity preservation, boolean attributes, <!DOCTYPE>, CDATA, conditional comments, processing instructions, SVG self-closing — are handled in this codebase. See docs/adr/0001-pipeline-shape.md for why.
Roadmap
- [ ] Additional ecosystem tags as user reports surface them.
- [ ] Public
edgeKnownBlockTags/edgeKnownInlineTagsconfig, if community templates show a real need.
How it works
The pipeline runs in this order, each stage scoped to one job:
| Stage | What it does |
| ------------- | ----------------------------------------------------------------------------------------------------------- |
| preprocess | Strip <script>/<style>/<pre>/<textarea> bodies and <?xml … ?> PIs into a side table; mask Alpine.js attributes; extract multi-line tag args. |
| edge-lex | Tokenize Edge constructs via edge-lexer. |
| edge-parse | Walk the token stream and replace each Edge construct with an HTML stand-in; the original node lives in a side table. |
| parse-html | Parse the masked HTML with angular-html-parser. |
| splice | Walk the HTML AST and substitute Edge nodes back in by stand-in id, producing one mixed tree. |
| restore | Pull the raw-text bodies and processing instructions from the side table back onto their nodes. |
| prepare | Async pre-pass: format every JS expression, <script>, and <style> body via prettier.format(…). |
| print | Walk the mixed AST and emit a Prettier doc using group/indent/hardline/softline/line builders. |
The architecture decisions are documented as ADRs under docs/adr/.
Development
npm test # 75 unit + corpus fixtures
UPDATE_SNAPSHOTS=1 npm test # regenerate expected.edge files
EDGE_SMOKE_DIRS=/path/to/views npm test # parse-only check against real templates
EDGE_DEBUG_PARSE=1 npm test # log parse errors instead of swallowingFixtures live under test/fixtures/:
units/<category>/<name>/{input,expected}.edge— focused cases. Optionaloptions.jsonoverrides Prettier options.corpus/<project>/<file>.edge— whole real-world files. Each one is asserted to parse cleanly and be idempotent. A sibling<file>.expected.edgemakes the baseline frozen.
To add a new test case, drop a file in the right place and run UPDATE_SNAPSHOTS=1 npm test. Review the diff in the generated expected.edge before committing.
