1
1
mirror of https://github.com/c8r/x0.git synced 2024-08-16 17:00:24 +03:00

Add scope and matching

This commit is contained in:
Brent Jackson 2018-06-19 11:41:18 -04:00
parent a1ddd164fe
commit 74818b7066
14 changed files with 235 additions and 104 deletions

View File

@ -367,9 +367,13 @@ See the [example](https://github.com/c8r/x0/tree/master/examples/webpack-config)
**REMOVE BEFORE MERGING**
- [ ] deep require context
- [ ] minimatch
- [ ] default route sorting
- [ ] markdown scope
- [ ] move client modules to src
- [ ] adjust resolve
- [ ] default route sorting
- [x] deep require context
- [ ] route dirname/full path
- [ ] minimatch
- [ ] pass front-matter as props
- [ ] props.Component in custom apps
- [x] move client modules to src
- [x] adjust resolve

4
cli.js
View File

@ -28,6 +28,7 @@ const cli = meow(`
${chalk.gray('Options')}
--webpack Path to webpack config file
--match String to match routes against using minimatch
${chalk.gray('Dev Server')}
@ -68,6 +69,9 @@ const cli = meow(`
type: 'string',
alias: 'c'
},
match: {
type: 'string'
},
scope: {
type: 'string',
},

36
docs/_app.js Normal file
View File

@ -0,0 +1,36 @@
import React from 'react'
import * as scope from 'rebass'
import { ScopeProvider } from '../src'
import {
Flex,
Box,
Container,
} from 'rebass'
export default class App extends React.Component {
static defaultProps = {
title: 'Hello'
}
render () {
const { render } = this.props
console.log('custom app', this.props.routes)
return (
<ScopeProvider scope={scope}>
<Flex>
{false && (
<Box p={2} flex='none' width={192}>
custom app
</Box>
)}
<Box width={1} p={3}>
<Container maxWidth={768}>
{render()}
</Container>
</Box>
</Flex>
</ScopeProvider>
)
}
}

View File

@ -9,5 +9,5 @@ This is a standard markdown file.
This is a live/editable code block:
```.jsx
<h1>Hello</h1>
<Box p={4} bg='tomato'>Hello</Box>
```

6
docs/examples/Button.md Normal file
View File

@ -0,0 +1,6 @@
# Button
```.jsx
<Button>Button</Button>
```

2
docs/examples/index.md Normal file
View File

@ -0,0 +1,2 @@
# Examples

View File

@ -165,7 +165,8 @@ module.exports = async (opts) => {
DEV: JSON.stringify(false),
OPTIONS: JSON.stringify(opts),
DIRNAME: JSON.stringify(opts.dirname),
APP: JSON.stringify(opts.app)
APP: JSON.stringify(opts.app),
MATCH: JSON.stringify(opts.match)
})
)

View File

@ -50,6 +50,7 @@ module.exports = async (opts) => {
OPTIONS: JSON.stringify(opts),
DIRNAME: JSON.stringify(opts.dirname),
APP: JSON.stringify(opts.app),
MATCH: JSON.stringify(opts.match)
})
)

View File

