From 61b13dd035e408ddd2983eb91c0dff64eda92cee Mon Sep 17 00:00:00 2001 From: brendt Date: Fri, 29 May 2026 14:21:15 +0200 Subject: [PATCH 1/2] wip --- docs/3-packages/03-responsive-image.md | 111 +++++++++++++++++++++++++ docs/3-packages/04-markdown.md | 97 +++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 docs/3-packages/03-responsive-image.md create mode 100644 docs/3-packages/04-markdown.md diff --git a/docs/3-packages/03-responsive-image.md b/docs/3-packages/03-responsive-image.md new file mode 100644 index 000000000..b43a5a4cb --- /dev/null +++ b/docs/3-packages/03-responsive-image.md @@ -0,0 +1,111 @@ +--- +title: Responsive images +description: "A standalone package to render responsive images" +--- + + +## Quickstart + +``` +composer require tempest/responsive-image +``` + +```php +use Tempest\ResponsiveImage\ResponsiveImageFactory; +use Tempest\ResponsiveImage\ResponsiveImageConfig; + +$config = new ResponsiveImageConfig( + srcPath: __DIR__ . '/path/to/image/sources', + publicPath: __DIR__ . '/../public', +); + +$imageFactory = new ResponsiveImageFactory($config); + +$image = $imageFactory->create('/parrot.jpg'); + +echo $image->html; +``` + +```html + +``` + +## In depth + +The goal of this package is to render [responsive images for better web performance](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Responsive_images). You provide it with a single image source, and this package will generate several responsive downscaled versions of that image. It will then move all images to the correct place and render the image HTML for you. Optionally, you can use [tempest/command-bus](/docs/features/command-bus) to generate responsive images in the background. + +### Basic setup + +Responsive images are generated with the `Tempest\ResponsiveImage\ResponsiveImageFactory` class. It takes one argument: a `Tempest\ResponsiveImage\ResponsiveImageConfig` object. This object looks like this: + +```php +use Tempest\ResponsiveImage\ResponsiveImageConfig; +use Intervention\Image\Drivers\Gd\Driver; +use Intervention\Image\ImageManager; + +$config = new ResponsiveImageConfig( + srcPath: __DIR__ . '/../resources/src/', + publicPath: __DIR__ . '/../public/', + async: true, + cache: true, + imageManager: new ImageManager(new Driver()), +); +``` +| Parameter | Description | +|--------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `{:hl-property:srcPath:}` | The path to the directory where all source images are stored. | +| `{:hl-property:publicPath:}` | The path to the public directory where rendered images should be served from. | +| `{:hl-property:async:}` | Whether responsive images should be generated in the background. This paramater is only taken into account if Tempest's command bus is installed. | +| `{:hl-property:cache:}` | Whether generated responsive variants should be cached. If true, then responsive variants won't be generated as long as the main image file exists in the public path. Cache clearing should be done manually on your end. | +| `{:hl-property:imageManager:}` | The Intervention imagemanager. Refer to the [Intervention docs](https://image.intervention.io/v4) for all options. | + +### Image rendering + +With a `ResponsiveImageFactory` in place, you can now render images. The only thing you need to do is pass it the image source (like it would be used in the HTML tag), and the factory will handle the rest. The final result will be an `Image` object that can be rendered to HTML. + +```php +$imageFactory = new ResponsiveImageFactory($config); + +$image = $imageFactory->create('/parrot.jpg'); +// This image `/parrot.jpg` is assumed to be present in the defined `srcPath`. +// It will be copied, together with its responsive variants to `publicPath` + +echo $image->html; + +// +``` + +### Image rendering options + +The factory will fill in and generate the image's `{:hl-property:srcset:}` for you based on image file sizes. However, you can also pass additional attributes to be added to the image's HTML: + +```php +$image = $factory->create( + src: '/parrot.jpg', + alt: 'A parrot', + sizes: [new Size(maxWidth: 1000, width: 300), new Size(maxWidth: 1500, width: 500)], + lazy: true, +); +``` + +```html +A parrot +``` + +## Integrations + +### Async image processing + +You can combine this package with [tempest/command-bus](/docs/features/command-bus) to enable async image processing. This will make it so that the responsive variations of an image are rendered in the background. The main image will still be copied immediately, so you won't have to wait until processing is done. + +Read about how to install Tempest's command bus as a standalone component [here](/docs/extra-topics/standalone-components#tempest-command-bus). + +### Markdown parsing + +This package is used by [`tempest/markdown`](/docs/packages/markdown) to render responsive images from markdown files. \ No newline at end of file diff --git a/docs/3-packages/04-markdown.md b/docs/3-packages/04-markdown.md new file mode 100644 index 000000000..66e2d6807 --- /dev/null +++ b/docs/3-packages/04-markdown.md @@ -0,0 +1,97 @@ +--- +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. + +## Quickstart + +```sh +composer require tempest/markdown +``` + +Render Markdown like this: + +```php +use Tempest\Markdown\Markdown; + +$markdown = new Markdown(); + +$parsed = $markdown->parse(file_get_contents('README.md')); + +echo $parsed->frontMatter['title']; +echo $parsed->html; +``` + +## Integrations + +### Code highlighting + +`tempest/markdown` comes with code highlighting out of the box powered by [`tempest/highlight`](/docs/packages/highlight). You can configure the highlighter by passing a new instance into the markdown parser: + +```php +use Tempest\Markdown\Markdown; +use Tempest\Highlight\Highlighter; + +$markdown = new Markdown( + highlighter: new Highlighter( + // Configure theme, etc + ), +); +``` + +Language definitions work in both inline and pre code blocks: + +
+This is an inline PHP codeblock: `{php}echo "Hello";`
+
+ +
+This is a pre PHP codeblock:
+
+```php
+echo "world";
+```
+
+ +You can disable all code highlighting by passing in `{php}null`: + +```php +$markdown = new Markdown( + highlighter: null, +); +``` + +### 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. + +```php +use Tempest\Markdown\Markdown; +use Tempest\ResponsiveImage\ResponsiveImageConfig; +use Tempest\ResponsiveImage\ResponsiveImageFactory; + +$imageConfig = new ResponsiveImageConfig( + srcPath: __DIR__ . '/../resources/images', + publicPath: __DIR__ . '/../public', +); + +$markdown = new Markdown( + imageFactory: new ResponsiveImageFactory($imageConfig), +); +``` + +## 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. + +Benchmarks are included in this repo and can be run with `composer bench` after installing all dev dependencies. Here are the results on a local machine rendering the full Tempest docs: + +| Package | Memory | Time to parse | +|------------------------|----------|---------------| +| tempest/markdown | 5.944mb | 6.281ms | +| league/commonmark | 21.114mb | 56.993ms | +| michelf/php-markdown | 7.343mb | 23.215ms | +| erusev/parsedown-extra | 8.485mb | 15.163ms | + From ca66df3f8d3523acb9366b5e0db59e721d40d1b3 Mon Sep 17 00:00:00 2001 From: brendt Date: Wed, 3 Jun 2026 08:57:12 +0200 Subject: [PATCH 2/2] wip --- docs/3-packages/04-markdown.md | 232 ++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 5 deletions(-) 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 |