Skip to content

Sandpack: upgrade dependencies and adds ReactDevtools #4161

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

Merged
merged 26 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
6 changes: 3 additions & 3 deletions beta/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"check-all": "npm-run-all prettier lint:fix tsc"
},
"dependencies": {
"@codesandbox/sandpack-react": "^0.1.20",
"@codesandbox/sandpack-react": "0.13.6-experimental.0",
"@docsearch/css": "3.0.0-alpha.41",
"@docsearch/react": "3.0.0-alpha.41",
"@headlessui/react": "^1.3.0",
Expand All @@ -34,9 +34,9 @@
"github-slugger": "^1.3.0",
"next": "^12.0.5",
"parse-numeric-range": "^1.2.0",
"react": "18.0.0-alpha-930c9e7ee-20211015",
"react": "experimental",
"react-collapsed": "3.1.0",
"react-dom": "18.0.0-alpha-930c9e7ee-20211015",
"react-dom": "experimental",
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is fine 👍🏼 at least until the caching APIs become stable.

"scroll-into-view-if-needed": "^2.2.25"
},
"devDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions beta/src/components/Icon/IconNewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import * as React from 'react';

export const IconNewPage = React.memo<JSX.IntrinsicElements['svg']>(
function IconNewPage({className}) {
function IconNewPage(props) {
return (
<svg
className={className}
width="0.72em"
height="0.72em"
viewBox="0 0 13 13"
xmlns="http://www.w3.org/2000/svg">
xmlns="http://www.w3.org/2000/svg"
{...props}>
<path
d="M4.72038 2.94824V4.28158H1.38704V11.6149H8.72038V8.28158H10.0537V12.2816C10.0537 12.4584 9.98347 12.628 9.85845 12.753C9.73343 12.878 9.56386 12.9482 9.38704 12.9482H0.720378C0.543567 12.9482 0.373997 12.878 0.248973 12.753C0.123949 12.628 0.0537109 12.4584 0.0537109 12.2816V3.61491C0.0537109 3.4381 0.123949 3.26853 0.248973 3.1435C0.373997 3.01848 0.543567 2.94824 0.720378 2.94824H4.72038ZM12.0537 0.948242V6.28158H10.7204V3.22358L5.52504 8.41958L4.58238 7.47691L9.77704 2.28158H6.72038V0.948242H12.0537Z"
fill="currentColor"
Expand Down
7 changes: 1 addition & 6 deletions beta/src/components/MDX/APIAnatomy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ const colors = [

export function APIAnatomy({children}: APIAnatomyProps) {
const [activeStep, setActiveStep] = React.useState<number | null>(null);
const ref = React.useRef<HTMLDivElement>();

const {steps, code} = React.Children.toArray(children).reduce(
(acc: AnatomyContent, child) => {
Expand All @@ -60,11 +59,7 @@ export function APIAnatomy({children}: APIAnatomyProps) {
break;
case 'pre':
acc.code = (
<CodeBlock
ref={ref}
{...child.props.children.props}
noMargin={true}
/>
<CodeBlock {...child.props.children.props} noMargin={true} />
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed ref as this hasn't been used anywhere

);
break;
}
Expand Down
34 changes: 15 additions & 19 deletions beta/src/components/MDX/CodeBlock/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Copyright (c) Facebook, Inc. and its affiliates.
*/

import * as React from 'react';
import cn from 'classnames';
import {
ClasserProvider,
Expand All @@ -21,22 +20,19 @@ interface InlineHiglight {
endColumn: number;
}

const CodeBlock = React.forwardRef(function CodeBlock(
{
children,
className = 'language-js',
metastring,
noMargin,
noMarkers,
}: {
children: string;
className?: string;
metastring: string;
noMargin?: boolean;
noMarkers?: boolean;
},
ref?: React.Ref<HTMLDivElement>
) {
const CodeBlock = function CodeBlock({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only removed forwardRef

children,
className = 'language-js',
metastring,
noMargin,
noMarkers,
}: {
children: string;
className?: string;
metastring: string;
noMargin?: boolean;
noMarkers?: boolean;
}) {
const getDecoratedLineInfo = () => {
if (!metastring) {
return [];
Expand Down Expand Up @@ -95,7 +91,7 @@ const CodeBlock = React.forwardRef(function CodeBlock(
'sp-cm': styles.codeViewer,
}}>
<SandpackCodeViewer
ref={ref}
key={children.trimEnd()}
showLineNumbers={false}
decorators={decorators}
/>
Expand All @@ -104,7 +100,7 @@ const CodeBlock = React.forwardRef(function CodeBlock(
</SandpackProvider>
</div>
);
});
};

export default CodeBlock;

Expand Down
23 changes: 19 additions & 4 deletions beta/src/components/MDX/Sandpack/CustomPreset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,27 @@ import {
useActiveCode,
SandpackCodeEditor,
SandpackThemeProvider,
SandpackReactDevTools,
} from '@codesandbox/sandpack-react';
import scrollIntoView from 'scroll-into-view-if-needed';

import cn from 'classnames';

import {IconChevron} from 'components/Icon/IconChevron';
import {NavigationBar} from './NavigationBar';
import {Preview} from './Preview';
import {CustomTheme} from './Themes';

export function CustomPreset({
isSingleFile,
onReset,
showDevTools,
onDevToolsLoad,
devToolsLoaded,
}: {
isSingleFile: boolean;
onReset: () => void;
showDevTools: boolean;
devToolsLoaded: boolean;
onDevToolsLoad: () => void;
}) {
const lineCountRef = React.useRef<{[key: string]: number}>({});
const containerRef = React.useRef<HTMLDivElement>(null);
Expand All @@ -50,11 +57,14 @@ export function CustomPreset({
<div
className="shadow-lg dark:shadow-lg-dark rounded-lg"
ref={containerRef}>
<NavigationBar showDownload={isSingleFile} onReset={onReset} />
<NavigationBar showDownload={isSingleFile} />
<SandpackThemeProvider theme={CustomTheme}>
<div
ref={sandpack.lazyAnchorRef}
className="sp-layout rounded-t-none"
className={cn(
'sp-layout sp-custom-layout',
showDevTools && devToolsLoaded && 'sp-layout-devtools'
)}
style={{
// Prevent it from collapsing below the initial (non-loaded) height.
// There has to be some better way to do this...
Expand All @@ -77,6 +87,7 @@ export function CustomPreset({
maxHeight: isExpanded ? '' : 406,
}}
/>

{isExpandable && (
<button
translate="yes"
Expand Down Expand Up @@ -104,6 +115,10 @@ export function CustomPreset({
</button>
)}
</div>

{showDevTools && (
<SandpackReactDevTools onLoadModule={onDevToolsLoad} />
)}
</SandpackThemeProvider>
</div>
</>
Expand Down
27 changes: 16 additions & 11 deletions beta/src/components/MDX/Sandpack/NavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
*/

import * as React from 'react';
import {FileTabs, useSandpack} from '@codesandbox/sandpack-react';
import {
FileTabs,
useSandpack,
useSandpackNavigation,
} from '@codesandbox/sandpack-react';
import {OpenInCodeSandboxButton} from './OpenInCodeSandboxButton';
import {ResetButton} from './ResetButton';
import {DownloadButton} from './DownloadButton';
import {FilesDropdown} from './FilesDropdown';

export function NavigationBar({
showDownload,
onReset,
}: {
showDownload: boolean;
onReset: () => void;
}) {
export function NavigationBar({showDownload}: {showDownload: boolean}) {
const {sandpack} = useSandpack();
const [dropdownActive, setDropdownActive] = React.useState(false);
const {openPaths} = sandpack;
const {openPaths, clients} = sandpack;
const clientId = Object.keys(clients)[0];
const {refresh} = useSandpackNavigation(clientId);

const resizeHandler = React.useCallback(() => {
const width = window.innerWidth || document.documentElement.clientWidth;
Expand All @@ -41,6 +41,11 @@ export function NavigationBar({
return;
}, [openPaths.length, resizeHandler]);

const handleReset = () => {
sandpack.resetAllFiles();
refresh();
};

return (
<div className="bg-wash dark:bg-card-dark flex justify-between items-center relative z-10 border-b border-border dark:border-border-dark rounded-t-lg rounded-b-none">
<div className="px-4 lg:px-6">
Expand All @@ -50,8 +55,8 @@ export function NavigationBar({
className="px-3 flex items-center justify-end flex-grow text-right"
translate="yes">
{showDownload && <DownloadButton />}
<ResetButton onReset={onReset} />
<OpenInCodeSandboxButton className="ml-2 md:ml-4" />
<ResetButton onReset={handleReset} />
<OpenInCodeSandboxButton />
</div>
</div>
);
Expand Down
31 changes: 11 additions & 20 deletions beta/src/components/MDX/Sandpack/OpenInCodeSandboxButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,20 @@
*/

import * as React from 'react';
import {useCodeSandboxLink} from '@codesandbox/sandpack-react';
import cn from 'classnames';
import {UnstyledOpenInCodeSandboxButton} from '@codesandbox/sandpack-react';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a minor Sandpack break change that fixed a bug for long sandboxes

import {IconNewPage} from '../../Icon/IconNewPage';

export const OpenInCodeSandboxButton = ({className}: {className?: string}) => {
const url = useCodeSandboxLink();

export const OpenInCodeSandboxButton = () => {
return (
<a
className={cn(
'text-sm text-primary dark:text-primary-dark inline-flex items-center hover:text-link duration-100 ease-in transition mx-1',
className
)}
href={url}
rel="noreferrer noopener"
target="_blank"
<UnstyledOpenInCodeSandboxButton
className="text-sm text-primary dark:text-primary-dark inline-flex items-center hover:text-link duration-100 ease-in transition mx-1 ml-3 md:ml-1"
title="Open in CodeSandbox">
<span className="hidden md:inline">
<IconNewPage className="inline mb-0.5 text-base" /> Fork
</span>
<span className="inline md:hidden">
<IconNewPage className="inline mb-0.5 text-base" /> Fork
</span>
</a>
<IconNewPage
className="inline mb-0.5 ml-1 mr-1 relative top-px"
width=".8em"
height=".8em"
/>
<span className="hidden md:block">Fork</span>
</UnstyledOpenInCodeSandboxButton>
);
};
25 changes: 7 additions & 18 deletions beta/src/components/MDX/Sandpack/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type SandpackProps = {
children: React.ReactChildren;
autorun?: boolean;
setup?: SandpackSetup;
showDevTools?: boolean;
};

const sandboxStyle = `
Expand Down Expand Up @@ -64,8 +65,8 @@ ul {
`.trim();

function Sandpack(props: SandpackProps) {
let {children, setup, autorun = true} = props;
let [resetKey, setResetKey] = React.useState(0);
let {children, setup, autorun = true, showDevTools = false} = props;
const [devToolsLoaded, setDevToolsLoaded] = React.useState(false);
let codeSnippets = React.Children.toArray(children) as React.ReactElement[];
let isSingleFile = true;

Expand Down Expand Up @@ -121,29 +122,17 @@ function Sandpack(props: SandpackProps) {
hidden: true,
};

let key = String(resetKey);
Copy link
Member

@gaearon gaearon Jan 18, 2022

Choose a reason for hiding this comment

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

Why was this removed? Is this not needed anymore? We use it in development so that editing sandbox source immediately forces it to refresh. This is important for authoring content.

See the lines just below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry if I'm missing something, but in my local tests, both situations worked fine (development and production mode). The main reason I removed the whole reset logic is that this key was causing an unnecessary render on production mode, and Sandpack already provides an API to reset all changes.

However, even in development mode, any changes on files shouldn't be enough to trigger a remount on the Sandpack component? At least, in the Sandpack implementation, we're taking this into account, but maybe I'm missing some specific case.

I brought back the key logic anyway, but I added a new conditional not to remount it on production mode, let me know if it makes sense.

Copy link
Member

Choose a reason for hiding this comment

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

but in my local tests, both situations worked fine (development and production mode)

Yeah this looks like it didn't work out of the box in the beginning of 2021, but has since been fixed!

if (process.env.NODE_ENV !== 'production') {
// Remount on any source change in development.
key +=
'-' +
JSON.stringify({
...props,
children: files,
});
}

return (
<div className="my-8" translate="no">
<div className="sandpack-container my-8" translate="no">
<SandpackProvider
key={key}
template="react"
customSetup={{...setup, files: files}}
autorun={autorun}>
<CustomPreset
isSingleFile={isSingleFile}
onReset={() => {
setResetKey((k) => k + 1);
}}
showDevTools={showDevTools}
onDevToolsLoad={() => setDevToolsLoaded(true)}
devToolsLoaded={devToolsLoaded}
/>
</SandpackProvider>
</div>
Expand Down
Loading