Docusaurus: Import files to `theme-live-codeblock` scope

Created on 24 May 2020  路  14Comments  路  Source: facebook/docusaurus

馃悰 Bug Report

Import statements in theme-live-codeblock not working. I read the react-live docs and it does look like they do support importing modules - does docusaurus expose the needed scope?

Have you read the Contributing Guidelines on issues?

Yes

To Reproduce

  1. Configure theme-live-codeblock according to these instructions
  2. Install @material-ui/core
  3. Create file.mdx attempt to import components from @material-ui/core
  4. Attempted to import within .mdx file as well as the ``` jsx live block

Expected behavior

Render the imported component

Actual Behavior

  • When the import statement is at the top of .mdx (keep in mind I can use the imported component within the .mdx file just fine, just can't use it within the ```jsx live block..):

image

  • When the import statement is inside the ```jsx live block:

image

Your Environment

  • Docusaurus version used: 2.0.0-alpha.55
  • Environment name and version (e.g. Chrome 78.0.3904.108, Node.js 10.17.0): Node: v13.12.0, Firefox: 76.0.1
  • Operating system and version (desktop or mobile): Catalina 10.15.4
// from package.json
"dependencies": {
  "@docusaurus/core": "^2.0.0-alpha.55",
  "@docusaurus/preset-classic": "^2.0.0-alpha.55",
  "@docusaurus/theme-live-codeblock": "^2.0.0-alpha.39",
  "@material-ui/core": "^4.10.0",
  "@material-ui/icons": "^4.9.1",
  "classnames": "^2.2.6",
},

Reproducible Demo

bug

All 14 comments

After browsing through the repo, it doesn't look like docusaurus lets you configure react-live scope..

As a workaround, this is what I did:


Slightly modified Playground

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React from 'react';

import {
  LiveProvider,
  LiveEditor,
  LiveError,
  LivePreview
} from 'react-live'

import classnames from 'classnames';
import usePrismTheme from './usePrismTheme.js';

import * as styles from './styles.module.css';

function Playground({children, /* theme, */ transformCode, ...props}) {
  const theme = usePrismTheme();

  return (
    <LiveProvider
      code={children.replace(/\n$/, '')}
      transformCode={transformCode || ((code) => `${code};`)}
      theme={theme}
      {...props}>
      <div
        className={classnames(
          styles.playgroundHeader,
          styles.playgroundEditorHeader,
        )}>
        Live Editor
      </div>
      <LiveEditor />
      <div
        className={classnames(
          styles.playgroundHeader,
          styles.playgroundPreviewHeader,
        )}>
        Result
      </div>
      <div className={styles.playgroundPreview}>
        <LivePreview />
        <LiveError />
      </div>
    </LiveProvider>
  );
}

export default Playground;


NOT modified usePrismTheme

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import defaultTheme from 'prism-react-renderer/themes/palenight';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useThemeContext from '@theme/hooks/useThemeContext';

const usePrismTheme = () => {
  const {
    siteConfig: {
      themeConfig: {prism = {}},
    },
  } = useDocusaurusContext();
  const {isDarkTheme} = useThemeContext();
  const lightModeTheme = prism.theme || defaultTheme;
  const darkModeTheme = prism.darkTheme || lightModeTheme;
  const prismTheme = isDarkTheme ? darkModeTheme : lightModeTheme;

  return prismTheme;
};

export default usePrismTheme;


NOT modified styles.module.css

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

 .playgroundHeader {
  letter-spacing: 0.08rem;
  padding: 0.75rem;
  text-transform: uppercase;
  font-weight: bold;
}

.playgroundEditorHeader {
  background: var(--ifm-color-emphasis-600);
  color: var(--ifm-color-content-inverse);
}

.playgroundPreviewHeader {
  background: var(--ifm-color-emphasis-200);
  color: var(--ifm-color-content);
}

.playgroundPreview {
  border: 1px solid var(--ifm-color-emphasis-200);
  border-bottom-left-radius: var(--ifm-global-radius);
  border-bottom-right-radius: var(--ifm-global-radius);
  position: relative;
  padding: 1rem;
}

From there, I import the Playground into an .mdx file and it worked! I did try to use react-live components on their own, which worked, but styling was not right. That is why I copied those components - to keep styling.

MyFile.mdx:

---
id: some_id
title: My Live Code Demo 
---

import LiveCodeBlock from '../Playground';
import { Button } from '@material-ui/core';

### Custom components

<LiveCodeBlock scope={{ Button }}>
{`
function MyButton() {
  return <Button color="secondary" onClick={() => alert("Hello, world!")}>Click me</Button>
}
`}
</LiveCodeBlock>

Which works perfectly!

image

Final Solution

Ultimately, with my solution above, there were still some minor differences in styling.. So I started digging into the codebase and it looks like my solution very similar (in theory, at least lol) to swizzling.

image

Despite the warning, swizzling MDXComponents was the easiest way to accomplish this:

yarn swizzle @docusaurus/theme-classic MDXComponents


This creates a file at src/theme/MDXComponents/index.js - (click on me to see edited file)

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React from 'react';
import Link from '@docusaurus/Link';
import CodeBlock from '@theme/CodeBlock';
import Heading from '@theme/Heading';

import styles from './styles.module.css';

//-------------------------------------------------------/
////// Import the component you need in scope
import Button from "@material-ui/core/Button";

//-------------------------------------------------------/
////// Object with components I need in scope
const SCOPE = {
  Button,
}

export default {
  code: (props) => {
    const {children} = props;
    if (typeof children === 'string') {

      //-------------------------------------------------------/
      return <CodeBlock {...props} scope={SCOPE} />;
      /** 
       * REPLACED THE LINE BELOW WITH THE LINE ABOVE 
       */
      //// return <CodeBlock {...props} />;
      //-------------------------------------------------------/

    }
    return children;
  },
  a: (props) => {
    if (/\.[^./]+$/.test(props.href)) {
      // eslint-disable-next-line jsx-a11y/anchor-has-content
      return <a {...props} />;
    }
    return <Link {...props} />;
  },
  pre: (props) => <div className={styles.mdxCodeBlock} {...props} />,
  h1: Heading('h1'),
  h2: Heading('h2'),
  h3: Heading('h3'),
  h4: Heading('h4'),
  h5: Heading('h5'),
  h6: Heading('h6'),
};

Then you can just use it in your markdown:

``` jsx live
function ButtonTest() {
    return (
        <Button onClick={() => alert('omg it worked')} color="secondary">Click Me</Button>
    )
}
```

This worked perfectly for me - no need for a custom component now.

Hi,

I'm going to reopen because this is something I noticed inspecting the code 3 days ago, and wanted to fix.

We should be able to import components in MDX, and they should become automatically available in the playgrounds of the mdx page. Afaik my discussions with Chris Biscardy there's a useComponents() in MDX that can be called to get the comps to pass to react-live scope.

I'm not too familiar with the libraries/technology used in this repo (lerna, mdx, react-live..) Hell, I just started with docusaurus like 3 days ago...but if there's anything you need help with, feel free to reach out.

Hey @slorber - just wanted to let you know that it doesn't look like that useMDXComponents hook returns imported components. It looks like it returns the data already in /MDXComponents/index.js - it doesn't return imported components. I'm not sure if there's a way to have MDX tell us what has been imported or not (so that we can add it to scope).. FYI.

Unfortunately you are right @oze4

I found back my discussion with Chris Biscardi here: https://twitter.com/sebastienlorber/status/1252899627126358016

There is a useMDXScope hook, but it's not from MDX, it's from MDX, it's from the Gatsby MDX plugin.

I've looked at its code and it's probably not so easy to port it fast to Docusaurus, as it uses a babel plugin to extract imports from the MDX generated JSX, write them to disk in a dedicated file...

https://github.com/gatsbyjs/gatsby/blob/cd150b5e5264a5a75f2abc27e3430c55bfcd4e41/packages/gatsby-plugin-mdx/gatsby/on-create-node.js

Will probably come back to this later when we have shipped more urgent features.


In the meantime, I think we still can make something about it, like allowing to more easily provide components to the react-live scope with swizzle. The scope may not be per-MDX file, but having a global scope is probably good enough for most usecases.

Also, afaik we don't have yet a "wrapRootElement" API but I've seen this is something we want. This would enable to use the MDXProvider

@oze4 I've made a PR to support more officially a workflow to provide some components to the react-live scope. It's not as good as the gatsby integration as I hoped (as you have to configure available scope globally) but I think it's good enough.

Can you tell me if this solves your usecase?

I think it would permit you to swizzle a "smaller" part of the live editor plugin

https://github.com/facebook/docusaurus/pull/2826

Hey @slorber - I did notice that Gatsby uses a custom hook, useMDXScope from the gatsby-plugin-mdx which I did test out as well. Unless I'm using it incorrectly, the useMDXScope hook also did not update with imported components - it returned the same data as useMDXComponents. (After all it looks like useMDXScope is a wrapper for useMDXComponents?)

When I imported a component in an .mdx file, the useMDXScope hook did not pick up on that. Again, I could be using it incorrectly, but that is also something I looked into. While it looks like they have the 'right idea', I'm not sure it's working as intended.

Thanks for everything!

Edit: FYI - this was one of the articles I found, which I used as part of my testing.

Yes it's normal as it's proprietary to gatsby, it assumes the gatsby-node.js code has run and written something to the FS, otherwise it just returns no additional context :)

Awesome. Thanks for your input. What's weird is that I was using it in Gatsby and it was still returning no additional context. I wanted to see how they handled this so I built out a little demo Gatsby site..

With that being said, it's probably something I was doing wrong.

Cheers!

Didn't test with Gatsby either but I guess it should work ^^ Does the doc of the PR I wrote seems good for your usecase?

Yes sir. Thank you!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sebqq picture sebqq  路  3Comments

endiliey picture endiliey  路  3Comments

cheercroaker picture cheercroaker  路  3Comments

JoelMarcey picture JoelMarcey  路  3Comments

ericnakagawa picture ericnakagawa  路  3Comments