@ -30,6 +30,8 @@
"@compositor/log": "^1.0.0-0",
"@mdx-js/loader": "^0.11.0",
"@mdx-js/mdx": "^0.10.1",
"@mdx-js/tag": "^0.11.0",
"@rebass/markdown": "^1.0.0-1",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.4",
"babel-plugin-macros": "^2.2.2",
@ -50,12 +52,14 @@
"koa-connect": "^2.0.1",
"meow": "^5.0.0",
"mini-html-webpack-plugin": "^0.2.3",
"minimatch": "^3.0.4",
"pkg-conf": "^2.1.0",
"react": "^16.4.1",
"react-dev-utils": "^5.0.1",
"react-dom": "^16.4.1",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-scope-provider": "^1.0.0-1",
"read-pkg-up": "^3.0.0",
"remark-autolink-headings": "^5.0.0",
"remark-emoji": "^2.0.1",
@ -69,13 +73,12 @@
},
"devDependencies": {
"@compositor/logo": "^1.4.0",
"@compositor/md-loader": "^1.0.34",
"ava": "^0.25.0",
"isomorphic-fetch": "^2.2.1",
"nano-style": "^1.0.0",
"nyc": "^12.0.1",
"raw-loader": "^0.5.1",
"rebass": "^2.0.0-7",
"rebass": "^2.0.0-6",
"refunk": "^3.0.1",
"rimraf": "^2.6.2",
"styled-components": "^3.3.2",

40
src/Catch.js Normal file
View File

@ -0,0 +1,40 @@
import React from 'react'
export default class Catch extends React.Component {
static getDerivedStateFromProps (props, state) {
if (!state.err) return null
return { err: null }
}
state = {
err: null
}
componentDidCatch (err) {
this.setState({ err })
}
render () {
const { err } = this.state
if (err) {
return (
<pre
children={err.toString()}
style={{
color: 'white',
backgroundColor: 'red',
fontFamily: 'Menlo, monospace',
fontSize: '14px',
margin: 0,
padding: '16px',
minHeight: '128px',
whiteSpace: 'prewrap'
}}
/>
)
}
return this.props.children
}
}

17
src/FileList.js Normal file
View File

@ -0,0 +1,17 @@
import React from 'react'
import { Link } from 'react-router-dom'
export default ({ routes = [] }) => (
<React.Fragment>
<pre>{DIRNAME}</pre>
<ul>
{routes.map(route => (
<li key={route.key}>
<Link to={route.path}>
{route.name}
</Link>
</li>
))}
</ul>
</React.Fragment>
)

30
src/LivePreview.js Normal file
View File

@ -0,0 +1,30 @@
import React from 'react'
import {
LiveProvider,
LivePreview,
LiveError
} from 'react-live'
import { ScopeConsumer } from 'react-scope-provider'
import { Box } from 'rebass'
const transformCode = str => `<React.Fragment>${str}</React.Fragment>`
export default ({
code,
scope
}) => (
<Box mb={4}>
<ScopeConsumer defaultScope={scope}>
{scope => (
<LiveProvider
code={code}
scope={scope}
mountStylesheet={false}
transformCode={transformCode}>
<LivePreview />
<LiveError />
</LiveProvider>
)}
</ScopeConsumer>
</Box>
)

22
src/ScrollTop.js Normal file
View File

@ -0,0 +1,22 @@
import React from 'react'
import { withRouter } from 'react-router-dom'
export default withRouter(class extends React.Component {
componentDidUpdate (prev) {
const { pathname, hash } = this.props.location
if (prev.location.pathname !== pathname) {
window.scrollTo(0, 0)
}
// check performance of this
if (hash) {
const el = document.getElementById(hash.slice(1))
if (!el) return
el.scrollIntoView()
}
}
render () {
return false
}
})

View File

@ -10,44 +10,41 @@ import {
Link,
withRouter
} from 'react-router-dom'
import minimatch from 'minimatch'
import Catch from './Catch'
import ScopeProvider from './ScopeProvider'
import FileList from './FileList'
import ScrollTop from './ScrollTop'
const IS_CLIENT = typeof document !== 'undefined'
const req = require.context(DIRNAME, false, /\.(js|md|mdx|jsx)$/)
const req = require.context(DIRNAME, true, /\.(js|md|mdx|jsx)$/)
const { filename, basename = '', disableScroll } = OPTIONS
const index = filename ? path.basename(filename, path.extname(filename)) : 'index'
const getComponents = req => req.keys().map(key => ({
key,
name: path.basename(key, path.extname(key)),
module: req(key),
Component: req(key).default || req(key)
}))
req.keys().forEach(key => {
console.log(key, minimatch(key.replace(/^\.\//, ''), MATCH) )
})
const getComponents = req => req.keys()
.filter(key => !MATCH || minimatch(key.replace(/^\.\//, ''), MATCH))
.map(key => ({
key,
name: path.basename(key, path.extname(key)),
module: req(key),
Component: req(key).default || req(key)
}))
.filter(component => !/^(\.|_)/.test(component.name))
.filter(component => typeof component.Component === 'function')
const initialComponents = getComponents(req)
const Index = ({ routes = [] }) => (
<React.Fragment>
<pre>{DIRNAME}</pre>
<ul>
{routes.map(route => (
<li key={route.key}>
<Link to={route.path}>
{route.name}
</Link>
</li>
))}
</ul>
</React.Fragment>
)
const DefaultApp = ({ render, routes }) => (
<Switch>
{render()}
<Route render={props => (
<Index
<FileList
{...props}
routes={routes}
/>
@ -55,58 +52,8 @@ const DefaultApp = ({ render, routes }) => (
</Switch>
)
class Catch extends React.Component {
static getDerivedStateFromProps (props, state) {
if (!state.err) return null
return { err: null }
}
state = {
err: null
}
componentDidCatch (err) {
this.setState({ err })
}
render () {
const { err } = this.state
if (err) {
return (
<pre
children={err.toString()}
style={{
color: 'white',
backgroundColor: 'red',
fontFamily: 'Menlo, monospace',
fontSize: '14px',
margin: 0,
padding: '16px',
minHeight: '128px',
whiteSpace: 'prewrap'
}}
/>
)
}
return this.props.children
}
}
const ScrollTop = withRouter(class extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
window.scrollTo(0, 0)
}
}
render () {
return false
}
})
const Router = IS_CLIENT ? BrowserRouter : StaticRouter
const App = withRouter(APP ? (require(APP).default || require(APP)) : DefaultApp)
const App = APP ? (require(APP).default || require(APP)) : DefaultApp
export const getRoutes = async (components = initialComponents) => {
const routes = await components.map(async ({ key, name, module, Component }) => {
@ -129,19 +76,29 @@ export const getRoutes = async (components = initialComponents) => {
return Promise.all(routes)
}
const RouterState = withRouter(({ render, ...props }) => {
const route = props.routes.find(r => r.path === props.location.pathname)
return render({ ...props, route })
})
export default class Root extends React.Component {
static defaultProps = {
path: '/',
basename
}
state = this.props
state = {
...this.props,
...App.defaultProps
}
render () {
const {
routes,
basename,
path = '/'
} = this.state
} = this.props
console.log('App', App.defaultProps)
return (
<Router
@ -149,28 +106,36 @@ export default class Root extends React.Component {
basename={basename}
location={path}>
<React.Fragment>
<Catch>
<App
routes={routes}
render={appProps => (
routes.map(({ Component, ...route }) => (
<Route
{...route}
render={props => (
<Catch>
<Component
{...props}
{...appProps}
{...route.props}
<ScopeProvider>
<Catch>
<RouterState
routes={routes}
render={(router) => (
<App
{...router}
routes={routes}
render={appProps => (
routes.map(({ Component, ...route }) => (
<Route
{...route}
render={props => (
<Catch>
<Component
{...props}
{...appProps}
{...route.props}
/>
</Catch>
)}
/>
</Catch>
))
)}
/>
))
)}
/>
</Catch>
{!disableScroll && <ScrollTop />}
)}
/>
</Catch>
{!disableScroll && <ScrollTop />}
</ScopeProvider>
</React.Fragment>
</Router>
)