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

SyntaxHighlighter: Expose registerLanguage #23166

Merged
merged 14 commits into from
Jul 5, 2023
Merged

SyntaxHighlighter: Expose registerLanguage #23166

merged 14 commits into from
Jul 5, 2023

Conversation

ndelangen
Copy link
Member

@ndelangen ndelangen commented Jun 21, 2023

Closes #21341

What I did

I exposed a method for registering new languages for our syntax highlighter.
I'm unsure if registering new languages after storybook rendered will actually work, but maybe we can inject early enough, or maybe it does just work.

How to test

We'd need to figure out a place where this can be added.

The SyntaxHighlighter can be rendered in docs, where it's most likely this feature-request is wanted.
So likely we'll need to experiment with adding something to .storybook/preview.ts.
Perhaps this would work:

import { SyntaxHighlighter } from '@storybook/components';
import less from 'react-syntax-highlighter/dist/esm/languages/prism/less';

SyntaxHighlighter.registerLanguage('less', less)

Checklist

If verified it actually work, or tweaked to make it work, we should ensure this behavior is tested, possibly in a story.

Maintainers

  • If this PR should be tested against many or all sandboxes,
    make sure to add the ci:merged or ci:daily GH label to it.
  • Make sure this PR contains one of the labels below.

["cleanup", "BREAKING CHANGE", "feature request", "bug", "documentation", "maintenance", "dependencies", "other"]

@ndelangen ndelangen self-assigned this Jun 21, 2023
@ndelangen
Copy link
Member Author

@hobbes7878 @timbomckay @marcosmko @jnschrag

You all indicated a desire to be able to inject additional languages into storybook's SyntaxHighlighter.
I'm not sure if this method would actually work, would you be interested in helping me test this?

@ndelangen ndelangen added bug components patch:yes Bugfix & documentation PR that need to be picked to main branch labels Jun 21, 2023
@jnschrag
Copy link
Contributor

I tried to give this a test following the Contributing guidelines to run a sandbox template on this branch and am getting this error:

image

SyntaxHighlighter.registerLanguage is not a function.

This is sandbox/react-vite-default-ts/.storybook/preview.ts.

import type { Preview } from '@storybook/react';

import { SyntaxHighlighter } from '@storybook/components';
import scss from 'react-syntax-highlighter/dist/esm/languages/prism/scss';

SyntaxHighlighter.registerLanguage('scss', scss)

const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
};

export default preview;

And I added this to sandbox/react-vite-default-ts/src/stories/Introduction.mdx

