fix(theme): vendor theme-common/internal usages ahead of Docusaurus v4 (#1140)#1502
fix(theme): vendor theme-common/internal usages ahead of Docusaurus v4 (#1140)#1502sserrata wants to merge 3 commits into
Conversation
#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>
|
Size Change: +262 B (+0.01%) Total Size: 2.32 MB 📦 View Changed
ℹ️ View Unchanged
|
|
Visit the preview URL for this PR (updated for commit 49c6fdf): https://docusaurus-openapi-36b86--pr1502-jfzzdf47.web.app (expires Tue, 30 Jun 2026 17:01:32 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>
|
Hi @slorber, interested to get your feedback on this approach. Do you foresee any issues with drift/maintenance of the vendored utils? |
|
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 |
| // We keep the old name here since our call sites still use it. | ||
| export { parseClassNameLanguage as parseLanguage }; | ||
|
|
||
| export function getPrismCssVariables(prismTheme: PrismTheme): CSSProperties { |
There was a problem hiding this comment.
Still exported, I could move it to public API alongside
| import { | ||
| containsLineNumbers, | ||
| parseCodeBlockTitle, | ||
| parseLanguage, | ||
| parseLines, | ||
| } from "@theme/utils/codeBlockUtils"; | ||
| import { useCodeWordWrap } from "@theme/utils/useCodeWordWrap"; |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
Still exported, I could consider making it public api
| import { useScrollPositionBlocker } from "@theme/utils/scrollUtils"; | ||
| import { | ||
| sanitizeTabsChildren, | ||
| type TabItemProps, | ||
| TabProps, | ||
| TabsProvider, | ||
| useTabsContextValue, | ||
| } from "@theme/utils/tabsUtils"; |
| type Props = TabItemProps; | ||
| import styles from "./styles.module.css"; | ||
|
|
||
| function TabItemPanel({ |
There was a problem hiding this comment.
What's the purpose of this file, it seems similar to our original component?
There was a problem hiding this comment.
Functionally exact copies. The only change is re-routing useTabs/TabsProvider/sanitizeTabsChildren/useTabsContextValue (and useScrollPositionBlocker) from @docusaurus/theme-common/internal to our vendored @theme/utils/tabsUtils. Needed because our OpenAPI tab variants (ApiTabs, MimeTabs, SchemaTabs, etc.) run on the same vendored context, so <Tabs>/<TabItem> must read the same provider. If those hooks become public in v4, both swizzles can be deleted.
| @@ -0,0 +1,48 @@ | |||
| /* ============================================================================ | |||
There was a problem hiding this comment.
I could consider flagging these apis as public
| @@ -0,0 +1,154 @@ | |||
| /* ============================================================================ | |||
There was a problem hiding this comment.
I could consider making it public api
| @@ -0,0 +1,329 @@ | |||
| /* ============================================================================ | |||
| @@ -0,0 +1,110 @@ | |||
| /* ============================================================================ | |||
There was a problem hiding this comment.
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 @@ | |||
| /* ============================================================================ | |||
|
Hi @slorber, thanks for the feedback. Regarding marking CodeBlock and Tabs as public/stable but undocumented, sure that could definitely help. But, the more I think about it, I'm also comfortable with whatever you decide. After having maintained this project for over 5 years now, even through the beta months, I've grown accustomed to having to do some refactoring with each new docusaurus release, although it has been much less of a heavy lift in v3. |
|
I see thanks FYI, I noticed some APIs you use are actually already flagged as public, for example, I think we need to figure out a plan to transition you to the new APIs, one at a time. It's important you do that before we mark new APIs as public because we need to somehow validate that those new APIs work well for your use-case. Now that they are not public yet, it's a good time to give us feedback on their flexibility because it may impact other plugin authors as well. Maybe we could have one dedicated PR for tabs, and one for the code block, where you try to adopt these new APIs? |
Per slorber's review on #1502, useEvent and ReactContextError are already public APIs in @docusaurus/theme-common, so we don't need to vendor them. Import them directly from the package and keep only useShallowMemoObject in the vendored reactUtils (it is not re-exported, public or internal). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Sounds good. I'll keep this PR scoped to removing the
Starting with Tabs since it's smaller and will give you concrete feedback on the new API before CodeBlock. |
Summary
Closes #1140. Migrates
docusaurus-theme-openapi-docsoff of@docusaurus/theme-common/internalahead 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):codeBlockUtils.tsparseCodeBlockTitle,parseLanguage,parseLines,containsLineNumbers,getPrismCssVariablesuseCodeWordWrap.tsuseCodeWordWrapuseMutationObserver.tsuseCodeWordWrapreactUtils.tsuseEvent,useShallowMemoObject,ReactContextErrortabsUtils.tsxsanitizeTabsChildren,useTabsContextValue,useTabs,TabsProvider(+ inlineduseQueryStringValueto avoid pullinghistoryUtils)scrollUtils.tsxScrollControllerProvider,useScrollPositionBlockerFiles 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/Tabsand@theme/TabItem. Reason: our vendoredTabsContextis 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
ScrollControllerProviderinside our vendoredTabsProviderso 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 fromsrc/theme-classic.d.ts. The compiler now enforces no regressions.Verification
tsc --noEmitcleanyarn workspace docusaurus-theme-openapi-docs buildpassesyarn workspace demo run docusaurus buildpassesgrep "@docusaurus/theme-common/internal" src/returns zero hits in source; zerorequire()hits in compiledlib/outputTest plan
oneOf/anyOf— SchemaTabs render, switching firesonChange<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