pngwn / MDsveX

A markdown preprocessor for Svelte.
https://mdsvex.pngwn.io
MIT License
2.35k stars 102 forks source link

`remark-directive` does not work. #533

Open vypxl opened 1 year ago

vypxl commented 1 year ago

The remark-directive plugin does not function as expected when used with mdsvex. I could not figure out why.

Minimal example:

import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkDirective from 'remark-directive'
import remark2Rehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'

import { compile } from 'mdsvex'

let md = `
:::note
yeet
:::
`

const unifiedResult = await unified()
  .use(remarkParse)
  .use(remarkDirective)
  .use(remark2Rehype)
  .use(rehypeStringify)
  .process(md)

console.log(unifiedResult.value)

let mdsvexResult = await compile(md, {
  remarkPlugins: [remarkDirective],
  smartypants: false,
})

console.log(mdsvexResult.code)

Expected behaviour: The two logs show the same compiled html output.

Actual behaviour:

<!-- unified output -->
<div><p>yeet</p></div>

<!-- mdsvex output -->
<p>:::note
yeet
:::</p>

To run this example:

sporritt commented 1 year ago

It looks like this is due to this project requiring version ^8.0.2 of remark-parse, which does not use micromark. From the remark changelog:

https://github.com/remarkjs/remark/releases?page=2

The first version of remark-parse to use micromark is 9.0.0.

sporritt commented 1 year ago

Updating the code to use ^9.0.0 gives this error:

      /my/computer/node_modules/mdsvex/dist/main.cjs.js:19595
    block_tokenizers.svelteBlock = parse_svelte_block;
                                 ^

TypeError: Cannot set properties of undefined (setting 'svelteBlock')
    at Function.mdsvex_parser (/Users/simon/programming/jsplumb/mds-test/node_modules/mdsvex/dist/main.cjs.js:19595:34)

which is from the mdsvex_parser code:

function mdsvex_parser() {
    const Parser = this.Parser;
    const block_tokenizers = Parser.prototype.blockTokenizers;
    const methods = Parser.prototype.blockMethods;

    block_tokenizers.svelteBlock = parse_svelte_block;
    block_tokenizers.svelteTag = parse_svelte_tag;
    block_tokenizers.html = blockHtml;
    block_tokenizers.indentedCode = indentedCode;

    methods.splice(methods.indexOf('html'), 0, 'svelteBlock');
    methods.splice(methods.indexOf('html'), 0, 'svelteTag');
}

This is something other projects have run into:

https://github.com/remarkjs/remark/issues/499

...which led me here:

https://github.com/zestedesavoir/zmarkdown/issues/465

and as far as i can tell remark-grid-tables has not been updated; it still lists remark 12 as a dependency (pre micromark).

I'm brand new to this Remark world so I could well be wrong but it feels like upgrading this would involve adding micromark extensions rather than injecting the tokenizers/methods as is currently done. I found this other "grid tables" plugin which uses Remark 13, for example:

https://github.com/adobe/remark-gridtables/blob/main/src/index.js#L32

Would be great to get this running and I'd be happy to help.

vypxl commented 1 year ago

Thanks for the insights @sporritt. I ended up implementing a custom markdown pipeline for my project, using up to date versions of all the things.

Maybe I'll modify it to an extent that it can also render markdown files as components, should not be that hard.

vypxl commented 1 year ago

To this issue: Maybe there should be a hint somewhere in the documentation, that plugins requiring micromark do not work with mdsvex.

lubiah commented 1 year ago

@vypxl , you can use it with Mdsvex

lubiah commented 1 year ago

You just need to install a bunch of additional plugins. I hope this isn't too late

artemkovalyov commented 9 months ago

@lubiah , what plugins? Can you share a recipe?

artemkovalyov commented 9 months ago

@vypxl , can you share your pipeline that makes remark-directive work?

vypxl commented 9 months ago

@vypxl , can you share your pipeline that makes remark-directive work?

I ended up building my own solution without using mdsvex.

You can look at it in my repo at vypxl/website, somewhere should be a file markdown.ts that contains the rendering pipeline.

lubiah commented 9 months ago

Hello @artemkovalyov ,you need to install remark-parse and remark-frontmatter before remark directive will work

PeppeL-G commented 8 months ago

I have an mdsvex project where I got remark-directive to work (got it working like a year ago, or so), and now I've updated all npm packages to the latest versions, and now remark-directive no longer works for me. In my .md file I have text like the following:

...

We have developed an :online-editor you can use...

...

And remark-directive no longer creates an AST node for :online-editor. The closes node I get looks like this:

{
   type: 'text',
   value: 'We have developed an :online-editor you can use...',
   position: {
     start: { line: 28, column: 1, offset: 4086 },
     end: { line: 28, column: 223, offset: 4308 },
     indent: []
   }
}

It worked good before when I used the following versions:

"remark-directive": "^2.0.1",
"remark-parse": "^10.0.1",

But when I now try to update to the latest versions:

"remark-directive": "^3.0.0",
"remark-parse": "^11.0.0",

It no longer works. If anyone knows why and what I can do to make it work, please let me know!

gushogg-blake commented 3 months ago

If you can tolerate a flash of unstyled content and only basic directive functionality (wrapping content in a div with a class), a workaround for this is to do a post-processing step on mount, for example:

<script>
import {onMount} from "svelte";

export let data;

let {content} = data;

let main;

onMount(function() {
    // find which element has the paragraphs in it
    let [test] = [...main.querySelectorAll("p")];
    let page = test.parentNode;
    let node = page.firstChild;
    let next;

    // move all paragraphs between :::directive and ::: into a div
    // with class="directive"

    while (node) {
        next = node.nextSibling;

        if (node.innerHTML?.startsWith(":::") && node.innerHTML !== ":::") {
            let type = node.innerHTML.substr(3);
            let container = document.createElement("div");

            container.className = type;

            node.parentNode.insertBefore(container, node);
            node.parentNode.removeChild(node);

            node = next;

            while (node && node.innerHTML !== ":::") {
                next = node.nextSibling;

                container.appendChild(node);

                node = next;
            }

            continue;
        }

        if (node.innerHTML === ":::") {
            next = node.nextSibling;

            node.parentNode.removeChild(node);

            node = next;

            continue;
        }

        node = next;
    }
});
</script>

<div bind:this={main}>
    <svelte:component this={content}/>
</div>

Example markdown file:

para 1

:::note

note content _markdown formatted_

:::

para 2

etc

The directives must be their own paragraphs, and the initial markdown rendering will have the directives as part of the content.