From 2979d0c967825e0862a831d1c12950b28d0a7140 Mon Sep 17 00:00:00 2001 From: Strek Date: Wed, 16 Feb 2022 23:06:04 +0530 Subject: [PATCH] Splitting sandpack from main bundle (#4256) * Initial Commit * play with it * oops * easier repro * import type * remove `suspense: true` * Add patch for next * Patch package * Add fallback * Enable flag * Fixes local dev env and adds better fallback for codeblock * Adds fallback for sandpack (should work fine) * turn off concurrentFeatures * Revert "turn off concurrentFeatures" This reverts commit 50158ecbd33969e707a2a91a54e822e90c2ebfde. * Update SandpackWrapper.tsx * Removed flags and setTimeouts * add timeouts and promise again * Adds bottom bezel and scroll to sandpack fallback * tinker bottombezel and remove console * Update CodeBlock.tsx * Update SandpackWrapper.tsx * removing overflows to avoid explicit scrolls * upgrade nextjs to canary * Rm patch * Fix TS * Bump Next * No more CSS jumping * Reverts the canary to use the latest Next.js `12.0.10` Co-authored-by: Dan Abramov Co-authored-by: Dan Abramov --- beta/next.config.js | 4 +- beta/package.json | 4 +- beta/src/components/MDX/APIAnatomy.tsx | 6 +- .../MDX/CodeBlock/CodeBlock.module.css | 7 - .../components/MDX/CodeBlock/CodeBlock.tsx | 17 +- beta/src/components/MDX/CodeBlock/index.tsx | 33 ++- beta/src/components/MDX/MDXComponents.tsx | 1 + beta/src/components/MDX/PackageImport.tsx | 1 + .../MDX/Sandpack/SandpackWrapper.tsx | 102 +++++++++ beta/src/components/MDX/Sandpack/index.tsx | 202 ++++++------------ beta/src/components/MDX/Sandpack/utils.ts | 54 ++++- beta/src/pages/_document.tsx | 36 ++-- beta/src/styles/sandpack.css | 4 +- beta/yarn.lock | 148 ++++++------- 14 files changed, 357 insertions(+), 262 deletions(-) delete mode 100644 beta/src/components/MDX/CodeBlock/CodeBlock.module.css create mode 100644 beta/src/components/MDX/Sandpack/SandpackWrapper.tsx diff --git a/beta/next.config.js b/beta/next.config.js index 3a2747ac..7b322272 100644 --- a/beta/next.config.js +++ b/beta/next.config.js @@ -11,7 +11,7 @@ module.exports = { experimental: { plugins: true, // TODO: this doesn't work because https://github.com/vercel/next.js/issues/30714 - // concurrentFeatures: true, + concurrentFeatures: false, scrollRestoration: true, }, async redirects() { @@ -27,7 +27,7 @@ module.exports = { }, webpack: (config, {dev, isServer, ...options}) => { if (process.env.ANALYZE) { - const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') + const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer'); config.plugins.push( new BundleAnalyzerPlugin({ analyzerMode: 'static', diff --git a/beta/package.json b/beta/package.json index 89be6637..8ddac1d3 100644 --- a/beta/package.json +++ b/beta/package.json @@ -18,7 +18,7 @@ "ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids", "tsc": "tsc --noEmit", "start": "next start", - "postinstall": "is-ci || (cd .. && husky install beta/.husky)", + "postinstall": "patch-package && (is-ci || (cd .. && husky install beta/.husky))", "check-all": "npm-run-all prettier lint:fix tsc" }, "dependencies": { @@ -32,7 +32,7 @@ "date-fns": "^2.16.1", "debounce": "^1.2.1", "github-slugger": "^1.3.0", - "next": "^12.0.9", + "next": "^12.0.10", "parse-numeric-range": "^1.2.0", "react": "experimental", "react-collapsed": "3.1.0", diff --git a/beta/src/components/MDX/APIAnatomy.tsx b/beta/src/components/MDX/APIAnatomy.tsx index 452a91dc..8965b436 100644 --- a/beta/src/components/MDX/APIAnatomy.tsx +++ b/beta/src/components/MDX/APIAnatomy.tsx @@ -59,7 +59,11 @@ export function APIAnatomy({children}: APIAnatomyProps) { break; case 'pre': acc.code = ( - + ); break; } diff --git a/beta/src/components/MDX/CodeBlock/CodeBlock.module.css b/beta/src/components/MDX/CodeBlock/CodeBlock.module.css deleted file mode 100644 index 176c53e5..00000000 --- a/beta/src/components/MDX/CodeBlock/CodeBlock.module.css +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - */ - -.codeViewer { - padding: 6px 0.5rem !important; -} diff --git a/beta/src/components/MDX/CodeBlock/CodeBlock.tsx b/beta/src/components/MDX/CodeBlock/CodeBlock.tsx index e36cb1d9..0ec6197a 100644 --- a/beta/src/components/MDX/CodeBlock/CodeBlock.tsx +++ b/beta/src/components/MDX/CodeBlock/CodeBlock.tsx @@ -4,14 +4,12 @@ import cn from 'classnames'; import { - ClasserProvider, SandpackCodeViewer, SandpackProvider, SandpackThemeProvider, } from '@codesandbox/sandpack-react'; import rangeParser from 'parse-numeric-range'; import {CustomTheme} from '../Sandpack/Themes'; -import styles from './CodeBlock.module.css'; interface InlineHiglight { step: number; @@ -86,16 +84,11 @@ const CodeBlock = function CodeBlock({ }, }}> - - - + diff --git a/beta/src/components/MDX/CodeBlock/index.tsx b/beta/src/components/MDX/CodeBlock/index.tsx index 12c2da98..d265571e 100644 --- a/beta/src/components/MDX/CodeBlock/index.tsx +++ b/beta/src/components/MDX/CodeBlock/index.tsx @@ -1,7 +1,34 @@ /* * Copyright (c) Facebook, Inc. and its affiliates. */ +import cn from 'classnames'; +import * as React from 'react'; +const CodeBlock = React.lazy(() => import('./CodeBlock')); -import CodeBlock from './CodeBlock'; - -export default CodeBlock; +export default React.memo(function CodeBlockWrapper(props: { + isFromAPIAnatomy: boolean; + isFromPackageImport: boolean; + children: string; + className?: string; + metastring: string; + noMargin?: boolean; + noMarkers?: boolean; +}): any { + const {children, isFromAPIAnatomy, isFromPackageImport} = props; + return ( + +
+

{children}

+
+ + }> + +
+ ); +}); diff --git a/beta/src/components/MDX/MDXComponents.tsx b/beta/src/components/MDX/MDXComponents.tsx index fae4eb97..0e363744 100644 --- a/beta/src/components/MDX/MDXComponents.tsx +++ b/beta/src/components/MDX/MDXComponents.tsx @@ -19,6 +19,7 @@ import Intro from './Intro'; import Link from './Link'; import {PackageImport} from './PackageImport'; import Recap from './Recap'; +import dynamic from 'next/dynamic'; import Sandpack from './Sandpack'; import SimpleCallout from './SimpleCallout'; import TerminalBlock from './TerminalBlock'; diff --git a/beta/src/components/MDX/PackageImport.tsx b/beta/src/components/MDX/PackageImport.tsx index edb2b6d0..83ae74f8 100644 --- a/beta/src/components/MDX/PackageImport.tsx +++ b/beta/src/components/MDX/PackageImport.tsx @@ -18,6 +18,7 @@ export function PackageImport({children}: PackageImportProps) { return ( + + setDevToolsLoaded(true)} + devToolsLoaded={devToolsLoaded} + /> + + + ); +} + +SandpackWrapper.displayName = 'Sandpack'; + +export default SandpackWrapper; diff --git a/beta/src/components/MDX/Sandpack/index.tsx b/beta/src/components/MDX/Sandpack/index.tsx index b0e6cee4..9c141732 100644 --- a/beta/src/components/MDX/Sandpack/index.tsx +++ b/beta/src/components/MDX/Sandpack/index.tsx @@ -1,146 +1,68 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - */ - -import React from 'react'; -import { - SandpackProvider, - SandpackSetup, - SandpackFile, -} from '@codesandbox/sandpack-react'; - -import {CustomPreset} from './CustomPreset'; - -type SandpackProps = { - children: React.ReactChildren; - autorun?: boolean; - setup?: SandpackSetup; - showDevTools?: boolean; -}; - -const sandboxStyle = ` -* { - box-sizing: border-box; -} - -body { - font-family: sans-serif; - margin: 20px; - padding: 0; -} - -h1 { - margin-top: 0; - font-size: 22px; -} - -h2 { - margin-top: 0; - font-size: 20px; -} - -h3 { - margin-top: 0; - font-size: 18px; -} - -h4 { - margin-top: 0; - font-size: 16px; -} - -h5 { - margin-top: 0; - font-size: 14px; -} - -h6 { - margin-top: 0; - font-size: 12px; -} - -ul { - padding-left: 20px; -} -`.trim(); - -function Sandpack(props: SandpackProps) { - 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; - - const files = codeSnippets.reduce( - (result: Record, codeSnippet: React.ReactElement) => { - if (codeSnippet.props.mdxType !== 'pre') { - return result; - } - const {props} = codeSnippet.props.children; - let filePath; // path in the folder structure - let fileHidden = false; // if the file is available as a tab - let fileActive = false; // if the file tab is shown by default - - if (props.metastring) { - const [name, ...params] = props.metastring.split(' '); - filePath = '/' + name; - if (params.includes('hidden')) { - fileHidden = true; - } - if (params.includes('active')) { - fileActive = true; - } - isSingleFile = false; - } else { - if (props.className === 'language-js') { - filePath = '/App.js'; - } else if (props.className === 'language-css') { - filePath = '/styles.css'; - } else { - throw new Error( - `Code block is missing a filename: ${props.children}` - ); - } - } - if (result[filePath]) { - throw new Error( - `File ${filePath} was defined multiple times. Each file snippet should have a unique path name` - ); - } - result[filePath] = { - code: (props.children as string).trim(), - hidden: fileHidden, - active: fileActive, - }; +import * as React from 'react'; +import {reducedCodeSnippet} from './utils'; +const Sandpack = React.lazy(() => import('./SandpackWrapper')); + +const SandpackFallBack = ({code}: {code: string}) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {code} +
+
+
+
+
+
+
+
+
+
+ {code.split('\n').length > 16 && ( +
+ )} +
+
+
+
+); - return result; - }, - {} +export default React.memo(function SandpackWrapper(props: any): any { + const codeSnippet = reducedCodeSnippet( + React.Children.toArray(props.children) ); - files['/styles.css'] = { - code: [sandboxStyle, files['/styles.css']?.code ?? ''].join('\n\n'), - hidden: true, - }; + // To set the active file in the fallback we have to find the active file first. If there are no active files we fallback to App.js as default + let activeCodeSnippet = Object.keys(codeSnippet).filter( + (fileName) => + codeSnippet[fileName]?.active === true && + codeSnippet[fileName]?.hidden === false + ); + let activeCode; + if (!activeCodeSnippet.length) { + activeCode = codeSnippet['/App.js'].code; + } else { + activeCode = codeSnippet[activeCodeSnippet[0]].code; + } return ( -
- - setDevToolsLoaded(true)} - devToolsLoaded={devToolsLoaded} - /> - -
+ <> + }> + + + ); -} - -Sandpack.displayName = 'Sandpack'; - -export default Sandpack; +}); diff --git a/beta/src/components/MDX/Sandpack/utils.ts b/beta/src/components/MDX/Sandpack/utils.ts index d8fe9e90..abcb5c64 100644 --- a/beta/src/components/MDX/Sandpack/utils.ts +++ b/beta/src/components/MDX/Sandpack/utils.ts @@ -1,7 +1,7 @@ /* * Copyright (c) Facebook, Inc. and its affiliates. */ - +import type {SandpackFile} from '@codesandbox/sandpack-react'; export type ViewportSizePreset = | 'iPhone X' | 'Pixel 2' @@ -47,3 +47,55 @@ export const computeViewportSize = ( return viewport; }; + +//TODO: revisit to reduce this (moved this to utils from sandpackWrapper since it is being used for finding active file for fallBack too) +export const reducedCodeSnippet = (codeSnippets: any) => { + let isSingleFile = true; + + return codeSnippets.reduce( + (result: Record, codeSnippet: React.ReactElement) => { + if (codeSnippet.props.mdxType !== 'pre') { + return result; + } + const {props} = codeSnippet.props.children; + let filePath; // path in the folder structure + let fileHidden = false; // if the file is available as a tab + let fileActive = false; // if the file tab is shown by default + + if (props.metastring) { + const [name, ...params] = props.metastring.split(' '); + filePath = '/' + name; + if (params.includes('hidden')) { + fileHidden = true; + } + if (params.includes('active')) { + fileActive = true; + } + isSingleFile = false; + } else { + if (props.className === 'language-js') { + filePath = '/App.js'; + } else if (props.className === 'language-css') { + filePath = '/styles.css'; + } else { + throw new Error( + `Code block is missing a filename: ${props.children}` + ); + } + } + if (result[filePath]) { + throw new Error( + `File ${filePath} was defined multiple times. Each file snippet should have a unique path name` + ); + } + result[filePath] = { + code: props.children as string, + hidden: fileHidden, + active: fileActive, + }; + + return result; + }, + {} + ); +}; diff --git a/beta/src/pages/_document.tsx b/beta/src/pages/_document.tsx index 969a659e..d93094e0 100644 --- a/beta/src/pages/_document.tsx +++ b/beta/src/pages/_document.tsx @@ -5,16 +5,15 @@ import * as React from 'react'; import Document, {Html, Head, Main, NextScript} from 'next/document'; -class MyDocument extends Document { - render() { - // @todo specify language in HTML? - return ( - - - -