```scss
.test {
  &__nested {
    background: $color-red-100;
  }
}

I'm sure I'm likely doing something incorrectly in setting this up, so will be interested to see if others are able to test successfully. In theory, this seems like it should work.

@ndelangen
Copy link
Member Author

@jnschrag I appreciate you testing this out.. I found that due to us lazy-loading the syntax-highlighter (due to it's size) I wasn't passing the API though deep enough.

I made another attempt. Would you care to give this one a try?

@jnschrag
Copy link
Contributor

I made another attempt. Would you care to give this one a try?

It's no longer giving me any errors, but it doesn't seem to actually be applying the formatting either. I added a svelte and js example as well.

image

preview.ts

import { SyntaxHighlighter } from '@storybook/components';
import scss from 'react-syntax-highlighter/dist/esm/languages/prism/scss';
import svelte from './svelte.js'

SyntaxHighlighter.registerLanguage('scss', scss)
SyntaxHighlighter.registerLanguage('svelte', svelte)

Let me know if I can provide any other information to help debug this.

@hobbes7878
Copy link
Contributor

hobbes7878 commented Jun 25, 2023

Hi. Thanks for getting this started @ndelangen. I had the same result as @jnschrag, but I've dug around a bit and think I may have a clue.

For MDX docs, the format prop on the SyntaxHighlighter component always seems to come through as false, which means that this:

{props.format !== false ? (
<LazySyntaxHighlighterWithFormatter {...props} />
) : (
<LazySyntaxHighlighter {...props} />
)}

... always returns LazySyntaxHighlighter (which makes some sense b/c you might expect I don't want the prettier formatter overwriting how I'm intentionally styling my codeblock in MDX).

Trouble is, only LazySyntaxHighlighterWithFormatter is registering new languages with the lazy-loaded SyntaxHighlighter:

const LazySyntaxHighlighter = lazy(() => import('./syntaxhighlighter'));
const LazySyntaxHighlighterWithFormatter = lazy(async () => {
const [{ SyntaxHighlighter }, { formatter }] = await Promise.all([
import('./syntaxhighlighter'),
import('./formatter'),
]);
if (languages.length > 0) {
languages.forEach((args) => {
SyntaxHighlighter.registerLanguage(...args);
});
languages = [];
}
if (Comp === null) {
Comp = SyntaxHighlighter;
}
return {
default: (props: ComponentProps<typeof LazySyntaxHighlighter>) => (
<SyntaxHighlighter {...props} formatter={formatter} />
),
};
});

If, for example, I reverse the condition based on props.format and return LazySyntaxHighlighterWithFormatter, my custom registered language highlights correctly:

image

Upshot: I think if we include the same language registering logic in both the formatted and non-formatted lazy-loaded SyntaxHighlighter component, this'll work.

UPDATE: Added a PR to this branch that takes the shortest route to duplicate the logic for LazySyntaxHighlighter. Hopefully, I got the typings close enough, but happy to dive back in if other thoughts.

@ndelangen
Copy link
Member Author

@hobbes7878 I've invite you to the github org, if you have interest, could you make your changed this this PR?

@hobbes7878
Copy link
Contributor

hobbes7878 commented Jun 27, 2023

Done and verified working in MDX docs.

I do see tests are failing on a missing type for the new method:

Error: src/components/Source.tsx(8,7): error TS2741: Property 'registerLanguage' is missing in type 'StyledComponent<SyntaxHighlighterBaseProps & SyntaxHighlighterCustomProps & { theme?: Theme; }, {}, {}>' but required in type '{ (props: SyntaxHighlighterBaseProps & SyntaxHighlighterCustomProps): Element; registerLanguage(name: string, func: any): void; }'.

@hobbes7878
Copy link
Contributor

Messed with the type error above, which comes from:

const StyledSyntaxHighlighter: typeof SyntaxHighlighter = styled(SyntaxHighlighter)(
({ theme }) => ({
// DocBlocks-specific styling and overrides
fontSize: `${theme.typography.size.s2 - 1}px`,
lineHeight: '19px',
margin: '25px 0 40px',
borderRadius: theme.appBorderRadius,
boxShadow:
theme.base === 'light'
? 'rgba(0, 0, 0, 0.10) 0 1px 3px 0'
: 'rgba(0, 0, 0, 0.20) 0 2px 5px 0',
'pre.prismjs': {
padding: 20,
background: 'inherit',
},
})
);

If I retype and cast StyledSyntaxHighlighter using the types for SyntaxHighlighterProps like...

import type { SyntaxHighlighterProps } from '@storybook/components';

const StyledSyntaxHighlighter: React.FunctionComponent<SyntaxHighlighterProps> = styled(SyntaxHighlighter)//...

... I seem to pass type check.

Happy to push that change @ndelangen, but there may be a better way...

@ndelangen ndelangen marked this pull request as ready for review June 30, 2023 10:18
@ndelangen ndelangen requested a review from shilman June 30, 2023 10:18
@ndelangen
Copy link
Member Author

@hobbes7878 I think it'd be awesome if we could add a story that demonstrated adding a custom syntax being injected.

Having a story demonstrates the behavior as well as serve as a test, because chromatic will apply visual regression and we'll be alerted if we'd ever regress.

Would you be able to add such story?

I personally don't really have a preference for how to do the types.. perhaps @kasperpeulen has?
I do know there's a hard requirement that the lazy loading should continue to work, of course.
Because the component is a heavy-weight, and should only be loaded when used. 👍

@kasperpeulen please have a look at the changes in this PR and check if you would want any changes to the way types get defined etc. 🙏

@hobbes7878
Copy link
Contributor

Apologies, @ndelangen, was out of town a few days. I'm happy to take a swing at documenting the change with a new story. Always figuring out how to work with the monorepo takes the longest, so if you can point me to a similar story that makes changes to storybook's overall config, i.e., preview.ts, that'd be super helpful. But I'll try to figure it out, regardless.

@ndelangen
Copy link
Member Author

ndelangen commented Jul 5, 2023

@hobbes7878 thank you so much for all the effort!
No need to apologise at all !

Here's a place in the code where we might add such story:

export const UnsupportedDark = {
args: {
language: 'C#',
children: `// A Hello World! program in C#.
using System;
namespace HelloWorld
{
class Hello
{
static void Main()
{
Console.WriteLine("Hello World!");
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}`,
},
render: (args: ComponentProps<typeof SyntaxHighlighter>) => (
<ThemeProvider theme={ensure(themes.dark)}>
<SyntaxHighlighter {...args} />
</ThemeProvider>
),
};

My idea would be to a a new story below and similarly using a custom render function.
We'd invoke the registerLanguage API there, and then return the SyntaxHighlighter being rendered with a previously unsupported language.

Alternatively, we could also use a custom loader function? 🤔
https://storybook.js.org/docs/react/writing-stories/loaders#page-top

Whichever you prefer.

Comment on lines 8 to 9
// Register custom language
SyntaxHighlighter.registerLanguage('scss', scss);
Copy link
Member Author

@ndelangen ndelangen Jul 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be done, as the story renders, a custom render function would allow us to do that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, gotcha. Let me try that. 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realize I'm reading too quickly and did this in a custom loader instead of render func in a9d0d22. The loader works, but let me know if you'd prefer I go the render route. Happy to rewrite.

@ndelangen
Copy link
Member Author

Sadly I can't approve the PR.. because.. I made it 😄

This LGTM, let's wait for the CI to approve as well! 👏

@ndelangen ndelangen added feature request and removed patch:yes Bugfix & documentation PR that need to be picked to main branch bug labels Jul 5, 2023
@ndelangen ndelangen merged commit 1bcbe36 into next Jul 5, 2023
@ndelangen ndelangen deleted the norbert/fix-21341 branch July 5, 2023 16:15
@github-actions github-actions bot mentioned this pull request Jul 5, 2023
11 tasks
@jnschrag
Copy link
Contributor

jnschrag commented Jul 5, 2023

Thank you all so much for your work on this! It's going to be so helpful for my team.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: Can't add new languages to syntax highlighter
3 participants