Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.

docs(Examples): use sources to render dedicated examples, separate contexts #644

Merged
merged 7 commits into from
Jan 2, 2019
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
@@ -1,3 +1,5 @@
import { exampleSourcesContext } from '../../../utils'

interface SourceCodeData {
path: string
code: string
Expand Down Expand Up @@ -84,9 +86,9 @@ class SourceCodeManager {

private safeRequire = (path: string): string | undefined => {
try {
const filename = `${path.replace(/^components\//, '')}.source.json`
const filename = `${path.replace(/^components\//, './')}.source.json`

return require(`!docs/src/exampleSources/${filename}`).js
return exampleSourcesContext(filename).js
} catch (e) {
return undefined
}
Expand Down
29 changes: 14 additions & 15 deletions docs/src/components/ComponentDoc/ComponentExamples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as _ from 'lodash'
import * as PropTypes from 'prop-types'
import * as React from 'react'

import { exampleContext } from 'docs/src/utils'
import { exampleIndexContext, exampleSourcesContext } from 'docs/src/utils'
import { Grid, List } from 'semantic-ui-react'
import { examplePathPatterns } from './ComponentExample'
import ContributionPrompt from './ContributionPrompt'
Expand Down Expand Up @@ -30,23 +30,22 @@ export default class ComponentExamples extends React.Component<ComponentExamples
*/
private renderExamples = (): JSX.Element | null => {
const { displayName } = this.props
const allPaths = exampleContext.keys()

// rule #1
const indexPath = _.find(allPaths, path =>
const indexPath = _.find(exampleIndexContext.keys(), path =>
new RegExp(`\/${displayName}\/index\.tsx$`).test(path),
)
if (!indexPath) {
return null
}

const ExamplesElement = React.createElement(exampleContext(indexPath).default) as any
const ExamplesElement = React.createElement(exampleIndexContext(indexPath).default) as any
if (!ExamplesElement) {
return null
}

// rules #2 and #3
const missingPaths = this.testExamplesStructure(displayName, allPaths)
const missingPaths = this.getMissingExamplePaths(displayName, exampleSourcesContext.keys())
return missingPaths && missingPaths.length ? (
<div>
{this.renderMissingShorthandExamples(missingPaths)} {ExamplesElement}
Expand Down Expand Up @@ -81,24 +80,24 @@ export default class ComponentExamples extends React.Component<ComponentExamples
</Grid>
)

private testExamplesStructure(displayName: string, allPaths: string[]): string[] {
const examplesPattern = `\.\/\\w*\/${displayName}[\\w\/]*\/\\w+Example`
const allExamplesRegExp = new RegExp(`${examplesPattern}[\\w\.]*\.tsx$`)

private getMissingExamplePaths(displayName: string, allPaths: string[]): string[] {
const examplesPattern = `\./${displayName}/[\\w/]+Example`
const [normalExtension, shorthandExtension] = [
examplePathPatterns.normal,
examplePathPatterns.shorthand,
].map(pattern => `${pattern}.tsx`)
].map(pattern => `${pattern}.source.json`)

const [normalRegExp, shorthandRegExp] = [normalExtension, shorthandExtension].map(
extension => new RegExp(`${examplesPattern}\\w*${extension}$`),
extension => new RegExp(`${examplesPattern}${extension}$`),
)

const allExamplesPaths = allPaths.filter(path => allExamplesRegExp.test(path))
const expectedShorthandExamples = allExamplesPaths
const expectedShorthandExamples = allPaths
.filter(path => normalRegExp.test(path))
.map(path => path.replace(normalExtension, shorthandExtension))
const actualShorthandExamples = allExamplesPaths.filter(path => shorthandRegExp.test(path))
const actualShorthandExamples = allPaths.filter(path => shorthandRegExp.test(path))

return _.difference(expectedShorthandExamples, actualShorthandExamples)
return _.difference(expectedShorthandExamples, actualShorthandExamples).map(exampleFile =>
exampleFile.replace(/\.source\.json$/, '.tsx'),
)
}
}
40 changes: 31 additions & 9 deletions docs/src/components/ExternalExampleLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
import * as _ from 'lodash/fp'
import * as PropTypes from 'prop-types'
import * as React from 'react'

import { exampleContext, exampleKebabNameToFilename, parseExamplePath } from 'docs/src/utils'
import SourceRender from 'react-source-render'

import { ExampleSource } from 'docs/src/types'
import {
exampleSourcesContext,
exampleKebabNameToSourceFilename,
parseExamplePath,
} from 'docs/src/utils'
import PageNotFound from '../views/PageNotFound'
import { babelConfig, importResolver } from './Playground/renderConfig'

const examplePaths = exampleContext.keys()
const examplePaths = exampleSourcesContext.keys()

const ExternalExampleLayout: any = props => {
const { exampleName } = props.match.params
const exampleFilename = exampleKebabNameToFilename(exampleName)
const exampleFilename = exampleKebabNameToSourceFilename(exampleName)

const examplePath = _.find(path => {
const { exampleName } = parseExamplePath(path)
return exampleFilename === exampleName
}, examplePaths)

if (!examplePath) return <PageNotFound />

const ExampleComponent = exampleContext(examplePath).default
if (!ExampleComponent) return <PageNotFound />

return <ExampleComponent />
const exampleSource: ExampleSource = exampleSourcesContext(examplePath)

return (
<SourceRender
Copy link
Contributor

Choose a reason for hiding this comment

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

still not sure why we are relying on the React Context functionality to render source code - but this is definitely not the point of this PR, we could address this moment later (if necessary)

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm looking for a better solution there.

babelConfig={babelConfig}
source={exampleSource.js}
renderHtml={false}
resolver={importResolver}
>
<SourceRender.Consumer>
{({ element, error }) => (
<>
{element}
{/* This block allows to see issues with examples as visual regressions. */}
{error && <div style={{ fontSize: '5rem', color: 'red' }}>{error.toString()}</div>}
Copy link
Member Author

Choose a reason for hiding this comment

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

It's a little clumsy, but it will show us broken examples as visual regressions.

</>
)}
</SourceRender.Consumer>
</SourceRender>
)
}

ExternalExampleLayout.propTypes = {
Expand Down
11 changes: 0 additions & 11 deletions docs/src/utils/exampleContext.ts

This file was deleted.

13 changes: 13 additions & 0 deletions docs/src/utils/exampleContexts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* The Webpack Context for doc site example groups.
*/
export const exampleIndexContext = require.context('docs/src/examples/', true, /index.tsx$/)

/**
* The Webpack Context for doc site example sources.
*/
export const exampleSourcesContext = require.context(
'docs/src/exampleSources/',
true,
/.source.json$/,
)
15 changes: 0 additions & 15 deletions docs/src/utils/exampleKebabNameToFilename.ts

This file was deleted.

15 changes: 15 additions & 0 deletions docs/src/utils/exampleKebabNameToSourceFilename.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as _ from 'lodash'

/**
* Converts kebab-cased-example-name back into the original filename.
* @param {string} exampleKebabName
*/
const exampleKebabNameToSourceFilename = (exampleKebabName: string) => {
// button-example => ButtonExample.source.json
// button-example-shorthand => ButtonExample.shorthand.source.json
return `${_.startCase(exampleKebabName)
.replace(/ /g, '')
Copy link
Contributor

Choose a reason for hiding this comment

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

we can be a bit more expressive here. The only source of the whitespace could be either leading or trailing ones of the initial kebab-cased name. So, we might eliminate them before transform will happen, like that:

const trimmedExampleKebabName = exampleKebabName.trim()

return `${_.startCase(trimmedExampleKebabName) ... }...`

While this makes code more efficient, I would put efficiency as the least worthy benefit in this case - the primary goal of this move is rather to improve explicitness of where the spaces (that regex aims to eliminate) might happen from.

Copy link
Member Author

@layershifter layershifter Jan 2, 2019

Choose a reason for hiding this comment

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

Proposed solution is not correct because _.startCase() will create spaces:

_.startCase('foo-bar')
// => 'Foo Bar'

.replace(/Shorthand$/, '.shorthand')}.source.json`
}

export default exampleKebabNameToSourceFilename
4 changes: 2 additions & 2 deletions docs/src/utils/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { default as componentInfoContext } from './componentInfoContext'
export { default as componentInfoShape } from './componentInfoShape'
export { default as exampleContext } from './exampleContext'
export { default as exampleKebabNameToFilename } from './exampleKebabNameToFilename'
export { exampleIndexContext, exampleSourcesContext } from './exampleContexts'
export { default as exampleKebabNameToSourceFilename } from './exampleKebabNameToSourceFilename'
export { default as examplePathToHash } from './examplePathToHash'
export { default as getComponentGroup } from './getComponentGroup'
export { default as getComponentPathname } from './getComponentPathname'
Expand Down