{
"$type": "site.standard.document",
"content": {
"$type": "site.standard.content.markdown",
"text": "import Callout from '../../components/mdx/Callout.astro'\n\nRecently, I ported my portfolio (again) to Astro from Hugo. While I really liked Hugo, I missed the choice JS gave me (plus, I was bored) so here we go!\n\n\n## Porting Data\n\nEverything is markdown in Hugo (mostly), so transitioning to and from was pretty simple; just import the data, build the collections and we're off to the races! The one thing I had to deal with was importing the cover images for the opengraph images, but that wasn't hard and went on without many issues.\n\n## Where are my shortcodes ?\n\nOne thing that I really like about Hugo is how it handles shortcodes. This is how I could generate a stackblitz embed using Hugo: `{{<stackblitz url goes here>}}` That line right there would then be mapped to a component called `stackblitz` where I could describe the html element and I was done! But unfortunately, Astro does not handle shortcodes (or similar) natively, so I had to get creative.\n\nAstro uses [remark](https://github.com/remarkjs/remark) and [rehype](https://github.com/rehypejs/rehype) to transform your markdown collections into html.\n\n<Callout type='info'>\n I've already written about the unified ecosystem, you can checkout that\n article [here](at://did:plc:dgtaz4vldacvqhvvmdvoc4ad/site.standard.publication/3mfbydibiwc7f/articles/markdown-editor)\n</Callout>\n\nThat means that we can hopefully build something within this pipeline and recreate the feeling of shortcodes.\n\n## Enter Remark Directive\n\nTurns out we (kind of) can! There's a remark plugin called [remark-directive](https://github.com/remarkjs/remark-directive) that enables remark to parse directives (basically shortcodes, but maybe more powerful). With this plugin, we can then write a custom function that looks for these directives and \"does something with them\".\n\n### What do we do with a remark directive ?\n\nLet's take a small look at the source of one of my articles:\n\n```md\nAstro uses [remark](https://github.com/remarkjs/remark) and [rehype](https://github.com/rehypejs/rehype) to transform your markdown collections into html.\n\n:::callout(type=\"info\")\nI've already written about the unified ecosystem, you can checkout that article [here](at://did:plc:dgtaz4vldacvqhvvmdvoc4ad/site.standard.publication/3mfbydibiwc7f/articles/markdown-editor)\n:::\n```\n\nThe thing with the 3 `:` is a remark directive. When I traverse the tree of the article (an abstract syntax tree describing the repo), I can check whether the node I'm looking at is a directive. If I find one, I can then check the type of directive (there are 3) and finally get its attributes.\n\nFor example, the following snippet\n\n```md\n::stackblitz{#mg-fbr-express}\n```\n\nContains a node that is a `leafDirective` with an id of `mg-fbr-express`\n\n<Callout type=\"info\">\nthe attributes section of the directive supports multiple elements separated by a whitespace like this: `::codesandbox{projectType=\"devbox\" projectid=\"cwmtww\"}`\n</Callout>\n\nGetting the node's attributes is pretty straightforward from there: they're in\n\n```js\nnode.attributes\n```\n\nAnd now we have everything we need to create our custom elements!\n\n\n## Web Components\n\nWeb Components are not new (they have been baseline available since 2021) but I don't think I've ever seen them much out in the wild. They have lots of advantages and will work pretty well in this use case, but they also come with some drawbacks.\n\n### The Good\n\nWeb components, as stated above, are pretty easy to get up and running and put in a page. Here's the code for my youtube embed web component:\n\n```ts\nclass Youtube extends HTMLElement {\n constructor() {\n super()\n }\n\n connectedCallback() {\n this.render()\n }\n\n render() {\n const iframe = document.createElement('iframe')\n iframe.src = `https://www.youtube.com/embed/${this.getAttribute('videoid')}`\n this.innerHTML = `<div style=\"width: 100%; min-height: 500px;\"><iframe class=\"w-full min-h-[500px]\" src=\"https://www.youtube.com/embed/${this.getAttribute(\n 'videoId',\n )}\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe></div>`\n }\n}\n\ncustomElements.define('wc-youtube', Youtube)\n```\n\nThis little snippet does all the heavy lifting to generate an iframe tag with the provided videoid and all the necessary styles and (potentially) logic.\n\nNotice the last line; that's where we tell the browser that there's a new valid tag (called `wc-youtube`) that is represented by the `Youtube` class.\n\n\n\n### The Bad\n\nWell, if web components are so nice, why is everyone bothering with frontend frameworks ?\n\nWell, because it’s not all roses. There are some design principles that you need to take into account if you want to use web components. If I had to choose one thing that \"grinds my gears\" about web components is that they are encapsulated; while this has some benefits in my use case this caused quite some headaches.\n\nI tried for quite some time to have TailwindCSS (which I'm using to style this whole website) pick up and generate the styling required by the web components. Because of this encapsulation (called the shadow DOM, by the way) this proved pretty hard - I probably would have needed to spend too much time looking into it (maybe next time?).\n\n## The Compromise\n\nSo, what did I do ? I simply rewrote the styles in plain css. I didn't need that much styling to begin with, so why bother ?\n\n## How can you do this?\n\n### The Markdown\n\nUsing Astro, you can modify the config file to load any custom remark and rehype extensions, so first thing we'll need to do is install [remark-directive](https://github.com/remarkjs/remark-directive) and add it to your astro config, which could look something like this:\n\n```json\n markdown: {\n remarkPlugins: [\n remarkDirective\n ]\n }\n```\n\nOnce that is done, we can start adding our custom detection logic\n\n<Callout type='warning'>\n make sure you add this logic after the remarkDirective inclusion!\n</Callout>\n\nLet's take the example I made above for Stackblitz: the detection logic could be something like this:\n\n```js\nfunction() {\n return function(tree, file) {\n visit(tree, function (node) {\n if (\n node.type == 'containerDirective' ||\n node.type == 'leafDirective'\n ) {\n const data = node.data || (node.data = {})\n const attributes = node.attributes || {}\n const id = attributes.id\n switch (node.name) {\n //you could add more types right here\n case 'stackblitz':\n if (!id) {\n file.fail('Unexpected missing `id` on `stackblitz` directive', node)\n }\n data.hName = 'wc-stackblitz'\n data.hProperties = {\n projectid: id\n }\n break\n }\n }\n })\n }\n}\n```\n\nAnd that's all you need to detect a basic directive in markdown to a custom html tag!\n\n### The HTML\n\nNow that the element is inserted into the abstract tree of the page, it will be renderer with all the data you have attached to it (in case of the stackblitz example, just an id). All that's left is to write the webcomponent to handle that tag. Once that is done, you just need to make sure to include it in the required pages.\n\n<Callout type='info'>\n in my case, I just added them to the page responsible for showing any blog\n post\n</Callout>\n\n\n\n## Should you do this?\n\nHonestly I don't know; this \"stack\" fit my use case pretty well and is, by definition, portable (I could use these web components on basically any website). Could I have found another solution? Probably. Was this fun? Kind of. Was this interesting? Definitely.",
"version": "1.0"
},
"path": "/articles/remark-directives-web-components",
"publishedAt": "2025-02-21T00:00:00.000Z",
"site": "at://did:plc:dgtaz4vldacvqhvvmdvoc4ad/site.standard.publication/3mfbydibiwc7f",
"tags": [
"javascript",
"markdown"
],
"textContent": "import Callout from '../../components/mdx/Callout.astro'\n\nRecently, I ported my portfolio (again) to Astro from Hugo. While I really liked Hugo, I missed the choice JS gave me (plus, I was bored) so here we go!\n\nPorting Data\n\nEverything is markdown in Hugo (mostly), so transitioning to and from was pretty simple; just import the data, build the collections and we're off to the races! The one thing I had to deal with was importing the cover images for the opengraph images, but that wasn't hard and went on without many issues.\n\nWhere are my shortcodes ?\n\nOne thing that I really like about Hugo is how it handles shortcodes. This is how I could generate a stackblitz embed using Hugo: That line right there would then be mapped to a component called where I could describe the html element and I was done! But unfortunately, Astro does not handle shortcodes (or similar) natively, so I had to get creative.\n\nAstro uses remark and rehype to transform your markdown collections into html.\n\n I've already written about the unified ecosystem, you can checkout that\n article here\n\nThat means that we can hopefully build something within this pipeline and recreate the feeling of shortcodes.\n\nEnter Remark Directive\n\nTurns out we (kind of) can! There's a remark plugin called remark-directive that enables remark to parse directives (basically shortcodes, but maybe more powerful). With this plugin, we can then write a custom function that looks for these directives and \"does something with them\".\n\nWhat do we do with a remark directive ?\n\nLet's take a small look at the source of one of my articles:\n\nThe thing with the 3 is a remark directive. When I traverse the tree of the article (an abstract syntax tree describing the repo), I can check whether the node I'm looking at is a directive. If I find one, I can then check the type of directive (there are 3) and finally get its attributes.\n\nFor example, the following snippet\n\nContains a node that is a with an id of \n\nthe attributes section of the directive supports multiple elements separated by a whitespace like this: \n\nGetting the node's attributes is pretty straightforward from there: they're in\n\nAnd now we have everything we need to create our custom elements!\n\nWeb Components\n\nWeb Components are not new (they have been baseline available since 2021) but I don't think I've ever seen them much out in the wild. They have lots of advantages and will work pretty well in this use case, but they also come with some drawbacks.\n\nThe Good\n\nWeb components, as stated above, are pretty easy to get up and running and put in a page. Here's the code for my youtube embed web component:\n\nThis little snippet does all the heavy lifting to generate an iframe tag with the provided videoid and all the necessary styles and (potentially) logic.\n\nNotice the last line; that's where we tell the browser that there's a new valid tag (called ) that is represented by the class.\n\nThe Bad\n\nWell, if web components are so nice, why is everyone bothering with frontend frameworks ?\n\nWell, because it’s not all roses. There are some design principles that you need to take into account if you want to use web components. If I had to choose one thing that \"grinds my gears\" about web components is that they are encapsulated; while this has some benefits in my use case this caused quite some headaches.\n\nI tried for quite some time to have TailwindCSS (which I'm using to style this whole website) pick up and generate the styling required by the web components. Because of this encapsulation (called the shadow DOM, by the way) this proved pretty hard - I probably would have needed to spend too much time looking into it (maybe next time?).\n\nThe Compromise\n\nSo, what did I do ? I simply rewrote the styles in plain css. I didn't need that much styling to begin with, so why bother ?\n\nHow can you do this?\n\nThe Markdown\n\nUsing Astro, you can modify the config file to load any custom remark and rehype extensions, so first thing we'll need to do is install remark-directive and add it to your astro config, which could look something like this:\n\nOnce that is done, we can start adding our custom detection logic\n\n make sure you add this logic after the remarkDirective inclusion!\n\nLet's take the example I made above for Stackblitz: the detection logic could be something like this:\n\nAnd that's all you need to detect a basic directive in markdown to a custom html tag!\n\nThe HTML\n\nNow that the element is inserted into the abstract tree of the page, it will be renderer with all the data you have attached to it (in case of the stackblitz example, just an id). All that's left is to write the webcomponent to handle that tag. Once that is done, you just need to make sure to include it in the required pages.\n\n in my case, I just added them to the page responsible for showing any blog\n post\n\nShould you do this?\n\nHonestly I don't know; this \"stack\" fit my use case pretty well and is, by definition, portable (I could use these web components on basically any website). Could I have found another solution? Probably. Was this fun? Kind of. Was this interesting? Definitely.",
"title": "Custom markdown components with remark and web components",
"updatedAt": "2026-05-17T13:09:08.897Z"
}