Skip to content

refactor(theme-common, theme-classic, theme-live-codeblock): Parse metastring into metaOptions and use it across components #11019

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

Closed
wants to merge 7 commits into from

Conversation

Danielku15
Copy link
Contributor

@Danielku15 Danielku15 commented Mar 22, 2025

Pre-flight checklist

  • I have read the Contributing Guidelines on pull requests.
  • If this is a code change: I have written unit tests and/or added dogfooding pages to fully verify the new behavior.
  • If this is a new API or substantial change: the PR has an accompanying issue (closes #0000) and the maintainers have approved on my working plan.

Motivation

This PR is part of a series related to #11008 and it superceeds #11011 in the goal to splitup the work into more isolated refactoring/feature parts.

Note that this PR also includes the changes of previous steps and therefore the overall diff can be misleading. The best approach will be to review and merge every step individually and then rebase accordingly.

Intermediately the specific changes can be checked here: Danielku15/docusaurus@feature/codeblock-meta...Danielku15:docusaurus:feature/codeblock-meta-2

Step 1 (#11017): Splitup current parseLines logic (separation of concerns)
Step 2 (this PR): Parse metastring into metaOptions and use it across components.
Step 3 (#11021): Add parsing of any options specified in the metastring.
Step 4 (#11022): Add CodeBlockToken component to customize DOM of highlighted tokens.
Step 5 (#11023): Pass metaOptions to whole codeblock component tree.

The motivation of this part is to centralize the metastring parsing across components to a central location without changing the parsing logic itself.

To achieve the central parsing a new metaOptions is introduced and the old inline logic is adapted everywhere.

Due to the way the live code editor works the metaOptions is introduced as component prop. This way we can parse it once and pass it through. The possible overlap between prop<->metaOptions is the same like prop<->metastring before.

We could split this PR further but I think it makes sense to update all components to use metaOptions in one go. The refactoring per component of clearly visible in individual commits.

Test Plan

Automatic testing relies on existing integration tests.

Manual testing by checking /docs/markdown-features/code-blocks showed no impact of the change.

Test links

Deploy preview: https://deploy-preview-11019--docusaurus-2.netlify.app
Code Blocks: https://deploy-preview-11019--docusaurus-2.netlify.app/docs/markdown-features/code-blocks/

Related issues/PRs

Copy link

netlify bot commented Mar 22, 2025

[V2]

Built without sensitive environment variables

Name Link
🔨 Latest commit 7136e71
🔍 Latest deploy log https://app.netlify.com/sites/docusaurus-2/deploys/67e7ef6e3ad50a0008056e9a
😎 Deploy Preview https://deploy-preview-11019--docusaurus-2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

github-actions bot commented Mar 22, 2025

⚡️ Lighthouse report for the deploy preview of this PR

URL Performance Accessibility Best Practices SEO Report
/ 🔴 41 🟢 98 🟢 100 🟢 100 Report
/docs/installation 🔴 49 🟢 97 🟢 100 🟢 100 Report
/docs/category/getting-started 🟠 71 🟢 100 🟢 100 🟠 86 Report
/blog 🟠 63 🟢 96 🟢 100 🟠 86 Report
/blog/preparing-your-site-for-docusaurus-v3 🟠 62 🟢 92 🟢 100 🟢 100 Report
/blog/tags/release 🟠 63 🟢 96 🟢 100 🟠 86 Report
/blog/tags 🟠 73 🟢 100 🟢 100 🟠 86 Report

Comment on lines 239 to 250
/**
* The supported types for {@link CodeBlockMetaOptions} values.
*/
export type CodeMetaOptionValue = string | number | boolean | undefined;

/**
* A property bag for custom options specified by the user via metastring
* to control aspects like title and line numbers.
*/
export type CodeBlockMetaOptions = {
[key: string]: CodeMetaOptionValue;
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not typesafe, we'd rather use an interface as explained here #11011 (comment)

import Playground from '@theme/Playground';
import ReactLiveScope from '@theme/ReactLiveScope';
import CodeBlock, {type Props} from '@theme-init/CodeBlock';

const withLiveEditor = (Component: typeof CodeBlock) => {
function WrappedComponent(props: Props) {
if (props.live) {
return <Playground scope={ReactLiveScope} {...props} />;
const metaOptions = parseCodeBlockMetaOptions(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't this going to lead to parsing 3 times the metastring?

We don't want to parse it for:

  • the live code block
  • the code block
  • the playground

We only want to parse options once

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I tried to ensure we parse it only once. Its a bit explained in the PR motivation:

Due to the way the live code editor works the metaOptions is introduced as component prop. This way we can parse it once and pass it through. The possible overlap between prop<->metaOptions is the same like prop<->metastring before.

I added a new metaOptions prop for this purpose to pass it from parent to child when possible. The forwarding is happening here in line 26 and 32.

parseCodeBlockMetaOptions prioritizes the bag from the props if available.

import Playground from '@theme/Playground';
import ReactLiveScope from '@theme/ReactLiveScope';
import CodeBlock, {type Props} from '@theme-init/CodeBlock';

const withLiveEditor = (Component: typeof CodeBlock) => {
function WrappedComponent(props: Props) {
if (props.live) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd like to keep it for retrocompatibility

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The prop is still there (see line 21)


// Retrocompatible support for live codeblock metastring
// Not really the appropriate place to handle that :s
node.data.hProperties.live = node.meta?.split(' ').includes('live');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Historically, with MDX 1 (Docusaurus v2), the live code block component would receive a "live" prop.

Our users have swizzled the live code block component, so if you remove this line, their component won't receive the live=true prop anymore and it might break their site. I'd prefer to keep it, and eventually only remove this for Docusaurus v4.

Copy link
Contributor Author

@Danielku15 Danielku15 Mar 28, 2025

Choose a reason for hiding this comment

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

Thanks for the further insight. This area was a bit sensitive for me as I wasn't sure about all the affected parts (also considering the note above in line 17).

The normal @theme/CodeBlock doesn't have a live property, it only comes via docusaurus-theme-live-codeblock (as @theme-init/CodeBlock) And I thought there users would rather swizzle the Playground component.

I didn't try all scenarios, but normally swizzling a CodeBlock gives you props without live. But it might be risky that people might have relied on a hidden live property in older code and declared a live property themselves.

Unfortunately this is the docusaurus-mdx-loader package which has no access to the codeBlockUtils. So we would need to keep this code as it is. I don't think we can rely on the metaOptions easily.

@Danielku15 Danielku15 force-pushed the feature/codeblock-meta-2 branch 2 times, most recently from 0b4addf to f8b3736 Compare March 28, 2025 17:03
// we keep the old custom logic which was moved from their old spots to here.

// normal codeblock
const title = metastring?.match(codeBlockTitleRegex)?.groups!.title;

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data High

This
regular expression
that depends on
library input
may run slow on strings starting with 'title="' and with many repetitions of 'title="a'.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pre-existing code was just moved. Regex pattern and input values are unchanged.

@Danielku15 Danielku15 force-pushed the feature/codeblock-meta-2 branch from f8b3736 to dbb2dcc Compare March 28, 2025 17:18
@Danielku15 Danielku15 force-pushed the feature/codeblock-meta-2 branch from dbb2dcc to 7136e71 Compare March 29, 2025 13:02
@slorber
Copy link
Collaborator

slorber commented Apr 4, 2025

Added, see #11017 (comment)

@slorber slorber closed this Apr 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Signed Facebook CLA
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants