diff --git a/docs/3-packages/04-markdown.md b/docs/3-packages/04-markdown.md index 66e2d6807..d91cdecd0 100644 --- a/docs/3-packages/04-markdown.md +++ b/docs/3-packages/04-markdown.md @@ -3,7 +3,7 @@ title: Markdown description: "Fast and extensible Markdown in PHP" --- -`tempest/markdown` is a Markdown parser written in PHP. It's designed to be fast and extensible and has a bunch of additional features built-in like code highlighting, responsive images, tables, and frontmatter support. +`tempest/markdown` is a Markdown parser for server-side Markdown parsing with PHP. It's designed to be fast and extensible, and has a bunch of extended Markdown features built-in like code highlighting, table and div support, responsive images, and frontmatter support. ## Quickstart @@ -24,7 +24,9 @@ echo $parsed->frontMatter['title']; echo $parsed->html; ``` -## Integrations +## Extended features + +One thing that sets `tempest/markdown` apart from other Markdown implementations is that it comes with a bunch of extended features built-in. ### Code highlighting @@ -41,7 +43,7 @@ $markdown = new Markdown( ); ``` -Language definitions work in both inline and pre code blocks: +Language definitions work for both inline and multiline code blocks:
 This is an inline PHP codeblock: `{php}echo "Hello";`
@@ -63,9 +65,28 @@ $markdown = new Markdown(
 );
 ```
 
+### Frontmatter
+
+Add frontmatter like you're used to with other Markdown parsers
+
+```md
+---
+title: Markdown
+description: "Fast and extensible Markdown in PHP"
+---
+
+`tempest/markdown` is a Markdown parser for server-side Markdown parsing with PHP. It's designed to be fast and extensible, and has a bunch of extended Markdown features built-in like code highlighting, table and div support, responsive images, and frontmatter support.
+```
+
+```php
+$parsed = $markdown->parse($contents);
+
+echo $parsed->frontmatter['title'];
+```
+
 ### Responsive images
 
-`tempest/markdown` has support for responsive images powered by [`tempest/responsive-image`](/docs/packages/responsive-image). You'll need to configure the responsive image factory before being able to use it.
+`tempest/markdown` has support for responsive images powered by [`tempest/responsive-image`](/docs/packages/responsive-image). You'll need to configure the responsive image factory to enable this feature.
 
 ```php
 use Tempest\Markdown\Markdown;
