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

feat: add code transformer to <Demo> component #7894

Merged
merged 5 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const IMPORT_REGEX = /^import [^'"]* from ['"]([^.'"\n ][^'"\n ]*)['"];?/gm;
const DEFAULT_EXPORT_REGEX = /export default /;
const COMPONENT_ONLY_REGEX = /^\(?</;

/**
* Transforms input JS/TS source code to a react-live compatible syntax.
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the great description, that's really helpful for understanding! 🙏

* react-live uses the surcase library to transform input source code into
* browser-readable JavaScript.
*
* While surcase does support CommonJS and ESM import/export statements,
* it's not trivial to expose our internal React and EUI exports through it
* and because we already control the execution scope of the interactive demos
* it isn't really necessary to implement a smart `require()` replacement.
*
* Returning an IIFE is necessary when the source code is more than just
* a JSX component definition (e.g. it contains a variable definition
* or `export default` statement).
*
* @see {@link https://github.com/alangpierce/sucrase}
* @see {@link https://github.com/FormidableLabs/react-live/blob/master/packages/react-live/src/utils/transpile/index.ts}
*/
export const demoCodeTransformer = (code: string) => {
// Remove ESM imports
code = code.replace(IMPORT_REGEX, '');

// Handle ESM default exports
code = code.replace(DEFAULT_EXPORT_REGEX, 'return ');

// If the demo is JSX only return as-is
if (COMPONENT_ONLY_REGEX.test(code)) {
return code;
}

// If the demo is more than just JSX wrap in an immediately invoked function expression
return `(() => { ${code} })()`;
Copy link
Contributor

Choose a reason for hiding this comment

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

🧠

}
48 changes: 38 additions & 10 deletions packages/docusaurus-theme/src/components/demo/demo.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { Children, PropsWithChildren, useCallback, useState } from 'react';
import {
Children,
PropsWithChildren,
useCallback,
useMemo,
useState,
} from 'react';
import { isElement } from 'react-is';
import { LiveProvider } from 'react-live';
import { themes as prismThemes } from 'prism-react-renderer';
import { useEuiMemoizedStyles, copyToClipboard, UseEuiTheme } from '@elastic/eui';
import {
useEuiMemoizedStyles,
copyToClipboard,
UseEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { DemoContext, DemoContextObject } from './context';
import { DemoEditor } from './editor';
import { DemoPreview } from './preview';
import { DemoSource } from './source';
import { demoScope } from './scope';
import { demoDefaultScope } from './scope';
import { DemoActionsBar } from './actions_bar';
import { demoCodeTransformer } from './code_transformer';

export interface DemoSourceMeta {
code: string;
Expand All @@ -18,11 +29,19 @@ export interface DemoSourceMeta {
}

export interface DemoProps extends PropsWithChildren {
/**
* Whether the source code editor is open by default
*/
isSourceOpen?: boolean;
/**
* Allows to extend the default scope of the rendered demo and pass additional
* properties available within the demo.
*
* The default scope exposes all React and EUI exports.
*/
scope?: Record<string, unknown>;
}

const transformCode = (code: string) => code;

const getDemoStyles = (euiTheme: UseEuiTheme) => ({
demo: css`
--docs-demo-border-color: ${euiTheme.euiTheme.colors.lightShade};
Expand All @@ -36,6 +55,7 @@ const getDemoStyles = (euiTheme: UseEuiTheme) => ({

export const Demo = ({
children,
scope,
isSourceOpen: _isSourceOpen = true
}: DemoProps) => {
const styles = useEuiMemoizedStyles(getDemoStyles);
Expand All @@ -46,9 +66,17 @@ export const Demo = ({
// liveProviderKey restarts the demo to its initial state
const [liveProviderKey, setLiveProviderKey] = useState<number>(0);

const addSource = useCallback<DemoContextObject['addSource']>((source: DemoSourceMeta) => {
setSources((sources) => ([...sources, source]));
}, []);
const finalScope = useMemo(() => ({
...demoDefaultScope,
...scope,
}), [scope]);

const addSource = useCallback<DemoContextObject['addSource']>(
(source: DemoSourceMeta) => {
setSources((sources) => ([...sources, source]));
},
[],
);

const onClickCopyToClipboard = useCallback(() => {
copyToClipboard(activeSource?.code || '');
Expand All @@ -69,9 +97,9 @@ export const Demo = ({
<LiveProvider
key={liveProviderKey}
code={activeSource?.code || ''}
transformCode={transformCode}
transformCode={demoCodeTransformer}
theme={prismThemes.dracula}
scope={demoScope}
scope={finalScope}
>
<DemoPreview />
<DemoActionsBar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const getEditorStyles = () => ({

& .prism-code {
border-radius: 0 0 calc(var(--docs-demo-border-radius) - 1px) calc(var(--docs-demo-border-radius) - 1px);
max-height: 450px;
overflow: auto;
}
`,
error: css`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { css } from '@emotion/react';
import { LiveError, LivePreview } from 'react-live';
import { LivePreview } from 'react-live';
import BrowserOnly from '@docusaurus/BrowserOnly';
import ErrorBoundary from '@docusaurus/ErrorBoundary';
import { ErrorBoundaryErrorMessageFallback } from '@docusaurus/theme-common';
Expand Down Expand Up @@ -30,9 +30,9 @@ export const DemoPreview = () => {
{() => (
<>
<ErrorBoundary fallback={(params: any) => <ErrorBoundaryErrorMessageFallback {...params} />}>
<EuiFlexGroup css={styles.previewWrapper} alignItems="center" justifyContent="center">
<div css={styles.previewWrapper}>
<LivePreview />
</EuiFlexGroup>
</div>
</ErrorBoundary>
</>
)}
Expand Down
5 changes: 4 additions & 1 deletion packages/docusaurus-theme/src/components/demo/scope.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import React from 'react';
import * as EUI from '@elastic/eui';

export const demoScope: Record<string, unknown> = {
export const demoDefaultScope: Record<string, unknown> = {
// React
React,
...React,

// EUI exports
...EUI,
};
Loading
Loading