Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeError: attacher.call is not a function when using remark-shiki-twoslash with unified #147

Open
mattcroat opened this issue Mar 19, 2022 · 5 comments
Labels
Bug Something isn't working You can do this The idea is well specified and good for a PR

Comments

@mattcroat
Copy link

Hey! 👋

I'm using remark-shiki-twoslash with unified and I get this error TypeError: attacher.call is not a function. I already opened a discussion at unifiedjs/unified#187 but I was told to raise the issue here.

Here is the reproduction on StackBlitz. I have a simple Express server without TypeScript and I use "type": "module" in package.json.

// index.js

import { unified } from 'unified'
import shikiTwoslash from 'remark-shiki-twoslash'
import parseMarkdown from 'remark-parse'
import serializeMarkdown from 'remark-stringify'
import markdownToHtml from 'remark-rehype'
import markdownToHtmlAgain from 'rehype-raw'
import serializeHtml from 'rehype-stringify'

const result = await unified()
  .use(parseMarkdown)
  .use(serializeMarkdown)
  .use([
    [shikiTwoslash, { theme: 'dark-plus' }]
    // ...
  ])
  .use(markdownToHtml, { allowDangerousHtml: true })
  .use(markdownToHtmlAgain)
  .use(serializeHtml)
  .process('```js \n console.log("Hello, World!") \n ```')
> server@ start /server
> node ./src/index.js

file:///server/node_modules/unified/lib/index.js:136
      const transformer = attacher.call(processor, ...options)
                                   ^

TypeError: attacher.call is not a function
    at Function.freeze (file:///server/node_modules/unified/lib/index.js:136:36)
    at Function.process (file:///server/node_modules/unified/lib/index.js:375:15)
    at output (file:///server/src/index.js:25:4)
    at file:///server/src/index.js:35:20
    at ModuleJob.run (node:internal/modules/esm/module_job:197:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:337:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:61:12)
@orta
Copy link
Contributor

orta commented Mar 22, 2022

Interesting, perhaps there's different constraints when running inside a module - I don't have any code which runs in ESM node, so I've not hit it. Open to PRs though 👍🏻

@orta orta added Bug Something isn't working You can do this The idea is well specified and good for a PR labels Mar 22, 2022
@chopfitzroy
Copy link

Heya @mattcroat

Can you try using:

.use([
    [shikiTwoslash.default, { theme: 'dark-plus' }]
    // ...
  ])

Have updated your example as well: https://stackblitz.com/edit/node-shiki-twoslash-tcez4v and it appears to be working as expected.

I ran into a similar issue using remark-shiki-twoslash with Astro.

@enpitsuLin
Copy link

enpitsuLin commented Dec 6, 2022

@chopfitzroy ’s advice is useful
Snipaste_2022-12-06_18-17-05

I Think remark-shiki-twoslash maybe should be a pure ESM library

@arcanis
Copy link
Contributor

arcanis commented Feb 17, 2023

It occurs because you list the esm entrypoint through the module key, which Node doesn't actually use when resolving import calls (it only reads the exports field). As a result, the cjs version of the library is loaded and, while Node tries to shim it to look like an ESM module, it doesn't do it perfectly and messes up the translation, causing the default export to be unnecessarily wrapped.

To fix it, you have to list in the package.json something akin to:

{
  "main": "./dist/index.js",
  "module": "./dist/remark-shiki-twoslash.esm.mjs",
  "typings": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/remark-shiki-twoslash.esm.mjs",
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    }
  }
}

Note that the esm file also needs to have its extension be .mjs, not .js - otherwise Node will complain and the import will fail. That's why I didn't submit a PR - this stuff seems to be handled by TSDX, and I don't know if it supports configuring that.

@Levent0z
Copy link

When I use the .default workaround, nextjs site builds, but all triple-tick blocks are removed from the resulting HTML and appear to be replaced with <!-- -->. This is my next.config.mjs:

const nextConfig = {
    reactStrictMode: true,
    webpack: (config, options) => {
        config.module.rules.push({
            test: /\.mdx?$/,
            use: [
                options.defaultLoaders.babel,
                {
                    loader: '@mdx-js/loader',
                    options: {
                        providerImportSource: '@mdx-js/react',
                        remarkPlugins: [
                            remarkFrontmatter,
                            [remarkShikiTwoslash.default, {
                                themes: ['dark-plus', 'light-plus'],
                                alwayRaiseForTwoslashExceptions: true,
                                // https://github.com/shikijs/twoslash/issues/131
                                disableImplicitReactImport: true,
                            }],
                            remarkGfm,
                            remarkMath,
                        ],
                        rehypePlugins: [
                            [rehypeKatex, { throwOnError: true, strict: true, output: 'mathml' }],
                            rehypeSlug,
                            [rehypeAutolinkHeadings, { behavior: 'wrap' }],
                            // [withShiki, { highlighter }],
                            [template, { template: wrapTemplate }]
                        ],
                    },
                },
            ],
        });
        // Fixes npm packages (mdx) that depend on `fs` module
        if (!options.isServer) {
            config.resolve.fallback.fs = false
        }
        return config;
    },
    pageExtensions: ['js', 'jsx', 'tsx', 'md', 'mdx'],
    images: {
        loader: 'imgix',
        path: 'https://images.unsplash.com/',
    },
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something isn't working You can do this The idea is well specified and good for a PR
Projects
None yet
Development

No branches or pull requests

6 participants