The CodeExternalsContext is a simple React context that provides access to external dependencies (modules and components) for demo components. It's primarily used internally by demo client providers created with abstractCreateDemoClient to make precomputed externals available to child components.
interface CodeExternalsContext {
externals?: Record<string, Module>;
}
type Module = {}; // Can be any imported module or object
function useCodeExternals(): CodeExternalsContext | undefined;
The primary way to access externals is through the useCodeExternals hook:
import { useCodeExternals } from '@mui/internal-docs-infra/CodeExternalsContext';
function DemoComponent() {
const externalsContext = useCodeExternals();
const externals = externalsContext?.externals || {};
// Access specific externals
const React = externals.react;
const { Button } = externals['@mui/material'] || {};
return <Button>Demo Button</Button>;
}
Demo clients are transformed by the build system to automatically provide this context. Here's what happens:
Before (source code):
// demo/client.ts
'use client';
import { createDemoClient } from '../createDemoClient';
export const DemoExampleClient = createDemoClient(import.meta.url);
After (build system transformation):
// demo/client.ts - Transformed by loader
'use client';
import React from 'react';
import { Button } from '@mui/material';
import { createDemoClient } from '../createDemoClient';
export const DemoExampleClient = createDemoClient(import.meta.url, {
precompute: {
externals: {
react: React,
'@mui/material': { Button },
},
},
});
// The client provider will make these externals available via CodeExternalsContext
The context provides a simple interface:
const context = {
externals: {
react: React, // Default import
'@mui/material': { Button }, // Named imports as object
'@mui/system': { styled }, // Single named import
'./local-module': { helper }, // Local modules
},
};
function SafeExternalDemo() {
const externalsContext = useCodeExternals();
if (!externalsContext?.externals) {
return <div>No externals available</div>;
}
const { react: React, '@mui/material': MUI = {} } = externalsContext.externals;
const { Button } = MUI;
if (!Button) {
return <div>Button component not available</div>;
}
return <Button>Safe Button</Button>;
}
When building live demo components that execute code dynamically, externals provide the scope for code execution. Here's how the provider and consumer work together:
Provider Setup (Demo Client):
// demo/client.tsx - The demo client provides externals context
'use client';
import * as React from 'react';
import { Button } from '@mui/material';
import { CodeExternalsContext } from '@mui/internal-docs-infra/CodeExternalsContext';
// This would typically be created by abstractCreateDemoClient
const externals = {
react: React,
'@mui/material': { Button },
// Other external dependencies...
};
function DemoProvider({ children }: { children: React.ReactNode }) {
return (
<CodeExternalsContext.Provider value={{ externals }}>{children}</CodeExternalsContext.Provider>
);
}
export { DemoProvider };
Consumer (Live Code Runner):
'use client';
import * as React from 'react';
import { useRunner } from 'react-runner';
import { useCodeExternals } from '@mui/internal-docs-infra/CodeExternalsContext';
function LiveCodeRunner({ code }: { code: string }) {
const externalsContext = useCodeExternals();
const scope = React.useMemo(() => {
let externals = externalsContext?.externals;
if (!externals) {
// Fallback if no externals context available
externals = { imports: { react: React } };
}
// Format externals for react-runner
return { import: { ...externals } };
}, [externalsContext]);
const { element, error } = useRunner({ code, scope });
if (error) {
return <div style={{ color: 'red' }}>Error: {error}</div>;
}
return element;
}
// Complete demo with provider
function CompleteLiveDemo() {
const [code, setCode] = React.useState(`
import React from 'react';
import { Button } from '@mui/material';
export default function Example() {
return <Button variant="contained">Live Demo!</Button>;
}
`);
return (
<DemoProvider>
<div>
<textarea
value={code}
onChange={(e) => setCode(e.target.value)}
style={{ width: '100%', height: 200 }}
/>
<div>
<h4>Live Preview:</h4>
<LiveCodeRunner code={code} />
</div>
</div>
</DemoProvider>
);
}
function ConditionalDemo() {
const externalsContext = useCodeExternals();
const externals = externalsContext?.externals || {};
const hasButton = externals['@mui/material']?.Button;
const hasTypography = externals['@mui/material']?.Typography;
return (
<div>
{hasButton && <externals['@mui/material'].Button>Button Available</externals['@mui/material'].Button>}
{hasTypography && <externals['@mui/material'].Typography>Typography Available</externals['@mui/material'].Typography>}
{!hasButton && !hasTypography && <div>No MUI components available</div>}
</div>
);
}
function ExternalDebugger() {
const externalsContext = useCodeExternals();
const externals = externalsContext?.externals || {};
return (
<details>
<summary>Available Externals ({Object.keys(externals).length})</summary>
<ul>
{Object.entries(externals).map(([key, value]) => (
<li key={key}>
<strong>{key}:</strong> {typeof value}
{value && typeof value === 'object' && !React.isValidElement(value) && (
<ul>
{Object.keys(value).map((subKey) => (
<li key={subKey}>{subKey}</li>
))}
</ul>
)}
</li>
))}
</ul>
</details>
);
}
{ externals?: Record<string, {}> } | undefinedThe context works seamlessly with the docs-infra build system:
useCodeExternals to access the externalsabstractCreateDemoClient: Creates providers that supply this contextloadPrecomputedCodeHighlighterClient: Loader that injects externalsCodeHighlighter: Main component for code highlighting and demos