@compositor/x0
Version:
Document & develop React components without breaking a sweat
197 lines (176 loc) • 5.12 kB
JavaScript
// Main application
import path from 'path'
import React from 'react'
import { render, hydrate } from 'react-dom'
import {
StaticRouter,
BrowserRouter,
Switch,
Route,
Link,
withRouter
} from 'react-router-dom'
import { Provider as RebassProvider } from 'rebass'
import minimatch from 'minimatch'
import sortBy from 'lodash.sortby'
import ScopeProvider from './ScopeProvider'
import Catch from './Catch'
import FileList from './FileList'
import ScrollTop from './ScrollTop'
import CenteredLayout from './CenteredLayout'
const IS_CLIENT = typeof document !== 'undefined'
const req = require.context(DIRNAME, true, /\.(js|md|mdx|jsx)$/)
const { filename, basename = '', disableScroll } = OPTIONS
const getComponents = req => req.keys()
.filter(minimatch.filter('!node_modules'))
.filter(key => !MATCH || minimatch(key.replace(/^\.\//, ''), MATCH))
.filter(key => !/^_/.test(path.basename(key)))
.map(key => ({
key,
name: path.basename(key, path.extname(key)),
module: req(key),
Component: req(key).default || req(key),
}))
.filter(component => typeof component.Component === 'function')
const initialComponents = getComponents(req)
const DefaultApp = props => props.children
const Router = IS_CLIENT ? BrowserRouter : StaticRouter
const appPath = req.keys().find(key => key === './_app.js')
const App = appPath ? (req(appPath).default || req(appPath)) : DefaultApp
export const getRoutes = async (components = initialComponents) => {
const promises = await components.map(async ({
key,
name,
module,
Component
}) => {
const exact = name === 'index'
const dirname = path.dirname(key).replace(/^\./, '')
const extname = path.extname(key)
let pathname = dirname + (exact ? '/' : '/' + name)
const href = pathname
const initialProps = Component.getInitialProps
? await Component.getInitialProps({ path: pathname })
: {}
const defaultProps = Component.defaultProps
const meta = module.frontMatter || {}
const props = { ...meta, ...initialProps, ...defaultProps }
// for dynamic routing
pathname = props.path || pathname
if (dirname && name === 'index') {
name = path.basename(dirname)
}
return {
key,
name,
extname,
href,
path: pathname,
dirname,
exact,
module,
Component,
props
}
})
const routes = await Promise.all(promises)
const filtered = routes
.filter(r => !r.props.ignore)
.filter(component => !/404/.test(component.name))
let sorted = [...filtered]
sorted = sortBy([...sorted], a => a.name)
sorted = sortBy([...sorted], a => !a.exact)
sorted = sortBy([...sorted], a => a.dirname)
sorted.notfound = routes.find(component => /404/.test(component.name))
return sorted
}
const RouterState = withRouter(({ render, ...props }) => {
const { pathname } = props.location
const route = props.routes.find(r => r.path === pathname || r.href === pathname) || { props: {} }
return render({ ...props, route })
})
export default class Root extends React.Component {
static defaultProps = {
path: '/',
basename
}
state = {
...this.props,
...App.defaultProps
}
render () {
const {
routes,
basename,
path = '/'
} = this.props
const NotFound = routes.notfound
? routes.notfound.Component
: FileList
const render = appProps => (
<Switch>
{routes.map(({ Component, ...route }) => (
<Route
{...route}
render={props => (
<Catch>
<CenteredLayout
active={!appPath && /md/.test(route.extname)}>
<Component
{...props}
{...appProps}
{...route.props}
/>
</CenteredLayout>
</Catch>
)}
/>
))}
<Route
render={props => <NotFound {...props} routes={routes} />}
/>
</Switch>
)
return (
<Router
context={{}}
basename={basename}
location={path}>
<React.Fragment>
<RebassProvider>
<ScopeProvider>
<Catch>
<RouterState
routes={routes}
render={(router) => (
<App
{...router}
routes={routes}
render={render}
Component={render}
children={render(router)}
/>
)}
/>
</Catch>
</ScopeProvider>
</RebassProvider>
{!disableScroll && <ScrollTop />}
</React.Fragment>
</Router>
)
}
}
if (IS_CLIENT) {
const mount = DEV ? render : hydrate
const div = window.root || document.body.appendChild(
document.createElement('div')
)
getRoutes()
.then(routes => {
mount(<Root routes={routes} />, div)
})
}
if (IS_CLIENT && module.hot) {
module.hot.accept()
}