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`
This commit is contained in:
Marcus Blättermann 2023-01-25 17:30:41 +01:00 committed by GitHub
parent 07dfa54669
commit 056b73468c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 179 additions and 154 deletions

View File

@ -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'

View File

@ -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) => (
<Pre>
<Code {...props} />
</Pre>
)
export default CodeBlock
export const Pre = (props) => {
return <pre className={classes['pre']}>{props.children}</pre>
}
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 (
<code className={codeClassNames} {...props}>
{children}
</code>
)
}
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 ? (
<Fragment>
{ws && ' '}
<Link to={url} hideIcon>
{elStr}
</Link>
</Fragment>
) : (
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 (
<span className={annotClassNames} role="code" aria-label="Type annotation">
{elements.map((el, i) => (
<Fragment key={i}>{linkType(el, !!link)}</Fragment>
))}
{meta && <span className={classes['type-annotation-meta']}>{meta}</span>}
</span>
)
}
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 }) => {
</span>
</h4>
<Juniper
<JuniperDynamic
repo={binderUrl}
branch={binderBranch}
lang={lang}
@ -381,7 +303,7 @@ const JuniperWrapper = ({ title, lang, children }) => {
}}
>
{children}
</Juniper>
</JuniperDynamic>
</div>
)
}

View File

@ -0,0 +1,14 @@
import React from 'react'
import Code from './codeDynamic'
import classes from '../styles/code.module.sass'
export const Pre = (props) => {
return <pre className={classes['pre']}>{props.children}</pre>
}
const CodeBlock = (props) => (
<Pre>
<Code {...props} />
</Pre>
)
export default CodeBlock

View File

@ -0,0 +1,5 @@
import dynamic from 'next/dynamic'
export default dynamic(() => import('./code'), {
loading: () => <div style={{ color: 'white', padding: '1rem' }}>Loading...</div>,
})

View File

@ -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'

View File

@ -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 :(

View File

@ -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)
}

View File

@ -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 (
<code className={codeClassNames} {...props}>
{children}
</code>
)
}
InlineCode.propTypes = {
wrap: PropTypes.bool,
className: PropTypes.string,
children: PropTypes.node,
}

View File

@ -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'

View File

@ -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 ? <MDXRemote {...mdx} /> : <></>
}

View File

@ -0,0 +1,5 @@
import dynamic from 'next/dynamic'
export default dynamic(() => import('./markdownToReact'), {
loading: () => <p>Loading...</p>,
})

View File

@ -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'

View File

@ -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) ? (
<span className={classes['cli-arg-subtle']}>{element}</span>
) : (
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 ? (
<Link to={url} hideIcon key={key}>
{elStr}
</Link>
) : (
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 (
<span className={annotClassNames} role="code" aria-label="Type annotation">
{rawText.split(/(\[|\]|,)/).map((el, i) => linkType(el, !!link, i))}
{meta && <span className={classes['type-annotation-meta']}>{meta}</span>}
</span>
)
}

View File

@ -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 ? <MDXRemote {...mdx} /> : <></>
}
/**
* Join an array of nodes with a given string delimiter, like Array.join for React
* @param {Array} arr - The elements to join.

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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)}
<HtmlToReactDynamic>{displayContent}</HtmlToReactDynamic>
</Quickstart>
)
}