tscanlin / tocbot

Build a table of contents from headings in an HTML document.
MIT License
1.37k stars 114 forks source link

Next.js SSG + markdown-it #346

Closed realChakrawarti closed 1 month ago

realChakrawarti commented 1 month ago

I am using Next.js for SSG with markdown-it. I have markown files on disk locally from which I generate the HTML string.

I want the TOC HTML string at build time with my custom class name attached to the parent for styling it.

export default async function markdownToHtml(markdown: string) {
  const md = markdownit({
    html: true,
    linkify: true,
    typographer: true,
    highlight: function (str: string, lang: string) {
      if (lang && hljs.getLanguage(lang)) {
        try {
          return hljs.highlight(str, { language: lang }).value;
        } catch (__) {}
      }

      return ''; // use external default escaping
    },
  });
  md.use(anchor, {
    tabIndex: false,
    slugify: (s: string) => slugify(s),
    permalink: anchor.permalink.headerLink(),
  });
  md.linkify.set({ fuzzyEmail: false });
  const result = md.render(markdown);
  return result;
}

The idea is to pass the result to a function for example tocify(result) => HTML TOC content

Is this something possible, if so then how? Thanks.

tscanlin commented 1 month ago

Hey, thanks for opening this issue! Currently this may be possible using jsdom on the server, but isn't natively supported since tocbot uses the browsers DOM methods and those are not typically available server side. However, this is something I'd be open to supporting better, if you have ideas for how this could be improved I'd love to hear them. Can you tell me any more about your use case? Thanks!

realChakrawarti commented 1 month ago

My use-case is quite generic. I am using Next.js to statically build a blog/portfolio site and I serve this on GitHub Pages.

All the operations, from building page slugs to parsing markdown to HTML happens at the build time and I want to keep it that way for peace of mind 😄 and this approach ensures a faster loading experience for visitors as all content is pre-rendered and avoids any client-side processing for TOC generation.

For example, I have this blog https://realchakrawarti.github.io/aham/blog/microdata-seo where content is parsed by markdown-it, and using one of its plugin I add the heading IDs for quick links.

I want to generate the TOC at build time against the headings without adding a placeholder text to inject the TOC generated at runtime on like [[toc]] to the original markdown file.

The way I am thinking is, the user will pass the HTML string, for example in the above code snippet result to a function tocify(result) which will output the TOC parsed from the result HTML string as unordered/ordered list (configurable) as HTML string.

This eliminates the need for placeholders in the markdown and allows for TOC insertion anywhere.

My requirement is to add the TOC HTML inside an <aside /> tag using innerHTML (plainJs) or dangerouslySetInnerHTML (React like library, virtual dom) and position it accordingly.

tscanlin commented 1 month ago

Thanks for the explanation! Is this a feature you would be interested in contributing to tocbot? I'd definitely appreciate a PR for this! Let me know what you think.

realChakrawarti commented 1 month ago

Coming weekend, I would go over code once and see how I could go about making changes. If this ends up with quite a lot of changes and across files, would create a separate package altogether.

Keeping this issue open for now. Will close it with an update in a week or two. Thanks :D

tscanlin commented 1 month ago

Here is a quick utility I put together that handles SSR of the toc content that you could use at build time. https://github.com/tscanlin/tocbot/pull/347 Let me know if that works for you.

realChakrawarti commented 1 month ago

Hi @tscanlin, I can see that https://github.com/tscanlin/tocbot/pull/347 has been merged to the main branch but I didn't find any note on how serverRender API is exposed? While testing on the PR, I imported the function directly from node_modules after linking it. I thought serverRender to be a named export like this, import {serverRender} from 'tocbot' but this isn't the case.

Please do let me know if I am missing something. Thanks!

tscanlin commented 1 month ago

Hey, I was planning to keep this separate from the main bundle since not all users may use it. But you should be able to import it with: import { serverRender } from 'tocbot/src/js/server-render.js' Let me know if that doesn't work for you.

realChakrawarti commented 1 month ago

Hi, this isn't working, tried with omitting the extension as well. module not found. image image

tscanlin commented 1 month ago

Hey, I forgot to add this into the exports, but I just pushed a fix to add the src/js folder. If you install the latest version (tocbot@4.28.2) it should work. Let me know if you still run into issues though.

realChakrawarti commented 1 month ago

Hi, I was able import it and get this running but has 2 minor issues, non-blocking

  1. I am getting this error: ⨯ Error: Cannot find module 'jsdom' at build time. To get around this, had to install jsdom manually on my project though, then it worked fine.
  2. Type declaration, just ignoring it using //@ts-ignore for now image

Just letting you know. Thanks for supporting server generation of toc 👍🏼

tscanlin commented 1 month ago

Hey, sorry for the delay in responding. Yeah, for now I'd say just install jsdom manually since I don't want to make it a hard requirement for everyone to have to download. As for the type declaration, yeah it isn't currently included since these are all JS files. Feel free to open a PR if you have an idea for how to address that, otherwise for now I'd say its best just to add a declaration for this util locally.

realChakrawarti commented 1 month ago

Again, thanks for supporting this. Really appreciate it. Feel free to close the issue 😊

tscanlin commented 1 month ago

Thank you for working with me on this!