Skip to content

fix(theme): vendor theme-common/internal usages ahead of Docusaurus v4 (#1140)#1502

Open
sserrata wants to merge 2 commits into
mainfrom
fix/theme-common-internal-1140
Open

fix(theme): vendor theme-common/internal usages ahead of Docusaurus v4 (#1140)#1502
sserrata wants to merge 2 commits into
mainfrom
fix/theme-common-internal-1140

Conversation

@sserrata

@sserrata sserrata commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

Closes #1140. Migrates docusaurus-theme-openapi-docs off of @docusaurus/theme-common/internal ahead of Docusaurus v4, which will remove that entry point. The back-compat shim (facebook/docusaurus#11153) is no longer load-bearing for this package.

Approach

Vendored the leaf utilities into src/theme/utils/ (with MIT attribution to Meta — Docusaurus is MIT-licensed):

File Symbols
codeBlockUtils.ts parseCodeBlockTitle, parseLanguage, parseLines, containsLineNumbers, getPrismCssVariables
useCodeWordWrap.ts useCodeWordWrap
useMutationObserver.ts transitive dep of useCodeWordWrap
reactUtils.ts useEvent, useShallowMemoObject, ReactContextError
tabsUtils.tsx sanitizeTabsChildren, useTabsContextValue, useTabs, TabsProvider (+ inlined useQueryStringValue to avoid pulling historyUtils)
scrollUtils.tsx ScrollControllerProvider, useScrollPositionBlocker

Files live under src/theme/utils/ so they're consumed via the existing @theme/* alias (e.g. @theme/utils/codeBlockUtils) at both compile time (tsconfig path mapping) and runtime (Docusaurus webpack). Follows existing precedent in this codebase: @theme/translationIds, @theme/ApiItem/store, @theme/ApiItem/hooks.

Repointed 10 source files (4 CodeBlock + 6 Tabs variants) at the local copies.

Swizzled @theme/Tabs and @theme/TabItem. Reason: our vendored TabsContext is a different React context object than Docusaurus's, so user-authored <Tabs>/<TabItem> in MDX would fail to find a provider at SSR. The swizzles route stock markdown tabs through our context so the whole package stays internally consistent.

Auto-mounted ScrollControllerProvider inside our vendored TabsProvider so every tab consumer (the six OpenAPI variants + swizzled @theme/Tabs) is self-sufficient — no root-level swizzle required.

Removed the ambient declare module "@docusaurus/theme-common/internal" block from src/theme-classic.d.ts. The compiler now enforces no regressions.

Verification

  • tsc --noEmit clean
  • yarn workspace docusaurus-theme-openapi-docs build passes
  • yarn workspace demo run docusaurus build passes
  • grep "@docusaurus/theme-common/internal" src/ returns zero hits in source; zero require() hits in compiled lib/ output
  • CI green; Netlify deploy preview rendering as expected

Test plan

  • CI passes (build, type-check, lint)
  • Deploy preview verified
  • Manual smoke test in deploy preview:
    • API doc with multiple response codes — ApiTabs render with response-code colors (200 green, 400 red), scroll arrows when overflowed
    • Request/response content type switching — MimeTabs dispatches to Redux; curl snippet updates
    • Request body with oneOf/anyOf — SchemaTabs render, switching fires onChange
    • Code samples — ApiCodeBlock shows title, line numbers, word-wrap toggle, copy button
    • Markdown content with stock <Tabs>/<TabItem> still renders (swizzle regression check)

Notes on long-term maintenance

Vendored utilities are leaf functions (regex/string parsers + a couple of small hooks + a context). Upstream changes have historically been rare and additive; worst-case drift = "we don't pick up a new magic-comment syntax," not a bug. When Docusaurus v4 lands with new public extension points (per @slorber's hint on #1140), we can revisit and potentially retire vendored copies in favor of the new public APIs.

🤖 Generated with Claude Code

#1140)

Docusaurus v4 will remove the `@docusaurus/theme-common/internal` entry point
and the back-compat shim added in facebook/docusaurus#11153. This package
imported 13 symbols across 10 files from that path (CodeBlock utilities and
Tabs primitives), each of which already started emitting ESModulesLinkingWarning
in v3.8 user builds.

Vendor the leaf utilities into `src/utils/` (codeBlockUtils, useCodeWordWrap,
useMutationObserver, reactUtils, tabsUtils, scrollUtils) with MIT attribution
to Meta, repoint the 10 source files at the local copies, and delete the
`declare module "@docusaurus/theme-common/internal"` ambient shim so the
compiler enforces no future regressions.

Swizzle `@theme/Tabs` and `@theme/TabItem` so user-authored `<Tabs>`/`<TabItem>`
in MDX share the same React context as our six OpenAPI tab variants (ApiTabs,
MimeTabs, SchemaTabs, OperationTabs, DiscriminatorTabs, CodeTabs). Without the
swizzles, our vendored TabsContext is a different object than Docusaurus's, so
stock TabItem children would fail to find a provider at SSR. ScrollControllerProvider
is auto-mounted inside our vendored TabsProvider so every tab consumer is
self-sufficient.

The peer-dep range on `@docusaurus/*` is unchanged; vendoring removes our
coupling to the unstable entry point without affecting public API compatibility.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

Size Change: +262 B (+0.01%)

Total Size: 2.32 MB

📦 View Changed
Filename Size Change
demo/build/assets/js/main.********.js 684 kB +262 B (+0.04%)
ℹ️ View Unchanged
Filename Size
demo/.docusaurus/codeTranslations.json 2 B
demo/.docusaurus/docusaurus.config.mjs 16.4 kB
demo/.docusaurus/globalData.json 76.7 kB
demo/.docusaurus/i18n.json 372 B
demo/.docusaurus/registry.js 111 kB
demo/.docusaurus/routes.js 105 kB
demo/.docusaurus/routesChunkNames.json 43.6 kB
demo/.docusaurus/site-metadata.json 1.58 kB
demo/build/assets/css/styles.********.css 173 kB
demo/build/assets/js/runtime~main.********.js 25.3 kB
demo/build/index.html 99.5 kB
demo/build/petstore/add-pet/index.html 30 kB
demo/build/petstore/create-user/index.html 24.7 kB
demo/build/petstore/create-users-with-array-input/index.html 24.8 kB
demo/build/petstore/create-users-with-list-input/index.html 24.8 kB
demo/build/petstore/delete-order/index.html 24.5 kB
demo/build/petstore/delete-pet/index.html 24.8 kB
demo/build/petstore/delete-user/index.html 25 kB
demo/build/petstore/find-pets-by-status/index.html 25.5 kB
demo/build/petstore/find-pets-by-tags/index.html 26.2 kB
demo/build/petstore/get-inventory/index.html 23.8 kB
demo/build/petstore/get-order-by-id/index.html 24.8 kB
demo/build/petstore/get-pet-by-id/index.html 25.6 kB
demo/build/petstore/get-user-by-name/index.html 25.1 kB
demo/build/petstore/login-user/index.html 25.6 kB
demo/build/petstore/logout-user/index.html 24.4 kB
demo/build/petstore/new-pet/index.html 25 kB
demo/build/petstore/pet/index.html 23.2 kB
demo/build/petstore/place-order/index.html 24 kB
demo/build/petstore/schemas/apiresponse/index.html 25.2 kB
demo/build/petstore/schemas/cat/index.html 39.1 kB
demo/build/petstore/schemas/category/index.html 26.3 kB
demo/build/petstore/schemas/dog/index.html 39.3 kB
demo/build/petstore/schemas/honeybee/index.html 39.4 kB
demo/build/petstore/schemas/id/index.html 23.4 kB
demo/build/petstore/schemas/order/index.html 27.4 kB
demo/build/petstore/schemas/pet/index.html 38.9 kB
demo/build/petstore/schemas/tag/index.html 24.7 kB
demo/build/petstore/schemas/user/index.html 40.8 kB
demo/build/petstore/store/index.html 22.2 kB
demo/build/petstore/subscribe-to-the-store-events/index.html 30.9 kB
demo/build/petstore/swagger-petstore-yaml/index.html 30.9 kB
demo/build/petstore/update-pet-with-form/index.html 25 kB
demo/build/petstore/update-pet/index.html 25.4 kB
demo/build/petstore/update-user/index.html 25 kB
demo/build/petstore/upload-file/index.html 24.8 kB
demo/build/petstore/user/index.html 22.9 kB

compressed-size-action

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit ebdafdd):

https://docusaurus-openapi-36b86--pr1502-jfzzdf47.web.app

(expires Tue, 23 Jun 2026 13:13:27 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: bf293780ee827f578864d92193b8c2866acd459f

…alias

Avoid `../../../utils/...` relative paths by relocating the vendored utilities
into `src/theme/utils/` so they can be imported via the existing `@theme/*`
alias (`@theme/utils/codeBlockUtils`, `@theme/utils/tabsUtils`, etc.).

The `@theme/*` alias is dual-purpose: tsconfig path mapping for type-checking
within this package, and a Docusaurus webpack runtime alias resolved by the
consuming site. Existing codebase precedent: `@theme/translationIds`,
`@theme/ApiItem/store`, `@theme/ApiItem/hooks` — non-component utility files
already live under `src/theme/` and are consumed via `@theme/...` at both
compile and runtime.

No functional changes; pure file move + import rewrite. Cross-references inside
the utils directory remain as sibling `./` paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sserrata

Copy link
Copy Markdown
Member Author

Hi @slorber, interested to get your feedback on this approach. Do you foresee any issues with drift/maintenance of the vendored utils?

@slorber

slorber commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Hey

Maybe that would work, but it probably won't be ideal in the long term.

I would suggest splitting this PR into many smaller ones because it's vendoring all the internal usage at once, and makes it hard to have a dedicated conversation about each specific case. We could consider marking these APIs as "public but undocumented API" on which we guarantee semver in v4, on a case-by-case basis.

From what I recall, we only refactored the CodeBlock APIs in v3.8 to parse metadata globally and provide it through context: https://docusaurus.io/blog/releases/3.8#code-block-refactor

I'm not sure how this plugin's logic has diverged regarding these metadata compared to our official implementation, but I'd encourage you to migrate to context-based code block metadata, too.

import React, {type ReactNode} from 'react';
import {useThemeConfig} from '@docusaurus/theme-common';
import {
  CodeBlockContextProvider,
  type CodeBlockMetadata,
  createCodeBlockMetadata,
  useCodeWordWrap,
} from '@docusaurus/theme-common/internal';
import type {Props} from '@theme/CodeBlock/Content/String';
import CodeBlockLayout from '@theme/CodeBlock/Layout';

function useCodeBlockMetadata(props: Props): CodeBlockMetadata {
  const {prism} = useThemeConfig();
  return createCodeBlockMetadata({
    code: props.children,
    className: props.className,
    metastring: props.metastring,
    magicComments: prism.magicComments,
    defaultLanguage: prism.defaultLanguage,
    language: props.language,
    title: props.title,
    showLineNumbers: props.showLineNumbers,
  });
}

export default function CodeBlockString(props: Props): ReactNode {
  const metadata = useCodeBlockMetadata(props);
  const wordWrap = useCodeWordWrap();
  return (
    <CodeBlockContextProvider metadata={metadata} wordWrap={wordWrap}>
      <CodeBlockLayout />
    </CodeBlockContextProvider>
  );
}

Similarly, we refactored Tabs to rely on context for v3.10: facebook/docusaurus#11733

I don't think other things have changed much recently.


If that helps, I could consider marking these code blocks and tabs API stable in v4 and ensuring retro-compatibility. This way, you wouldn't have to vendor as much internal code.

I haven't planned to refactor these in the near future. Maybe we'll try to use <Activity> in tabs, but not sure it would involve a public API change.

// We keep the old name here since our call sites still use it.
export { parseClassNameLanguage as parseLanguage };

export function getPrismCssVariables(prismTheme: PrismTheme): CSSProperties {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still exported, I could move it to public API alongside

Comment on lines +20 to +26
import {
containsLineNumbers,
parseCodeBlockTitle,
parseLanguage,
parseLines,
} from "@theme/utils/codeBlockUtils";
import { useCodeWordWrap } from "@theme/utils/useCodeWordWrap";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider using our context-based API instead:

import {
  CodeBlockContextProvider,
  type CodeBlockMetadata,
  createCodeBlockMetadata,
  useCodeWordWrap,
} from '@docusaurus/theme-common/internal';

That could be marked as public

import useIsBrowser from "@docusaurus/useIsBrowser";
import clsx from "clsx";

import { useScrollPositionBlocker } from "@theme/utils/scrollUtils";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still exported, I could consider making it public api

Comment on lines +23 to +30
import { useScrollPositionBlocker } from "@theme/utils/scrollUtils";
import {
sanitizeTabsChildren,
type TabItemProps,
TabProps,
TabsProvider,
useTabsContextValue,
} from "@theme/utils/tabsUtils";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could become public api

type Props = TabItemProps;
import styles from "./styles.module.css";

function TabItemPanel({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What's the purpose of this file, it seems similar to our original component?

@@ -0,0 +1,48 @@
/* ============================================================================

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I could consider flagging these apis as public

@@ -0,0 +1,154 @@
/* ============================================================================

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I could consider making it public api

@@ -0,0 +1,329 @@
/* ============================================================================

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could become public api

@@ -0,0 +1,110 @@
/* ============================================================================

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure this one should become public yet. We have features and pending PRs asking for a global code word wrap toggle and we may need to update the API in v4.x

@@ -0,0 +1,41 @@
/* ============================================================================

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

that could become public

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Usage of Docusaurus theme-common/internal

2 participants