From e6a5840c68afba846b18b1d67cfd1332bfaa1710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20Bl=C3=A4ttermann?= Date: Wed, 25 Jan 2023 17:30:41 +0100 Subject: [PATCH] Load components dynamically (decrease initial file size for docs) (#12175) * Extract `CodeBlock` component into own file * Extract `InlineCode` component into own file * Extract `TypeAnnotation` component into own file * Convert named `export` to `default export` * Remove unused `export` * Simplify `TypeAnnotation` to remove dependency for Prism * Load `Code` component dynamically * Extract `MarkdownToReact` component into own file * WIP Code Dynamic * Load `MarkdownToReact` component dynamically * Extract `htmlToReact` to own file * Load `htmlToReact` component dynamically * Dynamically load `Juniper` --- website/pages/index.tsx | 2 +- website/src/components/code.js | 94 ++----------------- website/src/components/codeBlock.js | 14 +++ website/src/components/codeDynamic.js | 5 + website/src/components/embed.js | 4 +- website/src/components/github.js | 2 +- website/src/components/htmlToReact.js | 12 +++ website/src/components/inlineCode.js | 23 +++++ website/src/components/landing.js | 2 +- website/src/components/markdownToReact.js | 32 +++++++ .../src/components/markdownToReactDynamic.js | 5 + website/src/components/title.js | 2 +- website/src/components/typeAnnotation.js | 51 ++++++++++ website/src/components/util.js | 45 +-------- website/src/remark.js | 5 +- website/src/templates/index.js | 2 +- website/src/templates/models.js | 13 +-- website/src/templates/universe.js | 6 +- website/src/widgets/changelog.js | 2 +- website/src/widgets/languages.js | 2 +- website/src/widgets/project.js | 2 +- website/src/widgets/quickstart-training.js | 8 +- 22 files changed, 179 insertions(+), 154 deletions(-) create mode 100644 website/src/components/codeBlock.js create mode 100644 website/src/components/codeDynamic.js create mode 100644 website/src/components/htmlToReact.js create mode 100644 website/src/components/inlineCode.js create mode 100644 website/src/components/markdownToReact.js create mode 100644 website/src/components/markdownToReactDynamic.js create mode 100644 website/src/components/typeAnnotation.js diff --git a/website/pages/index.tsx b/website/pages/index.tsx index 44b26be08..fc0dba378 100644 --- a/website/pages/index.tsx +++ b/website/pages/index.tsx @@ -13,7 +13,7 @@ import { LandingBanner, } from '../src/components/landing' import { H2 } from '../src/components/typography' -import { InlineCode } from '../src/components/code' +import { InlineCode } from '../src/components/inlineCode' import { Ul, Li } from '../src/components/list' import Button from '../src/components/button' import Link from '../src/components/link' diff --git a/website/src/components/code.js b/website/src/components/code.js index ccd5a3f77..09c2fabfc 100644 --- a/website/src/components/code.js +++ b/website/src/components/code.js @@ -14,96 +14,16 @@ import 'prismjs/components/prism-markdown.min.js' import 'prismjs/components/prism-python.min.js' import 'prismjs/components/prism-yaml.min.js' -import CUSTOM_TYPES from '../../meta/type-annotations.json' -import { isString, htmlToReact } from './util' +import { isString } from './util' import Link, { OptionalLink } from './link' import GitHubCode from './github' -import Juniper from './juniper' import classes from '../styles/code.module.sass' import siteMetadata from '../../meta/site.json' import { binderBranch } from '../../meta/dynamicMeta.mjs' +import dynamic from 'next/dynamic' -const WRAP_THRESHOLD = 30 const CLI_GROUPS = ['init', 'debug', 'project', 'ray', 'huggingface-hub'] -const CodeBlock = (props) => ( -
-        
-    
-) - -export default CodeBlock - -export const Pre = (props) => { - return
{props.children}
-} - -export const InlineCode = ({ wrap = false, className, children, ...props }) => { - const codeClassNames = classNames(classes['inline-code'], className, { - [classes['wrap']]: wrap || (isString(children) && children.length >= WRAP_THRESHOLD), - }) - return ( - - {children} - - ) -} - -InlineCode.propTypes = { - wrap: PropTypes.bool, - className: PropTypes.string, - children: PropTypes.node, -} - -function linkType(el, showLink = true) { - if (!isString(el) || !el.length) return el - const elStr = el.trim() - if (!elStr) return el - const typeUrl = CUSTOM_TYPES[elStr] - const url = typeUrl == true ? DEFAULT_TYPE_URL : typeUrl - const ws = el[0] == ' ' - return url && showLink ? ( - - {ws && ' '} - - {elStr} - - - ) : ( - el - ) -} - -export const TypeAnnotation = ({ lang = 'python', link = true, children }) => { - // Hacky, but we're temporarily replacing a dot to prevent it from being split during highlighting - const TMP_DOT = '۔' - const code = Array.isArray(children) ? children.join('') : children || '' - const [rawText, meta] = code.split(/(?= \(.+\)$)/) - const rawStr = rawText.replace(/\./g, TMP_DOT) - const rawHtml = - lang === 'none' || !code ? code : Prism.highlight(rawStr, Prism.languages[lang], lang) - const html = rawHtml.replace(new RegExp(TMP_DOT, 'g'), '.').replace(/\n/g, ' ') - const result = htmlToReact(html) - const elements = Array.isArray(result) ? result : [result] - const annotClassNames = classNames( - 'type-annotation', - `language-${lang}`, - classes['inline-code'], - classes['type-annotation'], - { - [classes['wrap']]: code.length >= WRAP_THRESHOLD, - } - ) - return ( - - {elements.map((el, i) => ( - {linkType(el, !!link)} - ))} - {meta && {meta}} - - ) -} - const splitLines = (children) => { const listChildrenPerLine = [] @@ -288,7 +208,7 @@ const addLineHighlight = (children, highlight) => { }) } -export const CodeHighlighted = ({ children, highlight, lang }) => { +const CodeHighlighted = ({ children, highlight, lang }) => { const [html, setHtml] = useState() useEffect( @@ -305,7 +225,7 @@ export const CodeHighlighted = ({ children, highlight, lang }) => { return <>{html} } -export class Code extends React.Component { +export default class Code extends React.Component { static defaultProps = { lang: 'none', executable: null, @@ -354,6 +274,8 @@ export class Code extends React.Component { } } +const JuniperDynamic = dynamic(() => import('./juniper')) + const JuniperWrapper = ({ title, lang, children }) => { const { binderUrl, binderVersion } = siteMetadata const juniperTitle = title || 'Editable Code' @@ -369,7 +291,7 @@ const JuniperWrapper = ({ title, lang, children }) => { - { }} > {children} - + ) } diff --git a/website/src/components/codeBlock.js b/website/src/components/codeBlock.js new file mode 100644 index 000000000..d990b93dd --- /dev/null +++ b/website/src/components/codeBlock.js @@ -0,0 +1,14 @@ +import React from 'react' +import Code from './codeDynamic' +import classes from '../styles/code.module.sass' + +export const Pre = (props) => { + return
{props.children}
+} + +const CodeBlock = (props) => ( +
+        
+    
+) +export default CodeBlock diff --git a/website/src/components/codeDynamic.js b/website/src/components/codeDynamic.js new file mode 100644 index 000000000..8c9483567 --- /dev/null +++ b/website/src/components/codeDynamic.js @@ -0,0 +1,5 @@ +import dynamic from 'next/dynamic' + +export default dynamic(() => import('./code'), { + loading: () =>
Loading...
, +}) diff --git a/website/src/components/embed.js b/website/src/components/embed.js index cc94c9738..ad15a0b8b 100644 --- a/website/src/components/embed.js +++ b/website/src/components/embed.js @@ -5,8 +5,8 @@ import ImageNext from 'next/image' import Link from './link' import Button from './button' -import { InlineCode } from './code' -import { MarkdownToReact } from './util' +import { InlineCode } from './inlineCode' +import MarkdownToReact from './markdownToReactDynamic' import classes from '../styles/embed.module.sass' diff --git a/website/src/components/github.js b/website/src/components/github.js index d967011ed..a609f893c 100644 --- a/website/src/components/github.js +++ b/website/src/components/github.js @@ -5,7 +5,7 @@ import classNames from 'classnames' import Icon from './icon' import Link from './link' import classes from '../styles/code.module.sass' -import { Code } from './code' +import Code from './codeDynamic' const defaultErrorMsg = `Can't fetch code example from GitHub :( diff --git a/website/src/components/htmlToReact.js b/website/src/components/htmlToReact.js new file mode 100644 index 000000000..443ac2dc9 --- /dev/null +++ b/website/src/components/htmlToReact.js @@ -0,0 +1,12 @@ +import { Parser as HtmlToReactParser } from 'html-to-react' + +const htmlToReactParser = new HtmlToReactParser() +/** + * Convert raw HTML to React elements + * @param {string} html - The HTML markup to convert. + * @returns {Node} - The converted React elements. + */ + +export default function HtmlToReact(props) { + return htmlToReactParser.parse(props.children) +} diff --git a/website/src/components/inlineCode.js b/website/src/components/inlineCode.js new file mode 100644 index 000000000..53c30e649 --- /dev/null +++ b/website/src/components/inlineCode.js @@ -0,0 +1,23 @@ +import React from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import { isString } from './util' +import classes from '../styles/code.module.sass' + +const WRAP_THRESHOLD = 30 + +export const InlineCode = ({ wrap = false, className, children, ...props }) => { + const codeClassNames = classNames(classes['inline-code'], className, { + [classes['wrap']]: wrap || (isString(children) && children.length >= WRAP_THRESHOLD), + }) + return ( + + {children} + + ) +} +InlineCode.propTypes = { + wrap: PropTypes.bool, + className: PropTypes.string, + children: PropTypes.node, +} diff --git a/website/src/components/landing.js b/website/src/components/landing.js index 08e0d678b..86c4d494a 100644 --- a/website/src/components/landing.js +++ b/website/src/components/landing.js @@ -11,7 +11,7 @@ import overlayLegacy from '../images/pattern_landing_legacy.png' import Grid from './grid' import { Content } from './main' import Button from './button' -import CodeBlock from './code' +import CodeBlock from './codeBlock' import { H1, H2, H3 } from './typography' import Link from './link' import classes from '../styles/landing.module.sass' diff --git a/website/src/components/markdownToReact.js b/website/src/components/markdownToReact.js new file mode 100644 index 000000000..054cd4db4 --- /dev/null +++ b/website/src/components/markdownToReact.js @@ -0,0 +1,32 @@ +import React, { useEffect, useState } from 'react' +import { serialize } from 'next-mdx-remote/serialize' +import { MDXRemote } from 'next-mdx-remote' +import remarkPlugins from '../../plugins/index.mjs' + +/** + * Convert raw Markdown to React + * @param {String} markdown - The Markdown markup to convert. + * @param {Object} [remarkReactComponents] - Optional React components to use + * for HTML elements. + * @returns {Node} - The converted React elements. + */ +export default function MarkdownToReact({ markdown }) { + const [mdx, setMdx] = useState(null) + + useEffect(() => { + const getMdx = async () => { + setMdx( + await serialize(markdown, { + parseFrontmatter: false, + mdxOptions: { + remarkPlugins, + }, + }) + ) + } + + getMdx() + }, [markdown]) + + return mdx ? : <> +} diff --git a/website/src/components/markdownToReactDynamic.js b/website/src/components/markdownToReactDynamic.js new file mode 100644 index 000000000..273d374c7 --- /dev/null +++ b/website/src/components/markdownToReactDynamic.js @@ -0,0 +1,5 @@ +import dynamic from 'next/dynamic' + +export default dynamic(() => import('./markdownToReact'), { + loading: () =>

Loading...

, +}) diff --git a/website/src/components/title.js b/website/src/components/title.js index 64754d7d6..c79b094ef 100644 --- a/website/src/components/title.js +++ b/website/src/components/title.js @@ -5,7 +5,7 @@ import classNames from 'classnames' import Button from './button' import Tag from './tag' import { OptionalLink } from './link' -import { InlineCode } from './code' +import { InlineCode } from './inlineCode' import { H1, Label, InlineList, Help } from './typography' import Icon from './icon' diff --git a/website/src/components/typeAnnotation.js b/website/src/components/typeAnnotation.js new file mode 100644 index 000000000..ba4ec6657 --- /dev/null +++ b/website/src/components/typeAnnotation.js @@ -0,0 +1,51 @@ +import React from 'react' +import classNames from 'classnames' +import CUSTOM_TYPES from '../../meta/type-annotations.json' +import Link from './link' +import classes from '../styles/code.module.sass' + +export const WRAP_THRESHOLD = 30 + +const specialCharacterList = ['[', ']', ',', ', '] + +const highlight = (element) => + specialCharacterList.includes(element) ? ( + {element} + ) : ( + element + ) + +function linkType(el, showLink = true, key) { + if (!el.length) return el + const elStr = el.trim() + if (!elStr) return el + const typeUrl = CUSTOM_TYPES[elStr] + const url = typeUrl == true ? DEFAULT_TYPE_URL : typeUrl + return url && showLink ? ( + + {elStr} + + ) : ( + highlight(el) + ) +} + +export const TypeAnnotation = ({ lang = 'python', link = true, children }) => { + const code = Array.isArray(children) ? children.join('') : children || '' + const [rawText, meta] = code.split(/(?= \(.+\)$)/) + const annotClassNames = classNames( + 'type-annotation', + `language-${lang}`, + classes['inline-code'], + classes['type-annotation'], + { + [classes['wrap']]: code.length >= WRAP_THRESHOLD, + } + ) + return ( + + {rawText.split(/(\[|\]|,)/).map((el, i) => linkType(el, !!link, i))} + {meta && {meta}} + + ) +} diff --git a/website/src/components/util.js b/website/src/components/util.js index 17f33f86d..cbd41afc3 100644 --- a/website/src/components/util.js +++ b/website/src/components/util.js @@ -1,12 +1,6 @@ -import React, { Fragment, useEffect, useState } from 'react' -import { Parser as HtmlToReactParser } from 'html-to-react' +import React, { Fragment } from 'react' import siteMetadata from '../../meta/site.json' import { domain } from '../../meta/dynamicMeta.mjs' -import remarkPlugins from '../../plugins/index.mjs' -import { serialize } from 'next-mdx-remote/serialize' -import { MDXRemote } from 'next-mdx-remote' - -const htmlToReactParser = new HtmlToReactParser() const isNightly = siteMetadata.nightlyBranches.includes(domain) export const DEFAULT_BRANCH = isNightly ? 'develop' : 'master' @@ -70,43 +64,6 @@ export function isEmptyObj(obj) { return Object.entries(obj).length === 0 && obj.constructor === Object } -/** - * Convert raw HTML to React elements - * @param {string} html - The HTML markup to convert. - * @returns {Node} - The converted React elements. - */ -export function htmlToReact(html) { - return htmlToReactParser.parse(html) -} - -/** - * Convert raw Markdown to React - * @param {String} markdown - The Markdown markup to convert. - * @param {Object} [remarkReactComponents] - Optional React components to use - * for HTML elements. - * @returns {Node} - The converted React elements. - */ -export function MarkdownToReact({ markdown }) { - const [mdx, setMdx] = useState(null) - - useEffect(() => { - const getMdx = async () => { - setMdx( - await serialize(markdown, { - parseFrontmatter: false, - mdxOptions: { - remarkPlugins, - }, - }) - ) - } - - getMdx() - }, [markdown]) - - return mdx ? : <> -} - /** * Join an array of nodes with a given string delimiter, like Array.join for React * @param {Array} arr - The elements to join. diff --git a/website/src/remark.js b/website/src/remark.js index 1aae589e3..7e5499b01 100644 --- a/website/src/remark.js +++ b/website/src/remark.js @@ -1,7 +1,10 @@ import Link from './components/link' import Section, { Hr } from './components/section' import { Table, Tr, Th, Tx, Td } from './components/table' -import CodeBlock, { Pre, Code, InlineCode, TypeAnnotation } from './components/code' +import Code from './components/codeDynamic' +import { TypeAnnotation } from './components/typeAnnotation' +import { InlineCode } from './components/inlineCode' +import CodeBlock, { Pre } from './components/codeBlock' import { Ol, Ul, Li } from './components/list' import { H2, H3, H4, H5, P, Abbr, Help, Label } from './components/typography' import Accordion from './components/accordion' diff --git a/website/src/templates/index.js b/website/src/templates/index.js index fd0d4a622..227b25be8 100644 --- a/website/src/templates/index.js +++ b/website/src/templates/index.js @@ -13,7 +13,7 @@ import Progress from '../components/progress' import Footer from '../components/footer' import SEO from '../components/seo' import Link from '../components/link' -import { InlineCode } from '../components/code' +import { InlineCode } from '../components/inlineCode' import Alert from '../components/alert' import Search from '../components/search' diff --git a/website/src/templates/models.js b/website/src/templates/models.js index 38f6f4b3d..2cca5c575 100644 --- a/website/src/templates/models.js +++ b/website/src/templates/models.js @@ -5,7 +5,8 @@ import Title from '../components/title' import Section from '../components/section' import Button from '../components/button' import Aside from '../components/aside' -import CodeBlock, { InlineCode } from '../components/code' +import { InlineCode } from '../components/inlineCode' +import CodeBlock from '../components/codeBlock' import { Table, Tr, Td, Th } from '../components/table' import Tag from '../components/tag' import { H2, Label } from '../components/typography' @@ -13,14 +14,8 @@ import Icon from '../components/icon' import Link, { OptionalLink } from '../components/link' import Infobox from '../components/infobox' import Accordion from '../components/accordion' -import { - isString, - isEmptyObj, - join, - arrayToObj, - abbrNum, - MarkdownToReact, -} from '../components/util' +import { isString, isEmptyObj, join, arrayToObj, abbrNum } from '../components/util' +import MarkdownToReact from '../components/markdownToReactDynamic' import siteMetadata from '../../meta/site.json' import languages from '../../meta/languages.json' diff --git a/website/src/templates/universe.js b/website/src/templates/universe.js index e72356924..75a8a6feb 100644 --- a/website/src/templates/universe.js +++ b/website/src/templates/universe.js @@ -8,7 +8,8 @@ import Grid from '../components/grid' import Button from '../components/button' import Icon from '../components/icon' import Tag from '../components/tag' -import CodeBlock, { InlineCode } from '../components/code' +import { InlineCode } from '../components/inlineCode' +import CodeBlock from '../components/codeBlock' import Aside from '../components/aside' import Sidebar from '../components/sidebar' import Section, { Hr } from '../components/section' @@ -16,7 +17,8 @@ import Main from '../components/main' import Footer from '../components/footer' import { H3, H5, Label, InlineList } from '../components/typography' import { YouTube, SoundCloud, Iframe } from '../components/embed' -import { github, MarkdownToReact } from '../components/util' +import { github } from '../components/util' +import MarkdownToReact from '../components/markdownToReactDynamic' import { nightly, legacy } from '../../meta/dynamicMeta.mjs' import universe from '../../meta/universe.json' diff --git a/website/src/widgets/changelog.js b/website/src/widgets/changelog.js index 2db06b78d..fafc252fb 100644 --- a/website/src/widgets/changelog.js +++ b/website/src/widgets/changelog.js @@ -2,7 +2,7 @@ import React, { useState, useEffect, Fragment } from 'react' import { window } from 'browser-monads' import Link from '../components/link' -import { InlineCode } from '../components/code' +import { InlineCode } from '../components/inlineCode' import { Label, H3 } from '../components/typography' import { Table, Tr, Th, Td } from '../components/table' import Infobox from '../components/infobox' diff --git a/website/src/widgets/languages.js b/website/src/widgets/languages.js index 9f5f9e23f..1ddf95675 100644 --- a/website/src/widgets/languages.js +++ b/website/src/widgets/languages.js @@ -1,7 +1,7 @@ import React from 'react' import Link from '../components/link' -import { InlineCode } from '../components/code' +import { InlineCode } from '../components/inlineCode' import { Table, Tr, Th, Td } from '../components/table' import { Ul, Li } from '../components/list' import Infobox from '../components/infobox' diff --git a/website/src/widgets/project.js b/website/src/widgets/project.js index 5841e93e8..339f1e054 100644 --- a/website/src/widgets/project.js +++ b/website/src/widgets/project.js @@ -3,7 +3,7 @@ import React from 'react' import CopyInput from '../components/copy' import Infobox from '../components/infobox' import Link from '../components/link' -import { InlineCode } from '../components/code' +import { InlineCode } from '../components/inlineCode' import { projectsRepo } from '../components/util' const COMMAND = 'python -m spacy project clone' diff --git a/website/src/widgets/quickstart-training.js b/website/src/widgets/quickstart-training.js index 57691edd4..62bdf1e0b 100644 --- a/website/src/widgets/quickstart-training.js +++ b/website/src/widgets/quickstart-training.js @@ -5,8 +5,8 @@ import 'prismjs/components/prism-ini.min.js' import { Quickstart } from '../components/quickstart' import generator, { DATA as GENERATOR_DATA } from './quickstart-training-generator' -import { htmlToReact } from '../components/util' import models from '../../meta/languages.json' +import dynamic from 'next/dynamic' const DEFAULT_LANG = 'en' const DEFAULT_HARDWARE = 'cpu' @@ -70,6 +70,10 @@ const DATA = [ }, ] +const HtmlToReactDynamic = dynamic(() => import('../components/htmlToReact'), { + loading: () => <>, +}) + export default function QuickstartTraining({ id, title, download = 'base_config.cfg' }) { const [lang, setLang] = useState(DEFAULT_LANG) const [_components, _setComponents] = useState([]) @@ -134,7 +138,7 @@ export default function QuickstartTraining({ id, title, download = 'base_config. small codeLang="ini" > - {htmlToReact(displayContent)} + {displayContent} ) }