Skip to content

edge-js/prettier-edge

prettier-edge

A Prettier plugin for AdonisJS Edge templates.

gh-workflow-image npm-image npm-downloads-image license-image node-image prettier-image

One pass formats your HTML, your Edge tags and interpolations, the JavaScript inside them, and the embedded <script> / <style> blocks.


Before After
@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>
@end

Install

npm install --save-dev prettier prettier-edge

Prettier 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

Dynamic attributes with embedded JavaScript
<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.

Multi-line tag arguments collapse against the parens
@!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.

Long HTML attributes wrap, but the value is never split
<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: containers and self-closing leaves
<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

Standard Prettier options
OptionDefaultNotes
printWidth80Drives HTML attribute wrapping and embedded-JS layout.
tabWidth2Indent unit.
useTabsfalseIndent character.
singleQuotefalsePropagates to embedded JS. HTML attribute quoting is controlled by edgeAttributeQuotes.
endOfLine"lf"
bracketSameLinefalseWhen true, the closing > of a multi-line opening tag glues to the last attribute.
singleAttributePerLinefalseWhen true, every HTML attribute is placed on its own line.
Edge-specific options
OptionDefaultNotes
edgeMustacheSpacing10 or 1 space inside {{ … }}, {{{ … }}}, @{{ … }}, {{-- … --}}.
edgeAttributeQuotes"double"One of "double", "single", "preserve". "preserve" keeps the source quote per attribute.
edgeBlankLinesInBlocks1Maximum blank lines preserved between block-level siblings.

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 / edgeKnownInlineTags config, 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 swallowing

Fixtures live under test/fixtures/:

  • units/<category>/<name>/{input,expected}.edge — focused cases. Optional options.json overrides Prettier options.
  • corpus/<project>/<file>.edge — whole real-world files. Each one is asserted to parse cleanly and be idempotent. A sibling <file>.expected.edge makes 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.

License

MIT

About

A Prettier plugin for AdonisJS Edge templates.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors