All in the <head> – Ponderings and code by Drew McLellan –

Adding the noopener attribute to CommonMark

Over on Notist I’m using the PHP League CommonMark Markdown parser to convert Markdown to HTML.

One recommendation that Google’s Lighthouse audits recommend is that rel="noopener" be added to any external links. There’s an entire article explaining why this is a positive move for both security and performance.

I initially couldn’t figure out how best to do this, but it turns out it’s really simple. CommonMark enables you to specify and register custom renderers and they even have an example of one for adding a class to external links. All I needed to do was slightly modify this to add the rel="noopener" attribute and I was away.

<?php
namespace Notist\Extensions;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Inline\Element\Link;
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
class ExternalLinkRenderer implements InlineRendererInterface
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
{
if (!($inline instanceof Link)) {
throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline));
}
$attrs = [];
$attrs['href'] = $htmlRenderer->escape($inline->getUrl(), true);
if (isset($inline->attributes['title'])) {
$attrs['title'] = $htmlRenderer->escape($inline->data['title'], true);
}
if ($this->isExternalUrl($inline->getUrl())) {
$attrs['target'] = '_blank';
$attrs['rel'] = 'noopener';
}
return new HtmlElement('a', $attrs, $htmlRenderer->renderInlines($inline->children()));
}
private function isExternalUrl($url)
{
return parse_url($url, PHP_URL_HOST) !== $this->host;
}
}

Posting this here, because when I searched I couldn’t find anything interesting about CommonMark, PHP, and noopener so I hope it might help someone else.