@@ -82,6 +103,207 @@ $markdown = new Markdown(
 );
 ```
 
+When enabled, `tempest/markdown` will generate different versions of the same image, and add them as `srcset` entries in the generate HTML.
+
+```md
+![A parrot](/parrot.jpg)
+```
+
+```html
+A parrot
+```
+
+Read more about responsive images [here](/docs/packages/responsive-image).
+
+### Tables
+
+Add tables like you're used to with other Markdown parsers.
+
+```
+| Package                | Memory   | Time to parse |
+|------------------------|----------|---------------|
+| tempest/markdown       | 6.826mb  | 13.273ms      |
+| league/commonmark      | 21.114mb | 56.993ms      |
+| michelf/php-markdown   | 7.343mb  | 23.215ms      |
+| erusev/parsedown-extra | 8.485mb  | 15.163ms      |
+```
+
+```html
+
+
+    
+    
+        
+        
+        
+    
+    
+    
+    
+        
+        
+        
+    
+
+    
+
+    
+
PackageMemoryTime to parse
tempest/markdown6.826mb13.273ms
+``` + +### Divs + +Use `:::` to create divs with optional classes: + +``` +:::alert +This is an important message! +::: +``` + +```html +
+ This is an important message! +
+``` + +### Strikethrough + +Use `~~` to strikethrough text: + +``` +~~This was wrong~~ +``` + +```html +This was wrong +``` + +### Target blank links + +Prepend `*` to a link's URI to open the link in a new tab: + +``` +[Click me](*https://stitcher.io) +``` + +```html +Click me +``` + +## Adding custom features + +`tempest/markdown` is meant to be extended. Adding custom parser rules is done in two steps: first you provide a `LexerRule`, this is a class that determines when your custom parsing logic should be triggered. Next you'll use a `Token` to render your selected Markdown code in any way you'd like. + +Let's work with an example. Say you want to add support for including custom HTML snippets. It could look something like this: + +``` +Hello world + +{{ snippets/call-to-action.html }} +``` + +The first step to adding this new feature is detecting when we run into our custom `{{ }}` syntax. This is done with a `LexerRule`. Let's call our implementation `SnippetRule`: + +```php +use Tempest\Markdown\Lexer; +use Tempest\Markdown\Rule; +use Tempest\Markdown\Token; + +final readonly class SnippetRule implements Rule +{ + public function shouldLex(Lexer $lexer): bool + { + // Our rule takes effect as soon as we run into `{{` + + return $lexer->comesNext('{{', length: 2); + } + + public function lex(Lexer $lexer): ?Token + { + // We'll consume all { characters + $lexer->consumeWhile('{'); + + // Then we'll consume and store the snippet path itself until we encounter the closing } characters + $snippet = $lexer->consumeUntil('}'); + + // Then we'll consume the closing } characters + $lexer->consumeWhile('}'); + + // Finally, we return a token with the snippet + return new SnippetToken(trim($snippet)); + } +} +``` + +:::info +For performance reasons, it's best to explcitly add the `{:hl-property:length:}` parameter to the `{:hl-property:comesNext:}` call. It's not required, but it will make parsing faster. If possible, also try not to rely on regex within your lexer rules, as it may become a performance bottleneck. +::: + +So that's our rule implementation: we consumed our custom `{{ path }}` syntax, and created a token with that path. Let's take a look at the token implementation next. + +The token's responsibility is to parse the content into HTML. + +```php +use Tempest\Markdown\Parser; +use Tempest\Markdown\Token; +use Tempest\View\Exceptions\ViewNotFound; +use Tempest\View\ViewRenderer; +use function Tempest\Container\get; + +final readonly class SnippetToken implements Token +{ + public function __construct( + public string $path, + ) {} + + public function parse(Parser $parser): string + { + // Of course, you should add validation here depending on your use case + + return file_get_contents($this->path); + } +} +``` + +Finally, you should add your custom rule to the Markdown parser: + +```php +use Tempest\Markdown\Markdown; + +$markdown = new Markdown(); + +$markdown->prependRules( + new SnippetRule(), +); +``` + +If needed, you can fully customize the Markdown parser by overwriting all rules: + +```php +$markdown = new Markdown()->withRules( + new NewLineRule(), + new FrontMatterRule(), + new HeadingRule(), + new QuoteRule(), + new PreRule(), + new DivRule(), + new ThinRulerRule(), + new ThickRulerRule(), + new ListRule(), + new OrderedListRule(), + new TableRule(), + new HtmlRule(), + new ParagraphRule(), +); +``` + +This way you have full control over how the parser works. For more inspiration, you can look at the [rules that come built-in with the package](https://github.com/tempestphp/markdown/tree/main/src/LexerRules). + ## Performance This package began as a challenge to make a more performant Markdown parser in pure PHP. The primary performance gain is from not relying on regex but instead using a simple lexer to tokenize Markdown files and convert them to HTML. @@ -90,7 +312,7 @@ Benchmarks are included in this repo and can be run with `composer bench` after | Package | Memory | Time to parse | |------------------------|----------|---------------| -| tempest/markdown | 5.944mb | 6.281ms | +| tempest/markdown | 6.826mb | 13.273ms | | league/commonmark | 21.114mb | 56.993ms | | michelf/php-markdown | 7.343mb | 23.215ms | | erusev/parsedown-extra | 8.485mb | 15.163ms |