pngwn / MDsveX

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

Any change to default highlighter breaks SvelteKit #514

Open donmccurdy opened 1 year ago

donmccurdy commented 1 year ago

Beginning from the template here:

https://github.com/josh-collinsworth/sveltekit-blog-starter

I'm finding that any change to the default highlighter ...

svelte.config.js

...
    preprocess: [
        preprocess({
            scss: { prependData: `@use 'src/lib/assets/scss/vars';` }
        }),
        mdsvex({
            extensions: ['.md'],
            rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings]
            highlight: {
                highlighter: function (code, lang) {
                    return `<pre><code>${code}</code></pre>`;
                }
            }
        })
    ],

... will break the page entirely, with errors like these:

Originally I wanted to add highlight.js to my own app (not based on the blog starter above), and ran into these errors. But it's easier to reproduce using the recommended method to disable syntax highlighting, beginning from any SvelteKit+MDsveX template, and appears to be the same problem. Possibly the default syntax highlighter is escaping characters that these alternatives do not? Returning static text like "hello" from the function works fine.

Thanks!

donmccurdy commented 1 year ago

Digging into source code a bit...

https://github.com/pngwn/MDsveX/blob/095d10873c25c6650516a95d06e05be5494166a6/packages/mdsvex/src/transformers/index.ts#L545-L579

... I was able to work around the issue by doing:

import escape from 'escape-html';

// escape curlies, backtick, \t, \r, \n to avoid breaking output of {@html `here`} in .svelte
const escape_svelty = (str) => str
  .replace(
    /[{}`]/g,
    (c) => ({ '{': '&#123;', '}': '&#125;', '`': '&#96;' }[c])
  )
  .replace(/\\([trn])/g, '&#92;$1');

...

highlight: {
  highlighter: function (code, lang) {
    const html = escape_svelty(escape(code));
    return `<pre>{@html \`<code>${html}</code>\`}</pre>`;
  }
}

... probably with a bit more tweaking I can get this working for highlight.js too, but, am I going about this the wrong way? Or should MDsvex do this escaping automatically when a custom highlighter is provided? Would be happy to make a PR if that's the case.

donmccurdy commented 1 year ago

Further update — I have a workaround for highlight.js, which requires writing the {@html ...} tag, but does not require escaping.

import hljs from 'highlight.js';

...

mdsvex({
    extensions: ['.md'],
    highlight: {
        highlighter: function (code, lang) {
            const language = hljs.getLanguage(lang) ? lang : 'plaintext';
            const html = hljs.highlight(code, { language }).value;
            return `<pre class="language-${lang}">{@html \`<code class="language-${lang}">${html}</code>\`}</pre>`;
        }
    }
})