fuma-nama / fumadocs

The beautiful docs framework with Next.js. Alternative to Nextra
https://fumadocs.vercel.app
MIT License
1.89k stars 115 forks source link

twoslash transformer leads to runtime error on the server #1095

Open tylersayshi opened 1 day ago

tylersayshi commented 1 day ago

To Reproduce

Issue Reproduction: https://github.com/tylersayshi/fumadocs-twoslash-bug

Current vs. Expected behavior

Bugs:

  1. // ^? and other twoslash comments are not being transformed on the docs pages after adding the plugin following the docs: https://fumadocs.vercel.app/docs/ui/twoslash
  2. Runtime error when attempting to use twoslash on the server in components/code-block.tsx for the home page

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.1.0: Thu Oct 10 21:02:26 PDT 2024; root:xnu-11215.41.3~2/RELEASE_ARM64_T8122
  Available memory (MB): 16384
  Available CPU cores: 8
Binaries:
  Node: 22.5.1
  npm: 10.8.2
  Yarn: N/A
  pnpm: 9.13.2
Relevant Packages:
  next: 15.0.3 // Latest available version is detected (15.0.3).
  eslint-config-next: N/A
  react: 18.3.1
  react-dom: 18.3.1
  typescript: N/A
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Content Sources (e.g. Fumadocs MDX, Content Collections), Integrations (e.g OpenAPI, Typescript DocsGen)

Additional context

the project i am working on this for is here: https://github.com/tylersayshi/fuma-move

This work is to move arktype.io to fumadocs: https://github.com/arktypeio/arktype/issues/1111

fuma-nama commented 1 day ago
  1. You need to enable it by adding twoslash meta string to codeblock. Like
```ts twoslash
console.log("I love Arktype") 

2. It requires a Popup component to display the generated popups, you can import them from the package:

```ts
import { Popup, PopupContent, PopupTrigger } from 'fumadocs-twoslash/ui';

And pass it to components option.

I would suggest to provide the details of runtime error too if you have other questions, it helps me to locate the problem faster.

fuma-nama commented 1 day ago

Sorry for late, I've just made the Twoslash section docs up-to-date.

Hope it clarifies the problems: https://fumadocs.vercel.app/docs/ui/twoslash

tylersayshi commented 17 hours ago

Thanks for the quick responses!

Is the code for customizing the config file referring to the next.config.js?

from the docs:

import { defineConfig } from 'fumadocs-mdx/config';
import { transformerTwoslash } from 'fumadocs-twoslash';
import { rehypeCodeDefaultOptions } from 'fumadocs-core/mdx-plugins';

export default defineConfig({
  mdxOptions: {
    rehypeCodeOptions: {
      transformers: [
        ...(rehypeCodeDefaultOptions.transformers ?? []),
        transformerTwoslash(),
      ],
    },
  },
});

I am probably just configuring this wrong somehow.

tylersayshi commented 16 hours ago

Oh it's the source.config.ts - sorry for my confusion there. #1 is now resolved for me and was not a bug at all. 🎉

re: the runtime error.

minimal reproduction: https://github.com/tylersayshi/fumadocs-twoslash-bug

image

link to related code block component

This error I find confusion because it mentions that the error happens on the server where path should definitely be defined.

I checked and this happens on node 20, 22, and 23

tylersayshi commented 15 hours ago

I get the same error when doing this with

import { transformerTwoslash } from "@shikijs/twoslash";

So maybe this is upstream somehow?

fuma-nama commented 10 hours ago

took some time for me, it's a bundler issue because typescript has dynamic require of modules including fs and path. You can add them to serverExternalPackages.

import * as Base from "fumadocs-ui/components/codeblock";
import { highlight } from "fumadocs-core/server";
import { transformerTwoslash } from "fumadocs-twoslash";
import { Popup, PopupContent, PopupTrigger } from "fumadocs-twoslash/ui";

export interface CodeBlockProps {
  code: string;
  wrapper?: Base.CodeBlockProps;
  lang: string;
}

export async function CodeBlock({
  code,
  lang,
  wrapper,
}: CodeBlockProps): Promise<React.ReactElement> {
  const rendered = await highlight(code, {
    lang,
    meta: {
      __raw: 'twoslash'
    },
    themes: {
      light: "github-light",
      dark: "vesper",
    },
    transformers: [transformerTwoslash()],
    components: {
      // @ts-expect-error -- JSX component
      pre: Base.Pre,
      Popup,
      PopupContent,
      PopupTrigger
    },
  });

  return <Base.CodeBlock {...wrapper}>{rendered}</Base.CodeBlock>;
}
import { createMDX } from "fumadocs-mdx/next";

const withMDX = createMDX();

/** @type {import('next').NextConfig} */
const config = {
  reactStrictMode: true,
    serverExternalPackages: ['twoslash', 'typescript'],
};

export default withMDX(config);

and install them to your project: pnpm i twoslash (you already have typescript installed)

tylersayshi commented 8 hours ago

wow! Thanks so much for the help! It all works super nicely now :)

tylersayshi commented 8 hours ago

Oh wait one last question, it looks like the twoslash snippets are parsing strangely. Is the meta.__raw: 'twoslash' supposed to fix this?

image
fuma-nama commented 7 hours ago

You probably forgot to add components option to JSX renderer or MDX component, the fumadocs-twoslash/ui exported all required components.

The purpose of meta is to trigger twoslash, without it twoslash won't be enabled

tylersayshi commented 6 hours ago

So the weird output above was because I was importing from @shikijs/twoslash for transformerTwoslash still by accident.

I am now seeing this error 😅

Error: Language `js` not found, you may need to load it first

this issue seems relevant but maybe different? https://github.com/vuejs/vitepress/issues/4334

the popups are added here btw: https://github.com/tylersayshi/fuma-move/blob/main/components/code-snippets/Code.tsx#L27-L32

Maybe I need to mark another package as external? https://github.com/tylersayshi/fuma-move/blob/main/next.config.js#L8

fuma-nama commented 5 hours ago

Yes it's relevant, codeblocks in popups may have a different language than the parent codeblock, but only the language of parent codeblock e.g. ts will be pre-loaded.

It's indeed a problem of Shiki, it doesn't support async markdown renderer so there's no official solution unless I make a PR to shiki.

for user-land workaround, you can pre-load all possible languages first (ideally bundledLanguages).

import {
  getSingletonHighlighter,
  bundledLanguages
} from 'shiki';

// before highlight call
await getSingletonHighlighter({
  langs: Object.keys(bundledLanguages)
})

Or you can just drop the highlight function and go with Shiki + your custom config, so you can have full control on this.