This project has moved and ships under a new name. Visit the Æsthetic repository from which the module now lives. This repository exists for preservational purposes. All future changes, releases and issues can be tracked in Æsthetic repo.
This module is in its infancy and working towards an official release candidate. Refer to the Language Support before using the module and please note that this readme will be subject to change.
The new generation code beautification tool for formatting HTML, Liquid, CSS/SCSS, JavaScript, TypeScript and more! Prettify is built atop of the Sparser lexing algorithm and its parse approach was adapted from the distributed source code of the late and powerful PrettyDiff.
- Fast, performant and lightweight (45kb gzip).
- Cross platform support. Browser and Node environments.
- Language aware. Automatically infers handling.
- Provides a granular set of formatting rules.
- Uniformed array data structures
- Drop-in solution with no complexities (boomer friendly)
- 15 different languages supported
Currently working on documentation to better inform upon rules and overall architecture. Below are the descriptions used in the Schema Stores and do a great job at explaining each rule.
Note Script mode documentation is still being worked on.
Prettify is mostly geared towards web projects and exists an alternative to Prettier and JS Beautify. It's the perfect choice for projects that leverage the Liquid template language and was developed for usage in the Liquify text editor extension/plugin. Prettify allows developers to comfortably infuse Liquid into different languages without sacrificing beautification support, it intends to be the solution you'd employ when working with the template language.
Below is current support list of languages, their completion status and whether you can run Prettify for beautification. You can leverage on languages above 90% completion, anything below that is not yet ready for the big time. Languages with an above 80% completion status will work with basic structures, but may not be viable in some cases and can be problematic.
Language | Status | Operational | Usage |
---|---|---|---|
XML | 92% Complete | ✓ | Safe enough to use |
HTML | 92% Complete | ✓ | Safe enough to use |
Liquid + HTML | 92% Complete | ✓ | Safe enough to use |
Liquid + CSS | 91% Complete | ✓ | Safe enough to use |
JSON | 92% Complete | ✓ | Safe enough to use |
CSS | 92% Complete | ✓ | Safe enough to use |
SCSS | 85% Complete | ✓ | Use with caution |
Liquid + JSON | 82% Complete | ✓ | Use with caution |
Liquid + JavaScript | 82% Complete | ✓ | Use with caution |
JavaScript | 82% Complete | 𐄂 | Use with caution |
TypeScript | 70% Complete | 𐄂 | Avoid using, many defects |
JSX | 70% Complete | 𐄂 | Avoid using, many defects |
LESS | 60% Complete | 𐄂 | Avoid using, many defects |
TSX | 40% Complete | 𐄂 | Avoid using, many defects |
YAML | 50% Complete | 𐄂 | Do not use, not yet supported |
Those wonderful individuals who come across any bugs or defects. Please inform about them. Edge cases are very important and submitting an issue is a huge help for me and the project.
- Installation
- Usage
- API
- Rules
- Parse Errors
- Inline Control
- Caveats
- Prettify vs Shopify's Liquid Prettier Plugin
- Credits
This module is currently used by the vscode-liquid] extension.
pnpm add @liquify/prettify -D
Because pnpm is dope and does dope shit
The tool provides a granular set of beautification rules. Each supported language exposes different formatting options and keeping the PrettyDiff logic 3 lexer modes are supplied markup
, style
and script
. Each mode can be used to beautify languages within a matching nexus. Prettify will automatically detect the language and forward input to the appropriate lexer for handling but it is recommended that you provide lexer
and/or language
values.
import prettify from '@liquify/prettify';
const code = '<div class="example">{% if x %} {{ x }} {% endif %}</div>';
prettify.format(code, {
language: 'liquid',
indentSize: 2
}).then(output => {
console.log(output)
// Do something with the beautified output
}).catch(error => {
// Print the error
console.error(error);
// Return the original input
return code;
});
Prettify does not yet provide CLI support but will in future releases. The API exports several methods on the default and intends to make usage as simple as possible with respect to extendability for more advanced use cases.
The format method returns a promise and is exposed on the default export. The function requires a string
parameter be passed and accepts an optional second rules
parameter. The format method also exposes 2 additional hook methods that can be invoked before or after beautification. An additional stats
getter is also available which will return some execution information.
import prettify from "@liquify/prettify";
// Formatting Code
prettify.format(source: string, rules?: Options): Promise<string>;
// Hook that will be invoked before formatting
prettify.format.before((rules: Options, input: string) => void | false)
// Hook that will be invoked after formatting
prettify.format.after((output: string, rules: Options) => void | false)
// Returns some statistical information related to the operation
prettify.format.stats: Stats
Returning
false
in either theprettify.format.before
orprettify.format.after
will cancel beautification.
Prettify also exposes a synchronous formatting method on the default. This option is similar to prettify.format
but when an error occurs the prettify.formatSync
method throws an instance of an Error.
import prettify from "@liquify/prettify";
// Formatting Code using Sync
prettify.formatSync(source: string, rules?: Options): string;
The options methods will augment formatting options (rules). Formatting options are persisted, so when you apply changes they are used for every beautification process thereafter. The prettify.options(rules)
method also exposes 2 hook methods. The prettify.options.listen
method allows you to listen for changes applied to options and the prettify.options.rules
getter returns a readonly reference of the current formatting options.
import prettify from "@liquify/prettify";
// Change formatting rules
prettify.options(rules?: Options): Rules;
// Hook listener that will be invoked when options change
prettify.options.listen((rules: Options) => void)
// Returns the current formatting options Prettify is using
prettify.options.rules: Rules
The parse method can be used to inspect the data structures the Prettify constructs. Prettify is using the sparser lexing algorithm under the hood, the generated parse tree returned by this method is representative of sparser's data structures. The method also exposes an additional stats
getter which returns some execution information pertaining to the parse process.
import prettify from "@liquify/prettify";
// The generated sparser data structure
prettify.parse(source: string): Promise<ParseTree>
// Returns some statistical information related to the parse
prettify.parse.stats: Stats
Prettify also exposes a synchronous parse method on the default. This option is similar to prettify.parse
but when an error occurs the prettify.parseSync
method throws an instance of an Error.
import prettify from "@liquify/prettify";
// Parsing code using Sync
prettify.parseSync(source: string, rules?: Options): ParseTree
The language
method is a utility method that Prettify uses in the beautification process. It's typically used for language detection and its how Prettify determines the lexing engine to be used on provided source string input.
import prettify from "@liquify/prettify";
// Detects a language from a string sample
prettify.language(sample: string): Language
// Hook listener which is invoked after language detection
prettify.language.listen((language: Language) => void | Language)
You can augment the language reference detected in the
prettify.language.listen
hook.
The definitions is a named export that exposes a definition list of the available formatting options. The definitions are used when validating rules. This is just an object, nothing really special.
import { definitions } from '@liquify/prettify';
// Print the definitions to console
console.log(definitions);
Prettify provides a granular set of beautification options (rules). The projects Typings explains in good detail the effect each available rule has on code. You can also checkout the Playground to get a better idea of how code will be beautified.
{
grammar: {},
language: 'auto',
lexer: 'auto',
indentSize: 2,
indentChar: ' ',
wrap: 0,
crlf: false,
endNewline: false,
preserveLine: 3,
commentIndent: false,
markup: {
correct: false,
attributeCasing: 'preserve',
attributeSort: false,
attributeSortList: [],
delimiterTrims: 'preserve',
commentNewline: false,
forceAttribute: false,
forceLeadAttribute: false,
forceIndent: false,
ignoreStyles: false,
ignoreScripts: false,
ignoreJson: false,
lineBreakSeparator: 'default',
normalizeSpacing: true,
preserveAttributes: false,
preserveComment: true,
preserveText: true,
preserveCaptures: false,
quoteConvert: 'double',
selfCloseSpace: false,
valueForce: 'intent'
},
json: {
arrayFormat: 'default',
braceAllman: true,
bracePadding: false,
objectIndent: 'indent',
objectSort: false
},
style: {
correct: false,
classPadding: false,
noLeadZero: false,
sortProperties: false,
sortSelectors: false,
quoteConvert: 'none',
functionSpace: false,
},
script: {
arrayFormat: 'default',
braceAllman: false,
bracePadding: false,
braceStyle: 'none',
endComma: 'never',
braceNewline: true,
correct: false,
caseSpace: false,
elseNewline: true,
functionNameSpace: true,
functionSpace: false,
methodChain: 0,
neverFlatten: false,
noCaseIndent: false,
noSemicolon: false,
objectIndent: 'indent',
objectSort: false,
preserveComment: true,
preserveText: true,
quoteConvert: 'single',
ternaryLine: false,
variableList: 'none',
vertical: false,
styleGuide: 'none'
}
}
Global rules will be applied to all lexer modes. You cannot override globals on a per lexer basis. Globals are exposed as first level properties.
{
grammar: {},
language: 'auto',
lexer: 'auto',
indentSize: 2,
indentChar: ' ',
wrap: 0,
crlf: false,
endNewline: false,
preserveLine: 3,
commentIndent: false,
grammar: {},
}
Refer to the typings declaration file for description. Rules will be used when formatting the following languages:
- Liquid
- HTML
- XHTML
- XML
- JSX
- TSX
{
correct: false,
attributeCasing: 'preserve',
attributeSort: false,
attributeSortList: [],
delimiterTrims: 'preserve',
commentNewline: false,
forceAttribute: false,
forceLeadAttribute: false,
forceIndent: false,
ignoreStyles: false,
ignoreScripts: false,
lineBreakSeparator: 'default',
normalizeSpacing: true,
preserveAttributes: false,
preserveComment: true,
preserveText: true,
quoteConvert: 'double',
selfCloseSpace: false,
valueForce: 'intent'
}
Refer to the typings declaration file for description. Rules will be used when formatting the following languages:
- CSS
- SCSS/SASS
- LESS
{
correct: false,
classPadding: false,
noLeadZero: false,
sortProperties: false,
sortSelectors: false,
quoteConvert: 'none',
functionSpace: false
}
Prettify supports Liquid infused style formatting and when encountered it will apply beautification using Markup rules
Refer to the typings declaration file for description. Rules will be used when formatting the following languages:
- JavaScript
- TypeScript
- JSX
- TSX
{
arrayFormat: 'default',
braceAllman: false,
bracePadding: false,
braceStyle: 'none',
endComma: 'never',
braceNewline: true,
correct: false,
caseSpace: false,
elseNewline: true,
functionNameSpace: true,
functionSpace: false,
methodChain: 0,
neverFlatten: false,
noCaseIndent: false,
noSemicolon: false,
objectIndent: 'indent',
objectSort: false,
preserveComment: true,
preserveText: true,
quoteConvert: 'single',
ternaryLine: false,
variableList: 'none',
vertical: false,
styleGuide: 'none'
}
Prettify supports Liquid infused script formatting and when encountered it will apply beautification using Markup rules
Refer to the JSON declaration file for description. Rules will be used when formatting the following languages:
- JSON
{
arrayFormat: 'default',
braceAllman: true,
bracePadding: false,
objectIndent: 'indent',
objectSort: false
}
Prettify partially supports Liquid infused JSON formatting, but you should avoid coupling these 2 language together.
The format
method returns a promise, so when beautification fails and a parse error occurs .catch()
is invoked. The error message will typically inform you of the issue but it's rather blasé and not very informative. There are plans to improve this aspect in future releases.
It's important to note that Liquify and Prettify are using different Parsers. The Liquify parser constructs an AST that provides diagnostic capabilities (ie: linting) whereas the Prettify parser constructs a data~structure. The errors of these tools will differ dramatically. Liquify will give you far more context opposed to Prettify.
import prettify from '@liquify/prettify';
// Invalid code
const code = '{% if x %} {{ x }} {% endless %}';
prettify.format(code).then(output => console.log(output)).catch(error => {
// Print the PrettyDiff error
console.error(error);
// Return the original input
return code;
});
import prettify from '@liquify/prettify';
// Invalid code
const code = '{% if x %} {{ x }} {% endless %}';
try {
const output = prettify.formatSync(code)
} catch (error) {
// Print the PrettyDiff error
console.error(error.message);
// Return the original input
return code;
}
Inline control is supported and can be applied within comments. Inline control allows your to ignore files, code regions or apply custom formatting options. Comments use the following structures:
@prettify-ignore
@prettify-ignore-next
@prettify-ignore-start
@prettify-ignore-end
@prettify: ....
You can prevent Prettify from formatting a file by placing an inline control comment at the type of the document.
{% # @prettify-ignore %}
<div>
<ul>
<li>The entire file will not be formatted</li>
</ul>
</div>
Prettify provides inline formatting support via comments. Inline formatting adopts a similar approach used in linters and other projects. The difference is how inline formats are expressed, in Prettify you express formats using inline annotation at the top of the document with a value of @prettify
followed by either a space of newline.
Not all inline ignore capabilities are operational
<!-- @prettify forceAttribute: true, indentLevel: 4 -->
{% comment %}
@prettify forceAttribute: true, indentLevel: 4
{% endcomment %}
{% # @prettify forceAttribute: true, indentLevel: 4 %}
/* @prettify forceAttribute: true, indentLevel: 4 */
// @prettify forceAttribute: true, indentLevel: 4
Lexer modes provide inline comments control and support ignoring regions (blocks) of code. All content contained between the comments will be preserved and unformatted.
<!-- @prettify-ignore-start -->
<!-- @prettify-ignore-end -->
{% comment %} @prettify-ignore-start {% endcomment %}
{% comment %} @prettify-ignore-end {% endcomment %}
{% # @prettify-ignore-start %}
{% # @prettify-ignore-end %}
/* @prettify-ignore-start */
/* @prettify-ignore-end */
// @prettify-ignore-start
// @prettify-ignore-end
Prettify is comparatively recluse in terms of PnP integrations/extensibility. Depending on your stack and development preferences you may wish to use Prettify together with additional tools like eslint, stylelint or even Prettier. There are a few notable caveats you should be aware before running Prettify, most of which are trivial.
It is not uncommon for developers to use Prettier in their projects but you avoid executing Prettier along-side Prettify. You can easily prevent issues arising by excluding the files Prettify handles by adding them to a .prettierignore
file. More on this below.
Prettify can be used together with tools like ESLint and Stylelint without the need to install additional plugins but the caveats come when you introduce Liquid into the code. Prettify can format Liquid contained in JavaScript, TypeScript, JSX and TSX but tools like ESLint are currently unable to process content of that nature and as such without official linting support for Liquid by these tools it is best to only run Prettify with linters on code that does not contain Liquid.
Developers working with straps like Dawn should take some consideration before running Prettify on the distributed code contained within the project. Dawn is chaotic, novice and it employs some terrible approaches. Using Prettify blindly on the project may lead to problematic scenarios and readability issues.
Developers working with JAMStack static site building tools like Jekyll or 11ty are fine to leverage Prettify but need to be aware that by default the Liquid processing will use the Shopify Liquid variation as a base reference. This means that you may need to disable and customize some rules to prevent Prettify from applying beautification in accordance.
Shopify recently shipped a Liquid prettier plugin but it does not really do much beyond indentation. It's great to see Shopify begin to bring support for Liquid beautification and it's thanks to the brilliant work C.P has been doing on behalf of Shopify and its community which has made that possible. Developers who prefer the Prettier style should indeed choose that solution.
Prettify is cut from a different cloth and takes a complete different approach to both Prettier and the Liquid Prettier Plugin. Under the hood, Prettify implements the Sparser lexing algorithm which allows for all traversal operations to done internally without the need for third party parsers. The generated data structure produced by Prettify has refined context specifically designed for beautification usage.
The Sparser algorithm along side its sister tool PrettyDiff at the time of their adaption into Prettify were efficient at handling Liquid contained in Markup, Script and Style languages. Both these tools allowed me to refine the analysis handling and extend upon their pre-existing logic. While the end product of both Prettify and the Liquid Prettier Plugin are similar, the goals and capabilities differ. The ambition I have for Prettify is make it competitive alternative to Prettier and disrupt the "opinionated" convention imposed which imo is kinda shitty and restrictive.
The Liquid Prettier Plugin appropriates the opinionated conventions of Prettier so when producing output the solution is indirectly impeding itself into your workflow. The restrictions of Prettier is great in a lot of cases but when you need to defer for the status-quo you'll find restrictions. This is a double edged sword and problematic when working with a template language like Liquid due to the manner in which developers infuse and express the syntax with other languages.
Prettify uses the developers intent and refines its result in accordance, this allows you to determine what works best for the project at hand with respecting to correctness. The granular set of beautification rules exposed by Prettify enables developers to progressively adapt the tool to their preferred code style.
Below is a formatting specific feature comparison as of October 2022 for Markup (Liquid + HTML). This a minimal comparison and I have omitted the cumbersome capabilities, overall Shopify's Prettier based solution offers 1/10th of what Prettify currently provides and is around 7x slower.
Feature | Liquid Prettier Plugin | Prettify |
---|---|---|
Tag Indentation | ✓ | ✓ |
HTML Attribute Indentation | ✓ | ✓ |
Comment Formatting | ✓ | ✓ |
Delimiter Spacing | ✓ | ✓ |
Delimiter Trims | 𐄂 | ✓ |
Content Controlled Indentation | 𐄂 | ✓ |
Wrapping Indentation | 𐄂 | ✓ |
Attribute Casing | 𐄂 | ✓ |
Attribute Sorting | 𐄂 | ✓ |
Liquid Attribute Indentations | 𐄂 | ✓ |
Liquid Newline Filters | 𐄂 | ✓ |
Liquid Line Break Separators | 𐄂 | ✓ |
Liquid + CSS/SCSS | 𐄂 | ✓ |
Liquid + JS | 𐄂 | ✓ |
Frontmatter | 𐄂 | ✓ |
Below is the embedded language support comparison. Shopify's solution employs Prettier native formatters when handling regions that contain external languages. Given Prettify is still under heavy development, Shopify's Liquid Prettier Plugin may suffice here but it does not support Liquid infused within the languages whereas Prettify does.
Feature | Tag | Liquid Prettier Plugin | Prettify |
---|---|---|---|
Embedded CSS | <style> |
✓ | ✓ |
Embedded JS | <script> |
✓ | ✓ |
Embedded CSS | {% style %} |
✓ | ✓ |
Embedded CSS | {% stylesheet %} |
✓ | ✓ |
Embedded JS | {% javascript %} |
✓ | ✓ |
Embedded JSON | {% schema %} |
✓ | ✓ |
Embedded CSS + Liquid | {% style %} |
𐄂 | ✓ |
Embedded CSS + Liquid | <style> |
𐄂 | ✓ |
Embedded JS + Liquid | <script> |
𐄂 | ✓ |
Prettify owes its existence to Sparser and PrettyDiff. This project has been adapted from these 2 brilliant tools and while largely refactored + overhauled the original parse architecture remains intact.
PrettyDiff and Sparser
Austin Cheney who is the original author of PrettyDiff and Sparser created these two projects and this module is only possible because of the work he has done. Austin is one of the great minds in JavaScript and I want to thank him for open sourcing these tools.
Both PrettyDiff and Sparser were abandoned in 2019 after a nearly a decade of production. Austin has since created Shared File Systems which is a privacy first point-to-point communication tool, please check it out and also have a read of wisdom which personally helped me become a better developer.